You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@solr.apache.org by ge...@apache.org on 2023/08/18 15:45:16 UTC

[solr] 01/02: SOLR-16825: Generate v2 API SolrRequest bindings (#1793)

This is an automated email from the ASF dual-hosted git repository.

gerlowskija pushed a commit to branch branch_9x
in repository https://gitbox.apache.org/repos/asf/solr.git

commit 376170ffb801b5a80012be0939883df462e106f4
Author: Jason Gerlowski <ge...@apache.org>
AuthorDate: Tue Aug 8 14:14:01 2023 -0400

    SOLR-16825: Generate v2 API SolrRequest bindings  (#1793)
    
    Prior to this commit, SolrJ had no real coverage of v2 APIs beyond the
    generic V2Request class.  This commit introduces the first request-
    specific v2 SolrRequest implementations to SolrJ.
    
    Rather than duplicating details about the API here in SolrJ so that both
    the server-side API definition and the client objects need maintained
    by hand, this commit generates the v2 SolrRequest classes from the
    OpenAPI specification (which in turn is generated from the server-side
    API definition).  This ensures that there's only one "source of truth"
    that needs maintained for each API.
    
    To achieve this, this commit introduces a new gradle module, 'api', to
    hold interfaces (representing Solr's APIs) and POJOs (representing the
    API's inputs and outputs).  This module is all that is needed to generate
    Solr's OpenAPI spec, and for SolrJ in turn to generate SolrRequest
    implementations from that spec.
    
    This commit restricts v2 SolrRequest generation to a few example APIs
    to prove the approach.  Additional APIs will be added in subsequent
    commits.
    
    ---------
    
    Co-authored-by: Houston Putman <ho...@apache.org>
---
 dev-tools/scripts/smokeTestRelease.py              |   2 +-
 gradle/maven/defaults-maven.gradle                 |   1 +
 gradle/solr/packaging.gradle                       |   1 +
 gradle/validation/spotless.gradle                  |  36 +++--
 settings.gradle                                    |   1 +
 solr/CHANGES.txt                                   |   5 +
 solr/api/build.gradle                              |  61 +++++++++
 .../client/api/endpoint/AddReplicaPropertyApi.java |  60 ++++++++
 .../solr/client/api/endpoint/DeleteAliasApi.java   |  45 ++++++
 .../client/api/endpoint/DeleteCollectionApi.java   |  47 +++++++
 .../solr/client/api/endpoint/package-info.java}    |  10 +-
 .../api/model/AddReplicaPropertyRequestBody.java   |  41 ++++++
 .../client/api/model}/AsyncJerseyResponse.java     |   2 +-
 .../apache/solr/client/api/model}/ErrorInfo.java   |  18 ++-
 .../solr/client/api/model}/SolrJerseyResponse.java |   9 +-
 .../SubResponseAccumulatingJerseyResponse.java     |   2 +-
 .../solr/client/api/model/package-info.java}       |  13 +-
 .../org/apache/solr/client/api/package-info.java}  |  10 +-
 .../apache/solr/client/api/util/ApiMetadata.java}  |  23 ++--
 .../apache/solr/client/api/util/Constants.java}    |  11 +-
 .../solr/client/api/util/ReflectWritable.java}     |  16 ++-
 .../apache/solr/client/api}/util/SolrVersion.java  |  20 ++-
 .../apache/solr/client/api/util/package-info.java} |  10 +-
 .../test/org/apache/solr/util/TestSolrVersion.java |   1 +
 solr/core/build.gradle                             |  14 +-
 .../java/org/apache/solr/api/JerseyResource.java   |   2 +-
 .../java/org/apache/solr/cli/SimplePostTool.java   |   2 +-
 .../src/java/org/apache/solr/cli/VersionTool.java  |   2 +-
 .../solr/handler/IncrementalShardBackup.java       |   2 +-
 .../apache/solr/handler/ReplicationHandler.java    |   2 +-
 .../java/org/apache/solr/handler/SnapShooter.java  |   2 +-
 .../apache/solr/handler/admin/BackupCoreOp.java    |   3 +-
 .../solr/handler/admin/CollectionsHandler.java     |  28 ++--
 .../solr/handler/admin/ZookeeperReadAPI.java       |   2 +-
 ...icaPropertyAPI.java => AddReplicaProperty.java} |  65 ++-------
 .../solr/handler/admin/api/AdminAPIBase.java       |   2 +-
 .../solr/handler/admin/api/AliasPropertyAPI.java   |   2 +-
 .../solr/handler/admin/api/BackupCoreAPI.java      |   2 +-
 .../solr/handler/admin/api/BalanceReplicasAPI.java |   2 +-
 .../handler/admin/api/BalanceShardUniqueAPI.java   |   2 +-
 .../handler/admin/api/CollectionPropertyAPI.java   |   2 +-
 .../solr/handler/admin/api/CoreAdminAPIBase.java   |   2 +-
 .../solr/handler/admin/api/CoreReplicationAPI.java |   2 +-
 .../solr/handler/admin/api/CoreSnapshotAPI.java    |   2 +-
 .../solr/handler/admin/api/CreateAliasAPI.java     |   4 +-
 .../handler/admin/api/CreateCollectionAPI.java     |   2 +-
 .../admin/api/CreateCollectionBackupAPI.java       |   4 +-
 .../admin/api/CreateCollectionSnapshotAPI.java     |   2 +-
 .../solr/handler/admin/api/CreateReplicaAPI.java   |   2 +-
 .../solr/handler/admin/api/CreateShardAPI.java     |   2 +-
 .../api/{DeleteAliasAPI.java => DeleteAlias.java}  |  17 +--
 ...eteCollectionAPI.java => DeleteCollection.java} |  23 +---
 .../admin/api/DeleteCollectionBackupAPI.java       |   4 +-
 .../admin/api/DeleteCollectionSnapshotAPI.java     |   2 +-
 .../solr/handler/admin/api/DeleteNodeAPI.java      |   2 +-
 .../solr/handler/admin/api/DeleteReplicaAPI.java   |   2 +-
 .../admin/api/DeleteReplicaPropertyAPI.java        |   2 +-
 .../solr/handler/admin/api/DeleteShardAPI.java     |   2 +-
 .../solr/handler/admin/api/ForceLeaderAPI.java     |   2 +-
 .../solr/handler/admin/api/GetSchemaAPI.java       |   2 +-
 .../solr/handler/admin/api/GetSchemaFieldAPI.java  |   2 +-
 .../handler/admin/api/GetSchemaZkVersionAPI.java   |   2 +-
 .../solr/handler/admin/api/InstallCoreDataAPI.java |   2 +-
 .../handler/admin/api/InstallShardDataAPI.java     |   2 +-
 .../solr/handler/admin/api/ListAliasesAPI.java     |   2 +-
 .../admin/api/ListCollectionBackupsAPI.java        |   2 +-
 .../admin/api/ListCollectionSnapshotsAPI.java      |   2 +-
 .../solr/handler/admin/api/ListCollectionsAPI.java |   2 +-
 .../solr/handler/admin/api/MigrateReplicasAPI.java |   2 +-
 .../solr/handler/admin/api/NodeLoggingAPI.java     |   2 +-
 .../handler/admin/api/ReloadCollectionAPI.java     |   2 +-
 .../handler/admin/api/RenameCollectionAPI.java     |   2 +-
 .../solr/handler/admin/api/ReplaceNodeAPI.java     |   2 +-
 .../handler/admin/api/RestoreCollectionAPI.java    |   4 +-
 .../solr/handler/admin/api/RestoreCoreAPI.java     |   2 +-
 .../solr/handler/admin/api/SyncShardAPI.java       |   2 +-
 .../org/apache/solr/handler/api/V2ApiUtils.java    |  75 +++++-----
 .../solr/handler/configsets/ListConfigSetsAPI.java |   2 +-
 .../solr/jersey/CatchAllExceptionMapper.java       |   1 +
 .../apache/solr/jersey/ExperimentalResponse.java   |   1 +
 .../org/apache/solr/jersey/JerseyApplications.java |  11 --
 .../org/apache/solr/jersey/MessageBodyWriters.java |  15 +-
 .../solr/jersey/PostRequestDecorationFilter.java   |   1 +
 .../solr/jersey/PostRequestLoggingFilter.java      |   1 +
 .../org/apache/solr/jersey/RequestContextKeys.java |   1 +
 .../apache/solr/jersey/RequestMetricHandling.java  |   1 +
 .../apache/solr/packagemanager/PackageManager.java |  22 +--
 .../apache/solr/packagemanager/PackageUtils.java   |   9 --
 .../solr/packagemanager/RepositoryManager.java     |   7 +-
 .../org/apache/solr/security/PublicKeyAPI.java     |   2 +-
 .../apache/solr/servlet/CoreContainerProvider.java |   2 +-
 .../org/apache/solr/servlet/ResponseUtils.java     |   6 +-
 .../admin/api/AddReplicaPropertyAPITest.java       |  12 +-
 .../solr/handler/admin/api/DeleteAliasAPITest.java |   7 +-
 .../handler/admin/api/DeleteCollectionAPITest.java |   7 +-
 .../solr/jersey/JacksonReflectMapWriterTest.java   |  26 +---
 .../pages/stream-decorator-reference.adoc          |   2 +-
 .../query-guide/pages/stream-source-reference.adoc |   8 +-
 solr/solrj/build.gradle                            |  81 ++++++++++-
 .../solr/client/solrj/JacksonContentWriter.java}   |  32 +++--
 .../solr/client/solrj/JacksonParsingResponse.java} |  36 ++---
 .../org/apache/solr/common/DelegateMapWriter.java} |  34 +++--
 .../java/org/apache/solr/common/SolrException.java |   5 +-
 .../org/apache/solr/common/util/JavaBinCodec.java  |   6 +
 .../org/apache/solr/common/util/TextWriter.java    |   4 +
 .../solrj/src/resources/java-template/api.mustache | 152 +++++++++++++++++++++
 106 files changed, 833 insertions(+), 431 deletions(-)

diff --git a/dev-tools/scripts/smokeTestRelease.py b/dev-tools/scripts/smokeTestRelease.py
index 6835de75ed4..8276fcecd2a 100755
--- a/dev-tools/scripts/smokeTestRelease.py
+++ b/dev-tools/scripts/smokeTestRelease.py
@@ -606,7 +606,7 @@ def verifyUnpacked(java, artifact, unpackPath, gitRevision, version, testArgs):
     expected_src_root_folders = ['buildSrc', 'dev-docs', 'dev-tools', 'gradle', 'help', 'solr']
     expected_src_root_files = ['build.gradle', 'gradlew', 'gradlew.bat', 'settings.gradle', 'versions.lock', 'versions.props']
     expected_src_solr_files = ['build.gradle']
-    expected_src_solr_folders = ['benchmark',  'bin', 'modules', 'core', 'docker', 'documentation', 'example', 'licenses', 'packaging', 'distribution', 'prometheus-exporter', 'server', 'solr-ref-guide', 'solrj', 'solrj-streaming', 'solrj-zookeeper', 'test-framework', 'webapp', '.gitignore', '.gitattributes']
+    expected_src_solr_folders = ['benchmark',  'bin', 'modules', 'api', 'core', 'docker', 'documentation', 'example', 'licenses', 'packaging', 'distribution', 'prometheus-exporter', 'server', 'solr-ref-guide', 'solrj', 'solrj-streaming', 'solrj-zookeeper', 'test-framework', 'webapp', '.gitignore', '.gitattributes']
     is_in_list(in_root_folder, expected_src_root_folders)
     is_in_list(in_root_folder, expected_src_root_files)
     is_in_list(in_solr_folder, expected_src_solr_folders)
diff --git a/gradle/maven/defaults-maven.gradle b/gradle/maven/defaults-maven.gradle
index 936de2c0988..8d0ef8f2a43 100644
--- a/gradle/maven/defaults-maven.gradle
+++ b/gradle/maven/defaults-maven.gradle
@@ -24,6 +24,7 @@
 configure(rootProject) {
   ext {
     published = [
+        ":solr:api",
         ":solr:core",
         ":solr:solrj",
         ":solr:solrj-streaming",
diff --git a/gradle/solr/packaging.gradle b/gradle/solr/packaging.gradle
index b1993ab8194..bb3fd5703ab 100644
--- a/gradle/solr/packaging.gradle
+++ b/gradle/solr/packaging.gradle
@@ -58,6 +58,7 @@ configure(allprojects.findAll {project -> project.path.startsWith(":solr:modules
     dependencies {
       solrPlatformLibs project(":solr:core")
       solrPlatformLibs project(":solr:solrj")
+      solrPlatformLibs project(":solr:api")
       solrPlatformLibs project(":solr:solrj-zookeeper")
       // libExt has logging libs, which we don't want.  Lets users decide what they want.
       solrPlatformLibs project(path: ":solr:server", configuration: 'libExt')
diff --git a/gradle/validation/spotless.gradle b/gradle/validation/spotless.gradle
index 3239ac4b1a3..a8cb41e0f39 100644
--- a/gradle/validation/spotless.gradle
+++ b/gradle/validation/spotless.gradle
@@ -26,18 +26,32 @@ configure(project(":solr").subprojects) { prj ->
   plugins.withType(JavaPlugin) {
     prj.apply plugin: 'com.diffplug.spotless'
 
-    spotless {
-      java {
-        toggleOffOn() // obviously, only to be used sparingly.
+    ext {
+      spotlessJavaSetup = (Action){
+        it.toggleOffOn() // obviously, only to be used sparingly.
         // TODO: Work out how to support multiple different header files (we have
         // classes in the codebase that have original headers). We currently use
         // Apache RAT to enforce headers so this is of lesser priority.
         //
-        // licenseHeaderFile file("${resources}/asl-header.txt"), '^(\\s*package)'
+        // it.licenseHeaderFile(file("${resources}/asl-header.txt"), '^(\\s*package)')
+        it.setLineEndings(Enum.valueOf(rootProject.buildscript.classLoader.loadClass("com.diffplug.spotless.LineEnding"), "UNIX"))
+        it.endWithNewline()
+        it.googleJavaFormat('1.15.0')
 
-        lineEndings 'UNIX'
-        endWithNewline()
-        googleJavaFormat('1.15.0')
+        it.custom('Refuse wildcard imports', { line ->
+          // Wildcard imports can't be resolved by spotless itself.
+          // This will require the developer themselves to adhere to best practices.
+          if (line =~ /\nimport .*\*;/) {
+            throw new AssertionError("Do not use wildcard imports.  'spotlessApply' cannot resolve this issue.")
+          }
+          line
+        })
+      }
+    }
+
+    spotless {
+      java {
+        prj.ext.spotlessJavaSetup.execute(it)
 
         // Apply to all Java sources
         target "src/**/*.java"
@@ -62,14 +76,6 @@ configure(project(":solr").subprojects) { prj ->
             target "modules/**/examples/*.java"
             break
         }
-
-        custom 'Refuse wildcard imports', {
-          // Wildcard imports can't be resolved by spotless itself.
-          // This will require the developer themselves to adhere to best practices.
-          if (it =~ /\nimport .*\*;/) {
-            throw new AssertionError("Do not use wildcard imports.  'spotlessApply' cannot resolve this issue.")
-          }
-        }
       }
     }
 
diff --git a/settings.gradle b/settings.gradle
index d6deb59e8c6..50912027467 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -26,6 +26,7 @@ rootProject.name = "solr-root"
 
 includeBuild("dev-tools/solr-missing-doclet")
 
+include "solr:api"
 include "solr:solrj"
 include "solr:solrj-zookeeper"
 include "solr:solrj-streaming"
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index de06c4b3156..cff301aab04 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -32,6 +32,11 @@ Improvements
   This is useful when using the ZkCli on a remote Solr using the embedded ZK, or Solr running in a Docker container.
   The SOLR_ZK_EMBEDDED_HOST envVar or -Dsolr.zk.embedded.host sysProp control this bind address. (Houston Putman)
 
+* SOLR-16825: Solr now offers `SolrRequest` implementations for a subset of its v2 APIs.  These implementations
+  are experimental and should be used with caution, but may be preferable to their v1 counterparts in some
+  circumstances as they are generated and more likely to remain up-to-date with future API changes.
+  (Jason Gerlowski, David Smiley, Houston Putman)
+
 Optimizations
 ---------------------
 
diff --git a/solr/api/build.gradle b/solr/api/build.gradle
new file mode 100644
index 00000000000..f61977993af
--- /dev/null
+++ b/solr/api/build.gradle
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+plugins {
+  id 'io.swagger.core.v3.swagger-gradle-plugin' version '2.2.2'
+}
+
+apply plugin: 'java-library'
+
+description = 'API - Interfaces and classes used to represent Solrs APIs'
+
+ext {
+    openApiSpecDir = "${buildDir}/generated/openapi"
+    openApiSpecFile = "${project.openApiSpecDir}/openapi.json"
+}
+
+configurations {
+    openapiSpec {
+        canBeConsumed = true
+        canBeResolved = false
+    }
+}
+
+resolve {
+  classpath = sourceSets.main.runtimeClasspath
+  outputDir = file(project.openApiSpecDir)
+  prettyPrint = true
+}
+
+dependencies {
+    runtimeOnly 'org.slf4j:slf4j-api'
+    
+    implementation 'jakarta.ws.rs:jakarta.ws.rs-api'
+    implementation 'com.fasterxml.jackson.core:jackson-annotations'
+    api 'io.swagger.core.v3:swagger-annotations'
+    implementation 'org.semver4j:semver4j'
+
+    testImplementation project(':solr:test-framework')
+    testImplementation project(':solr:api')
+    testImplementation 'org.apache.lucene:lucene-test-framework'
+}
+
+artifacts {
+    openapiSpec resolve.outputDir, {
+        builtBy resolve
+    }
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/AddReplicaPropertyApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/AddReplicaPropertyApi.java
new file mode 100644
index 00000000000..d29ee057502
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/AddReplicaPropertyApi.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.client.api.endpoint;
+
+import static org.apache.solr.client.api.util.Constants.BINARY_CONTENT_TYPE_V2;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.parameters.RequestBody;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import org.apache.solr.client.api.model.AddReplicaPropertyRequestBody;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
+
+@Path("/collections/{collName}/shards/{shardName}/replicas/{replicaName}/properties/{propName}")
+public interface AddReplicaPropertyApi {
+
+  @PUT
+  @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2})
+  @Operation(
+      summary = "Adds a property to the specified replica",
+      tags = {"replicas"})
+  public SolrJerseyResponse addReplicaProperty(
+      @Parameter(
+              description = "The name of the collection the replica belongs to.",
+              required = true)
+          @PathParam("collName")
+          String collName,
+      @Parameter(description = "The name of the shard the replica belongs to.", required = true)
+          @PathParam("shardName")
+          String shardName,
+      @Parameter(description = "The replica, e.g., `core_node1`.", required = true)
+          @PathParam("replicaName")
+          String replicaName,
+      @Parameter(description = "The name of the property to add.", required = true)
+          @PathParam("propName")
+          String propertyName,
+      @RequestBody(
+              description = "The value of the replica property to create or update",
+              required = true)
+          AddReplicaPropertyRequestBody requestBody)
+      throws Exception;
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteAliasApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteAliasApi.java
new file mode 100644
index 00000000000..24759ead2ad
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteAliasApi.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.client.api.endpoint;
+
+import static org.apache.solr.client.api.util.Constants.BINARY_CONTENT_TYPE_V2;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
+
+@Path("/aliases/{aliasName}")
+public interface DeleteAliasApi {
+
+  @DELETE
+  @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2})
+  @Operation(
+      summary = "Deletes an alias by its name",
+      tags = {"aliases"})
+  SolrJerseyResponse deleteAlias(
+      @Parameter(description = "The name of the alias to delete", required = true)
+          @PathParam("aliasName")
+          String aliasName,
+      @QueryParam("async") String asyncId)
+      throws Exception;
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteCollectionApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteCollectionApi.java
new file mode 100644
index 00000000000..9e28e7b1722
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteCollectionApi.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.client.api.endpoint;
+
+import static org.apache.solr.client.api.util.Constants.BINARY_CONTENT_TYPE_V2;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import org.apache.solr.client.api.model.SubResponseAccumulatingJerseyResponse;
+
+@Path("/collections/{collectionName}")
+public interface DeleteCollectionApi {
+
+  @DELETE
+  @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2})
+  @Operation(
+      summary = "Deletes a collection from SolrCloud",
+      tags = {"collections"})
+  SubResponseAccumulatingJerseyResponse deleteCollection(
+      @Parameter(description = "The name of the collection to be deleted.", required = true)
+          @PathParam("collectionName")
+          String collectionName,
+      @QueryParam("followAliases") Boolean followAliases,
+      @Parameter(description = "An ID to track the request asynchronously") @QueryParam("async")
+          String asyncId)
+      throws Exception;
+}
diff --git a/solr/core/src/java/org/apache/solr/jersey/AsyncJerseyResponse.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/package-info.java
similarity index 79%
copy from solr/core/src/java/org/apache/solr/jersey/AsyncJerseyResponse.java
copy to solr/api/src/java/org/apache/solr/client/api/endpoint/package-info.java
index af08d227172..377e217fda9 100644
--- a/solr/core/src/java/org/apache/solr/jersey/AsyncJerseyResponse.java
+++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/package-info.java
@@ -15,11 +15,5 @@
  * limitations under the License.
  */
 
-package org.apache.solr.jersey;
-
-import com.fasterxml.jackson.annotation.JsonProperty;
-
-public class AsyncJerseyResponse extends SolrJerseyResponse {
-  @JsonProperty("requestid")
-  public String requestId;
-}
+/** Interfaces representing individual Solr v2 APIs. */
+package org.apache.solr.client.api.endpoint;
diff --git a/solr/api/src/java/org/apache/solr/client/api/model/AddReplicaPropertyRequestBody.java b/solr/api/src/java/org/apache/solr/client/api/model/AddReplicaPropertyRequestBody.java
new file mode 100644
index 00000000000..f78c7bebd9c
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/model/AddReplicaPropertyRequestBody.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.client.api.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import org.apache.solr.client.api.util.ReflectWritable;
+
+public class AddReplicaPropertyRequestBody implements ReflectWritable {
+  public AddReplicaPropertyRequestBody() {}
+
+  public AddReplicaPropertyRequestBody(String value) {
+    this.value = value;
+  }
+
+  @Schema(description = "The value to assign to the property.", required = true)
+  @JsonProperty("value")
+  public String value;
+
+  @Schema(
+      description =
+          "If `true`, then setting this property in one replica will remove the property from all other replicas in that shard. The default is `false`.\\nThere is one pre-defined property `preferredLeader` for which `shardUnique` is forced to `true` and an error returned if `shardUnique` is explicitly set to `false`.",
+      defaultValue = "false")
+  @JsonProperty("shardUnique")
+  public Boolean shardUnique;
+}
diff --git a/solr/core/src/java/org/apache/solr/jersey/AsyncJerseyResponse.java b/solr/api/src/java/org/apache/solr/client/api/model/AsyncJerseyResponse.java
similarity index 95%
copy from solr/core/src/java/org/apache/solr/jersey/AsyncJerseyResponse.java
copy to solr/api/src/java/org/apache/solr/client/api/model/AsyncJerseyResponse.java
index af08d227172..08651598d9d 100644
--- a/solr/core/src/java/org/apache/solr/jersey/AsyncJerseyResponse.java
+++ b/solr/api/src/java/org/apache/solr/client/api/model/AsyncJerseyResponse.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.solr.jersey;
+package org.apache.solr.client.api.model;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
 
diff --git a/solr/core/src/java/org/apache/solr/jersey/ErrorInfo.java b/solr/api/src/java/org/apache/solr/client/api/model/ErrorInfo.java
similarity index 74%
rename from solr/core/src/java/org/apache/solr/jersey/ErrorInfo.java
rename to solr/api/src/java/org/apache/solr/client/api/model/ErrorInfo.java
index 24ca06d64ff..1a6ca071642 100644
--- a/solr/core/src/java/org/apache/solr/jersey/ErrorInfo.java
+++ b/solr/api/src/java/org/apache/solr/client/api/model/ErrorInfo.java
@@ -6,7 +6,7 @@
  * (the "License"); you may not use this file except in compliance with
  * the License.  You may obtain a copy of the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0
+ *     http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -15,19 +15,23 @@
  * limitations under the License.
  */
 
-package org.apache.solr.jersey;
+package org.apache.solr.client.api.model;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
 import java.util.List;
 import java.util.Map;
-import org.apache.solr.common.SolrException;
+import org.apache.solr.client.api.util.ReflectWritable;
 
 /**
  * A value type representing an error.
  *
  * <p>Based on the fields exposed in responses from Solr's v1/requestHandler API.
  */
-public class ErrorInfo implements JacksonReflectMapWriter {
+public class ErrorInfo implements ReflectWritable {
+
+  public static final String ROOT_ERROR_CLASS = "root-error-class";
+  public static final String ERROR_CLASS = "error-class";
+
   @JsonProperty("metadata")
   public ErrorMetadata metadata;
 
@@ -43,11 +47,11 @@ public class ErrorInfo implements JacksonReflectMapWriter {
   @JsonProperty("code")
   public Integer code;
 
-  public static class ErrorMetadata implements JacksonReflectMapWriter {
-    @JsonProperty(SolrException.ERROR_CLASS)
+  public static class ErrorMetadata implements ReflectWritable {
+    @JsonProperty(ERROR_CLASS)
     public String errorClass;
 
-    @JsonProperty(SolrException.ROOT_ERROR_CLASS)
+    @JsonProperty(ROOT_ERROR_CLASS)
     public String rootErrorClass;
   }
 }
diff --git a/solr/core/src/java/org/apache/solr/jersey/SolrJerseyResponse.java b/solr/api/src/java/org/apache/solr/client/api/model/SolrJerseyResponse.java
similarity index 86%
rename from solr/core/src/java/org/apache/solr/jersey/SolrJerseyResponse.java
rename to solr/api/src/java/org/apache/solr/client/api/model/SolrJerseyResponse.java
index 770fc1eec53..59e528c91f8 100644
--- a/solr/core/src/java/org/apache/solr/jersey/SolrJerseyResponse.java
+++ b/solr/api/src/java/org/apache/solr/client/api/model/SolrJerseyResponse.java
@@ -6,7 +6,7 @@
  * (the "License"); you may not use this file except in compliance with
  * the License.  You may obtain a copy of the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0
+ *     http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -15,13 +15,14 @@
  * limitations under the License.
  */
 
-package org.apache.solr.jersey;
+package org.apache.solr.client.api.model;
 
 import com.fasterxml.jackson.annotation.JsonAnyGetter;
 import com.fasterxml.jackson.annotation.JsonAnySetter;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import java.util.HashMap;
 import java.util.Map;
+import org.apache.solr.client.api.util.ReflectWritable;
 
 /**
  * Base response-body POJO to be used by Jersey resources.
@@ -29,7 +30,7 @@ import java.util.Map;
  * <p>Contains fields common to all Solr API responses, particularly the 'responseHeader' and
  * 'error' fields.
  */
-public class SolrJerseyResponse implements JacksonReflectMapWriter {
+public class SolrJerseyResponse implements ReflectWritable {
 
   @JsonProperty("responseHeader")
   public ResponseHeader responseHeader = new ResponseHeader();
@@ -37,7 +38,7 @@ public class SolrJerseyResponse implements JacksonReflectMapWriter {
   @JsonProperty("error")
   public ErrorInfo error;
 
-  public static class ResponseHeader implements JacksonReflectMapWriter {
+  public static class ResponseHeader implements ReflectWritable {
     @JsonProperty("status")
     public int status;
 
diff --git a/solr/core/src/java/org/apache/solr/jersey/SubResponseAccumulatingJerseyResponse.java b/solr/api/src/java/org/apache/solr/client/api/model/SubResponseAccumulatingJerseyResponse.java
similarity index 98%
rename from solr/core/src/java/org/apache/solr/jersey/SubResponseAccumulatingJerseyResponse.java
rename to solr/api/src/java/org/apache/solr/client/api/model/SubResponseAccumulatingJerseyResponse.java
index a0f0dbfb0e2..5031f86c3a5 100644
--- a/solr/core/src/java/org/apache/solr/jersey/SubResponseAccumulatingJerseyResponse.java
+++ b/solr/api/src/java/org/apache/solr/client/api/model/SubResponseAccumulatingJerseyResponse.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.solr.jersey;
+package org.apache.solr.client.api.model;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
 
diff --git a/solr/core/src/java/org/apache/solr/jersey/AsyncJerseyResponse.java b/solr/api/src/java/org/apache/solr/client/api/model/package-info.java
similarity index 79%
copy from solr/core/src/java/org/apache/solr/jersey/AsyncJerseyResponse.java
copy to solr/api/src/java/org/apache/solr/client/api/model/package-info.java
index af08d227172..4264373269c 100644
--- a/solr/core/src/java/org/apache/solr/jersey/AsyncJerseyResponse.java
+++ b/solr/api/src/java/org/apache/solr/client/api/model/package-info.java
@@ -15,11 +15,8 @@
  * limitations under the License.
  */
 
-package org.apache.solr.jersey;
-
-import com.fasterxml.jackson.annotation.JsonProperty;
-
-public class AsyncJerseyResponse extends SolrJerseyResponse {
-  @JsonProperty("requestid")
-  public String requestId;
-}
+/**
+ * POJOs representing various inputs and outputs of the v2 APIs described in {@link
+ * org.apache.solr.client.api.endpoint}
+ */
+package org.apache.solr.client.api.model;
diff --git a/solr/core/src/java/org/apache/solr/jersey/AsyncJerseyResponse.java b/solr/api/src/java/org/apache/solr/client/api/package-info.java
similarity index 79%
copy from solr/core/src/java/org/apache/solr/jersey/AsyncJerseyResponse.java
copy to solr/api/src/java/org/apache/solr/client/api/package-info.java
index af08d227172..aa44ba957da 100644
--- a/solr/core/src/java/org/apache/solr/jersey/AsyncJerseyResponse.java
+++ b/solr/api/src/java/org/apache/solr/client/api/package-info.java
@@ -15,11 +15,5 @@
  * limitations under the License.
  */
 
-package org.apache.solr.jersey;
-
-import com.fasterxml.jackson.annotation.JsonProperty;
-
-public class AsyncJerseyResponse extends SolrJerseyResponse {
-  @JsonProperty("requestid")
-  public String requestId;
-}
+/** Root package for interfaces and POJOs used to describe Solr's v2 APIs */
+package org.apache.solr.client.api;
diff --git a/solr/core/src/java/org/apache/solr/jersey/ExperimentalResponse.java b/solr/api/src/java/org/apache/solr/client/api/util/ApiMetadata.java
similarity index 63%
copy from solr/core/src/java/org/apache/solr/jersey/ExperimentalResponse.java
copy to solr/api/src/java/org/apache/solr/client/api/util/ApiMetadata.java
index 657e3652064..11a70a916d3 100644
--- a/solr/core/src/java/org/apache/solr/jersey/ExperimentalResponse.java
+++ b/solr/api/src/java/org/apache/solr/client/api/util/ApiMetadata.java
@@ -15,16 +15,17 @@
  * limitations under the License.
  */
 
-package org.apache.solr.jersey;
+package org.apache.solr.client.api.util;
 
-import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.OpenAPIDefinition;
+import io.swagger.v3.oas.annotations.info.Info;
+import io.swagger.v3.oas.annotations.info.License;
 
-/**
- * {@link SolrJerseyResponse} implementation with a warning field indicating that the format may
- * change
- */
-public class ExperimentalResponse extends SolrJerseyResponse {
-  @JsonProperty("WARNING")
-  public String warning =
-      "This response format is experimental.  It is likely to change in the future.";
-}
+@OpenAPIDefinition(
+    info =
+        @Info(
+            title = "v2 API",
+            description = "OpenAPI spec for Solr's v2 API endpoints",
+            license = @License(name = "ASL 2.0"),
+            version = SolrVersion.LATEST_STRING))
+public class ApiMetadata {}
diff --git a/solr/core/src/java/org/apache/solr/jersey/AsyncJerseyResponse.java b/solr/api/src/java/org/apache/solr/client/api/util/Constants.java
similarity index 77%
copy from solr/core/src/java/org/apache/solr/jersey/AsyncJerseyResponse.java
copy to solr/api/src/java/org/apache/solr/client/api/util/Constants.java
index af08d227172..1dbd6e61a6c 100644
--- a/solr/core/src/java/org/apache/solr/jersey/AsyncJerseyResponse.java
+++ b/solr/api/src/java/org/apache/solr/client/api/util/Constants.java
@@ -15,11 +15,12 @@
  * limitations under the License.
  */
 
-package org.apache.solr.jersey;
+package org.apache.solr.client.api.util;
 
-import com.fasterxml.jackson.annotation.JsonProperty;
+public class Constants {
+  private Constants() {
+    /* Private ctor prevents instantiation */
+  }
 
-public class AsyncJerseyResponse extends SolrJerseyResponse {
-  @JsonProperty("requestid")
-  public String requestId;
+  public static final String BINARY_CONTENT_TYPE_V2 = "application/vnd.apache.solr.javabin";
 }
diff --git a/solr/core/src/java/org/apache/solr/jersey/AsyncJerseyResponse.java b/solr/api/src/java/org/apache/solr/client/api/util/ReflectWritable.java
similarity index 70%
copy from solr/core/src/java/org/apache/solr/jersey/AsyncJerseyResponse.java
copy to solr/api/src/java/org/apache/solr/client/api/util/ReflectWritable.java
index af08d227172..bad94276747 100644
--- a/solr/core/src/java/org/apache/solr/jersey/AsyncJerseyResponse.java
+++ b/solr/api/src/java/org/apache/solr/client/api/util/ReflectWritable.java
@@ -15,11 +15,13 @@
  * limitations under the License.
  */
 
-package org.apache.solr.jersey;
+package org.apache.solr.client.api.util;
 
-import com.fasterxml.jackson.annotation.JsonProperty;
-
-public class AsyncJerseyResponse extends SolrJerseyResponse {
-  @JsonProperty("requestid")
-  public String requestId;
-}
+/**
+ * A marker interface used by v2 POJOs to indicate they contain annotations that can be written out
+ * via reflection.
+ *
+ * <p>Used primarily by custom serialization/deserialization codepaths that don't natively recognize
+ * (e.g.) Jackson annotations.
+ */
+public interface ReflectWritable {}
diff --git a/solr/core/src/java/org/apache/solr/util/SolrVersion.java b/solr/api/src/java/org/apache/solr/client/api/util/SolrVersion.java
similarity index 90%
rename from solr/core/src/java/org/apache/solr/util/SolrVersion.java
rename to solr/api/src/java/org/apache/solr/client/api/util/SolrVersion.java
index 1bff48df458..0a8b3e3b367 100644
--- a/solr/core/src/java/org/apache/solr/util/SolrVersion.java
+++ b/solr/api/src/java/org/apache/solr/client/api/util/SolrVersion.java
@@ -14,10 +14,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.solr.util;
+package org.apache.solr.client.api.util;
 
 import java.util.Locale;
-import org.apache.solr.common.SolrException;
 import org.semver4j.Semver;
 
 /**
@@ -45,6 +44,14 @@ public final class SolrVersion implements Comparable<SolrVersion> {
     return new SolrVersion(new Semver(String.format(Locale.ROOT, "%d.%d.%d", major, minor, patch)));
   }
 
+  /**
+   * Compares two versions v1 and v2. Returns negative if v1 isLessThan v2, positive if v1
+   * isGreaterThan v2 and 0 if equal.
+   */
+  public static int compareVersions(String v1, String v2) {
+    return new Semver(v1).compareTo(new Semver(v2));
+  }
+
   /** Return version as plain SemVer string, e.g. "9.0.1" */
   @Override
   public String toString() {
@@ -118,13 +125,4 @@ public final class SolrVersion implements Comparable<SolrVersion> {
     }
     return compareTo((SolrVersion) other) == 0;
   }
-
-  public static class InvalidSemVerExpressionException extends SolrException {
-    public InvalidSemVerExpressionException(Exception exception, String expression) {
-      super(
-          ErrorCode.BAD_REQUEST,
-          String.format(Locale.ROOT, "Invalid SemVer expression: %s", expression),
-          exception);
-    }
-  }
 }
diff --git a/solr/core/src/java/org/apache/solr/jersey/AsyncJerseyResponse.java b/solr/api/src/java/org/apache/solr/client/api/util/package-info.java
similarity index 79%
rename from solr/core/src/java/org/apache/solr/jersey/AsyncJerseyResponse.java
rename to solr/api/src/java/org/apache/solr/client/api/util/package-info.java
index af08d227172..c7634ce945c 100644
--- a/solr/core/src/java/org/apache/solr/jersey/AsyncJerseyResponse.java
+++ b/solr/api/src/java/org/apache/solr/client/api/util/package-info.java
@@ -15,11 +15,5 @@
  * limitations under the License.
  */
 
-package org.apache.solr.jersey;
-
-import com.fasterxml.jackson.annotation.JsonProperty;
-
-public class AsyncJerseyResponse extends SolrJerseyResponse {
-  @JsonProperty("requestid")
-  public String requestId;
-}
+/** Utilities for working with Solr api interfaces and model classes. */
+package org.apache.solr.client.api.util;
diff --git a/solr/core/src/test/org/apache/solr/util/TestSolrVersion.java b/solr/api/src/test/org/apache/solr/util/TestSolrVersion.java
similarity index 97%
rename from solr/core/src/test/org/apache/solr/util/TestSolrVersion.java
rename to solr/api/src/test/org/apache/solr/util/TestSolrVersion.java
index 1a43e7cab71..ff55514e4ab 100644
--- a/solr/core/src/test/org/apache/solr/util/TestSolrVersion.java
+++ b/solr/api/src/test/org/apache/solr/util/TestSolrVersion.java
@@ -17,6 +17,7 @@
 package org.apache.solr.util;
 
 import org.apache.solr.SolrTestCase;
+import org.apache.solr.client.api.util.SolrVersion;
 import org.semver4j.SemverException;
 
 public class TestSolrVersion extends SolrTestCase {
diff --git a/solr/core/build.gradle b/solr/core/build.gradle
index 83f48e2f223..d868164cd75 100644
--- a/solr/core/build.gradle
+++ b/solr/core/build.gradle
@@ -15,20 +15,10 @@
  * limitations under the License.
  */
 
-plugins {
-  id 'io.swagger.core.v3.swagger-gradle-plugin' version '2.2.2'
-}
-
 apply plugin: 'java-library'
 
 description = 'Apache Solr Core'
 
-resolve {
-  classpath = sourceSets.main.runtimeClasspath
-  outputDir = file("build/generated/openapi")
-  prettyPrint = true
-}
-
 dependencies {
   // Spotbugs Annotations are only needed for old findbugs
   // annotation usage like in Zookeeper during compilation time.
@@ -54,6 +44,7 @@ dependencies {
   // We export logging api with dependencies, which is useful for all modules
   api 'org.slf4j:slf4j-api'
 
+  api project(':solr:api')
   api project(':solr:solrj')
   api project(':solr:solrj-zookeeper')
   api project(':solr:solrj-streaming')
@@ -142,9 +133,6 @@ dependencies {
   // required for instantiating a Zookeeper server (for embedding ZK or running tests)
   runtimeOnly ('org.xerial.snappy:snappy-java')
 
-  // For Package Manager
-  implementation 'org.semver4j:semver4j'
-
   implementation('com.jayway.jsonpath:json-path', {
     exclude group: "net.minidev", module: "json-smart"
   })
diff --git a/solr/core/src/java/org/apache/solr/api/JerseyResource.java b/solr/core/src/java/org/apache/solr/api/JerseyResource.java
index e96216e170e..6a304654e27 100644
--- a/solr/core/src/java/org/apache/solr/api/JerseyResource.java
+++ b/solr/core/src/java/org/apache/solr/api/JerseyResource.java
@@ -22,9 +22,9 @@ import static org.apache.solr.jersey.RequestContextKeys.SOLR_JERSEY_RESPONSE;
 import java.util.function.Supplier;
 import javax.ws.rs.container.ContainerRequestContext;
 import javax.ws.rs.core.Context;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.jersey.CatchAllExceptionMapper;
-import org.apache.solr.jersey.SolrJerseyResponse;
 import org.apache.solr.servlet.HttpSolrCall;
 
 /**
diff --git a/solr/core/src/java/org/apache/solr/cli/SimplePostTool.java b/solr/core/src/java/org/apache/solr/cli/SimplePostTool.java
index 11478366711..d15650e1636 100644
--- a/solr/core/src/java/org/apache/solr/cli/SimplePostTool.java
+++ b/solr/core/src/java/org/apache/solr/cli/SimplePostTool.java
@@ -65,9 +65,9 @@ import javax.xml.xpath.XPathConstants;
 import javax.xml.xpath.XPathExpression;
 import javax.xml.xpath.XPathExpressionException;
 import javax.xml.xpath.XPathFactory;
+import org.apache.solr.client.api.util.SolrVersion;
 import org.apache.solr.common.util.Utils;
 import org.apache.solr.util.RTimer;
-import org.apache.solr.util.SolrVersion;
 import org.w3c.dom.Document;
 import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
diff --git a/solr/core/src/java/org/apache/solr/cli/VersionTool.java b/solr/core/src/java/org/apache/solr/cli/VersionTool.java
index 1b0e1fa4644..f5e84e6c1f8 100644
--- a/solr/core/src/java/org/apache/solr/cli/VersionTool.java
+++ b/solr/core/src/java/org/apache/solr/cli/VersionTool.java
@@ -21,7 +21,7 @@ import java.util.Collections;
 import java.util.List;
 import org.apache.commons.cli.CommandLine;
 import org.apache.commons.cli.Option;
-import org.apache.solr.util.SolrVersion;
+import org.apache.solr.client.api.util.SolrVersion;
 
 public class VersionTool extends ToolBase {
 
diff --git a/solr/core/src/java/org/apache/solr/handler/IncrementalShardBackup.java b/solr/core/src/java/org/apache/solr/handler/IncrementalShardBackup.java
index 74128375ad0..c4de4807588 100644
--- a/solr/core/src/java/org/apache/solr/handler/IncrementalShardBackup.java
+++ b/solr/core/src/java/org/apache/solr/handler/IncrementalShardBackup.java
@@ -29,6 +29,7 @@ import java.util.UUID;
 import org.apache.commons.math3.util.Precision;
 import org.apache.lucene.index.IndexCommit;
 import org.apache.lucene.store.Directory;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.cloud.CloudDescriptor;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.core.DirectoryFactory;
@@ -39,7 +40,6 @@ import org.apache.solr.core.backup.Checksum;
 import org.apache.solr.core.backup.ShardBackupId;
 import org.apache.solr.core.backup.ShardBackupMetadata;
 import org.apache.solr.core.backup.repository.BackupRepository;
-import org.apache.solr.jersey.SolrJerseyResponse;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
diff --git a/solr/core/src/java/org/apache/solr/handler/ReplicationHandler.java b/solr/core/src/java/org/apache/solr/handler/ReplicationHandler.java
index f49ef231f9e..9f6d09bab22 100644
--- a/solr/core/src/java/org/apache/solr/handler/ReplicationHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/ReplicationHandler.java
@@ -66,6 +66,7 @@ import org.apache.lucene.store.IOContext;
 import org.apache.lucene.store.IndexInput;
 import org.apache.lucene.store.RateLimiter;
 import org.apache.solr.api.JerseyResource;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.SolrException.ErrorCode;
 import org.apache.solr.common.params.CommonParams;
@@ -91,7 +92,6 @@ import org.apache.solr.core.backup.repository.LocalFileSystemRepository;
 import org.apache.solr.handler.IndexFetcher.IndexFetchResult;
 import org.apache.solr.handler.admin.api.CoreReplicationAPI;
 import org.apache.solr.handler.api.V2ApiUtils;
-import org.apache.solr.jersey.SolrJerseyResponse;
 import org.apache.solr.metrics.MetricsMap;
 import org.apache.solr.metrics.SolrMetricsContext;
 import org.apache.solr.request.SolrQueryRequest;
diff --git a/solr/core/src/java/org/apache/solr/handler/SnapShooter.java b/solr/core/src/java/org/apache/solr/handler/SnapShooter.java
index 184d2aeb0c2..4f2beb51983 100644
--- a/solr/core/src/java/org/apache/solr/handler/SnapShooter.java
+++ b/solr/core/src/java/org/apache/solr/handler/SnapShooter.java
@@ -35,6 +35,7 @@ import java.util.Optional;
 import java.util.function.Consumer;
 import org.apache.lucene.index.IndexCommit;
 import org.apache.lucene.store.Directory;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.SolrException.ErrorCode;
 import org.apache.solr.common.util.NamedList;
@@ -47,7 +48,6 @@ import org.apache.solr.core.backup.repository.BackupRepository.PathType;
 import org.apache.solr.core.backup.repository.LocalFileSystemRepository;
 import org.apache.solr.core.snapshots.SolrSnapshotMetaDataManager;
 import org.apache.solr.handler.api.V2ApiUtils;
-import org.apache.solr.jersey.SolrJerseyResponse;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/BackupCoreOp.java b/solr/core/src/java/org/apache/solr/handler/admin/BackupCoreOp.java
index 2f5407416c4..bfd939c43b4 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/BackupCoreOp.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/BackupCoreOp.java
@@ -25,7 +25,6 @@ import org.apache.solr.common.util.SimpleOrderedMap;
 import org.apache.solr.core.backup.ShardBackupId;
 import org.apache.solr.handler.admin.api.BackupCoreAPI;
 import org.apache.solr.handler.api.V2ApiUtils;
-import org.apache.solr.jersey.SolrJerseyResponse;
 
 class BackupCoreOp implements CoreAdminHandler.CoreAdminOp {
 
@@ -53,7 +52,7 @@ class BackupCoreOp implements CoreAdminHandler.CoreAdminOp {
         new BackupCoreAPI(
             it.handler.coreContainer, it.req, it.rsp, it.handler.coreAdminAsyncTracker);
     try {
-      SolrJerseyResponse response = backupCoreAPI.createBackup(cname, backupCoreRequestBody);
+      final var response = backupCoreAPI.createBackup(cname, backupCoreRequestBody);
       NamedList<Object> namedList = new SimpleOrderedMap<>();
       V2ApiUtils.squashIntoNamedListWithoutHeader(namedList, response);
       it.rsp.addResponse(namedList);
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java
index 23a0d094ed6..4b63e0c5138 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java
@@ -122,6 +122,8 @@ import java.util.concurrent.TimeoutException;
 import org.apache.solr.api.AnnotatedApi;
 import org.apache.solr.api.Api;
 import org.apache.solr.api.JerseyResource;
+import org.apache.solr.client.api.model.AddReplicaPropertyRequestBody;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.client.solrj.SolrResponse;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
 import org.apache.solr.client.solrj.response.RequestStatusState;
@@ -160,7 +162,7 @@ import org.apache.solr.core.CoreContainer;
 import org.apache.solr.core.snapshots.CollectionSnapshotMetaData;
 import org.apache.solr.core.snapshots.SolrSnapshotManager;
 import org.apache.solr.handler.RequestHandlerBase;
-import org.apache.solr.handler.admin.api.AddReplicaPropertyAPI;
+import org.apache.solr.handler.admin.api.AddReplicaProperty;
 import org.apache.solr.handler.admin.api.AdminAPIBase;
 import org.apache.solr.handler.admin.api.AliasPropertyAPI;
 import org.apache.solr.handler.admin.api.BalanceReplicasAPI;
@@ -173,8 +175,8 @@ import org.apache.solr.handler.admin.api.CreateCollectionBackupAPI;
 import org.apache.solr.handler.admin.api.CreateCollectionSnapshotAPI;
 import org.apache.solr.handler.admin.api.CreateReplicaAPI;
 import org.apache.solr.handler.admin.api.CreateShardAPI;
-import org.apache.solr.handler.admin.api.DeleteAliasAPI;
-import org.apache.solr.handler.admin.api.DeleteCollectionAPI;
+import org.apache.solr.handler.admin.api.DeleteAlias;
+import org.apache.solr.handler.admin.api.DeleteCollection;
 import org.apache.solr.handler.admin.api.DeleteCollectionBackupAPI;
 import org.apache.solr.handler.admin.api.DeleteCollectionSnapshotAPI;
 import org.apache.solr.handler.admin.api.DeleteNodeAPI;
@@ -199,7 +201,6 @@ import org.apache.solr.handler.admin.api.RestoreCollectionAPI;
 import org.apache.solr.handler.admin.api.SplitShardAPI;
 import org.apache.solr.handler.admin.api.SyncShardAPI;
 import org.apache.solr.handler.api.V2ApiUtils;
-import org.apache.solr.jersey.SolrJerseyResponse;
 import org.apache.solr.logging.MDCLoggingContext;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
@@ -543,8 +544,8 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
         DELETE,
         (req, rsp, h) -> {
           final RequiredSolrParams requiredParams = req.getParams().required();
-          final DeleteCollectionAPI deleteCollectionAPI =
-              new DeleteCollectionAPI(h.coreContainer, req, rsp);
+          final DeleteCollection deleteCollectionAPI =
+              new DeleteCollection(h.coreContainer, req, rsp);
           final SolrJerseyResponse deleteCollResponse =
               deleteCollectionAPI.deleteCollection(
                   requiredParams.get(NAME),
@@ -623,7 +624,7 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
     DELETEALIAS_OP(
         DELETEALIAS,
         (req, rsp, h) -> {
-          final DeleteAliasAPI deleteAliasAPI = new DeleteAliasAPI(h.coreContainer, req, rsp);
+          final DeleteAlias deleteAliasAPI = new DeleteAlias(h.coreContainer, req, rsp);
           final SolrJerseyResponse response =
               deleteAliasAPI.deleteAlias(
                   req.getParams().required().get(NAME), req.getParams().get(ASYNC));
@@ -988,8 +989,7 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
         ADDREPLICAPROP,
         (req, rsp, h) -> {
           final RequiredSolrParams requiredParams = req.getParams().required();
-          final AddReplicaPropertyAPI.AddReplicaPropertyRequestBody requestBody =
-              new AddReplicaPropertyAPI.AddReplicaPropertyRequestBody();
+          final var requestBody = new AddReplicaPropertyRequestBody();
           requestBody.value = requiredParams.get(PROPERTY_VALUE_PROP);
           requestBody.shardUnique = req.getParams().getBool(SHARD_UNIQUE);
           final String propName = requiredParams.get(PROPERTY_PROP);
@@ -998,8 +998,8 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
                   ? propName.substring(PROPERTY_PREFIX.length())
                   : propName;
 
-          final AddReplicaPropertyAPI addReplicaPropertyAPI =
-              new AddReplicaPropertyAPI(h.coreContainer, req, rsp);
+          final AddReplicaProperty addReplicaPropertyAPI =
+              new AddReplicaProperty(h.coreContainer, req, rsp);
           final SolrJerseyResponse addReplicaPropResponse =
               addReplicaPropertyAPI.addReplicaProperty(
                   requiredParams.get(COLLECTION_PROP),
@@ -1364,15 +1364,15 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
   public Collection<Class<? extends JerseyResource>> getJerseyResources() {
     return List.of(
         CreateReplicaAPI.class,
-        AddReplicaPropertyAPI.class,
+        AddReplicaProperty.class,
         BalanceShardUniqueAPI.class,
         CreateAliasAPI.class,
         CreateCollectionAPI.class,
         CreateCollectionBackupAPI.class,
         CreateShardAPI.class,
-        DeleteAliasAPI.class,
+        DeleteAlias.class,
         DeleteCollectionBackupAPI.class,
-        DeleteCollectionAPI.class,
+        DeleteCollection.class,
         DeleteReplicaAPI.class,
         DeleteReplicaPropertyAPI.class,
         DeleteShardAPI.class,
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperReadAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperReadAPI.java
index a56c5b7deed..c2f244e93d8 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperReadAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperReadAPI.java
@@ -36,6 +36,7 @@ import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
 import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.MediaType;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.client.solrj.impl.BinaryResponseParser;
 import org.apache.solr.client.solrj.impl.XMLResponseParser;
 import org.apache.solr.common.SolrException;
@@ -47,7 +48,6 @@ import org.apache.solr.handler.admin.api.AdminAPIBase;
 import org.apache.solr.jersey.ExperimentalResponse;
 import org.apache.solr.jersey.JacksonReflectMapWriter;
 import org.apache.solr.jersey.PermissionName;
-import org.apache.solr.jersey.SolrJerseyResponse;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.RawResponseWriter;
 import org.apache.solr.response.SolrQueryResponse;
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/AddReplicaPropertyAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/AddReplicaProperty.java
similarity index 68%
rename from solr/core/src/java/org/apache/solr/handler/admin/api/AddReplicaPropertyAPI.java
rename to solr/core/src/java/org/apache/solr/handler/admin/api/AddReplicaProperty.java
index 30936638ce5..c3f29f90b00 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/AddReplicaPropertyAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/AddReplicaProperty.java
@@ -16,7 +16,6 @@
  */
 package org.apache.solr.handler.admin.api;
 
-import static org.apache.solr.client.solrj.impl.BinaryResponseParser.BINARY_CONTENT_TYPE_V2;
 import static org.apache.solr.cloud.Overseer.QUEUE_OPERATION;
 import static org.apache.solr.cloud.api.collections.CollectionHandlingUtils.SHARD_UNIQUE;
 import static org.apache.solr.common.cloud.ZkStateReader.COLLECTION_PROP;
@@ -28,18 +27,13 @@ import static org.apache.solr.common.params.CollectionAdminParams.PROPERTY_PREFI
 import static org.apache.solr.handler.admin.CollectionsHandler.DEFAULT_COLLECTION_OP_TIMEOUT;
 import static org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM;
 
-import com.fasterxml.jackson.annotation.JsonProperty;
-import io.swagger.v3.oas.annotations.Parameter;
-import io.swagger.v3.oas.annotations.media.Schema;
-import io.swagger.v3.oas.annotations.parameters.RequestBody;
 import java.util.HashMap;
 import java.util.Locale;
 import java.util.Map;
 import javax.inject.Inject;
-import javax.ws.rs.PUT;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
-import javax.ws.rs.Produces;
+import org.apache.solr.client.api.endpoint.AddReplicaPropertyApi;
+import org.apache.solr.client.api.model.AddReplicaPropertyRequestBody;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.client.solrj.SolrResponse;
 import org.apache.solr.cloud.overseer.SliceMutator;
 import org.apache.solr.common.SolrException;
@@ -47,9 +41,7 @@ import org.apache.solr.common.cloud.ZkNodeProps;
 import org.apache.solr.common.params.CollectionParams;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.handler.admin.CollectionsHandler;
-import org.apache.solr.jersey.JacksonReflectMapWriter;
 import org.apache.solr.jersey.PermissionName;
-import org.apache.solr.jersey.SolrJerseyResponse;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 
@@ -58,39 +50,24 @@ import org.apache.solr.response.SolrQueryResponse;
  *
  * <p>This API is analogous to the v1 /admin/collections?action=ADDREPLICAPROP command.
  */
-@Path("/collections/{collName}/shards/{shardName}/replicas/{replicaName}/properties/{propName}")
-public class AddReplicaPropertyAPI extends AdminAPIBase {
+public class AddReplicaProperty extends AdminAPIBase implements AddReplicaPropertyApi {
 
   @Inject
-  public AddReplicaPropertyAPI(
+  public AddReplicaProperty(
       CoreContainer coreContainer,
       SolrQueryRequest solrQueryRequest,
       SolrQueryResponse solrQueryResponse) {
     super(coreContainer, solrQueryRequest, solrQueryResponse);
   }
 
-  @PUT
-  @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2})
+  @Override
   @PermissionName(COLL_EDIT_PERM)
   public SolrJerseyResponse addReplicaProperty(
-      @Parameter(
-              description = "The name of the collection the replica belongs to.",
-              required = true)
-          @PathParam("collName")
-          String collName,
-      @Parameter(description = "The name of the shard the replica belongs to.", required = true)
-          @PathParam("shardName")
-          String shardName,
-      @Parameter(description = "The replica, e.g., `core_node1`.", required = true)
-          @PathParam("replicaName")
-          String replicaName,
-      @Parameter(description = "The name of the property to add.", required = true)
-          @PathParam("propName")
-          String propertyName,
-      @RequestBody(
-              description = "The value of the replica property to create or update",
-              required = true)
-          AddReplicaPropertyRequestBody requestBody)
+      String collName,
+      String shardName,
+      String replicaName,
+      String propertyName,
+      AddReplicaPropertyRequestBody requestBody)
       throws Exception {
     if (requestBody == null) {
       throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Missing required request body");
@@ -153,24 +130,4 @@ public class AddReplicaPropertyAPI extends AdminAPIBase {
 
     return new ZkNodeProps(remoteMessage);
   }
-
-  public static class AddReplicaPropertyRequestBody implements JacksonReflectMapWriter {
-
-    public AddReplicaPropertyRequestBody() {}
-
-    public AddReplicaPropertyRequestBody(String value) {
-      this.value = value;
-    }
-
-    @Schema(description = "The value to assign to the property.", required = true)
-    @JsonProperty("value")
-    public String value;
-
-    @Schema(
-        description =
-            "If `true`, then setting this property in one replica will remove the property from all other replicas in that shard. The default is `false`.\\nThere is one pre-defined property `preferredLeader` for which `shardUnique` is forced to `true` and an error returned if `shardUnique` is explicitly set to `false`.",
-        defaultValue = "false")
-    @JsonProperty("shardUnique")
-    public Boolean shardUnique;
-  }
 }
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/AdminAPIBase.java b/solr/core/src/java/org/apache/solr/handler/admin/api/AdminAPIBase.java
index 17890115d2b..cfb7df038e3 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/AdminAPIBase.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/AdminAPIBase.java
@@ -21,6 +21,7 @@ import static org.apache.solr.handler.admin.CollectionsHandler.DEFAULT_COLLECTIO
 
 import java.util.Map;
 import org.apache.solr.api.JerseyResource;
+import org.apache.solr.client.api.model.SubResponseAccumulatingJerseyResponse;
 import org.apache.solr.client.solrj.SolrResponse;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.cloud.ClusterState;
@@ -28,7 +29,6 @@ import org.apache.solr.common.cloud.ZkNodeProps;
 import org.apache.solr.common.params.CollectionParams;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.handler.admin.CollectionsHandler;
-import org.apache.solr.jersey.SubResponseAccumulatingJerseyResponse;
 import org.apache.solr.logging.MDCLoggingContext;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/AliasPropertyAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/AliasPropertyAPI.java
index 02d2f7024c5..baeb6ddc7ac 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/AliasPropertyAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/AliasPropertyAPI.java
@@ -39,6 +39,7 @@ import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
 import javax.ws.rs.core.MediaType;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.client.solrj.SolrResponse;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.cloud.Aliases;
@@ -49,7 +50,6 @@ import org.apache.solr.core.CoreContainer;
 import org.apache.solr.handler.admin.CollectionsHandler;
 import org.apache.solr.jersey.JacksonReflectMapWriter;
 import org.apache.solr.jersey.PermissionName;
-import org.apache.solr.jersey.SolrJerseyResponse;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/BackupCoreAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/BackupCoreAPI.java
index 833149cb07b..8f57c58f2d9 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/BackupCoreAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/BackupCoreAPI.java
@@ -31,6 +31,7 @@ import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.core.SolrCore;
@@ -41,7 +42,6 @@ import org.apache.solr.handler.IncrementalShardBackup;
 import org.apache.solr.handler.SnapShooter;
 import org.apache.solr.handler.admin.CoreAdminHandler;
 import org.apache.solr.jersey.PermissionName;
-import org.apache.solr.jersey.SolrJerseyResponse;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/BalanceReplicasAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/BalanceReplicasAPI.java
index 0a92fcea962..53e23886585 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/BalanceReplicasAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/BalanceReplicasAPI.java
@@ -35,6 +35,7 @@ import javax.inject.Inject;
 import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.Produces;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.client.solrj.SolrResponse;
 import org.apache.solr.common.cloud.ZkNodeProps;
 import org.apache.solr.common.params.CollectionParams.CollectionAction;
@@ -42,7 +43,6 @@ import org.apache.solr.core.CoreContainer;
 import org.apache.solr.handler.admin.CollectionsHandler;
 import org.apache.solr.jersey.JacksonReflectMapWriter;
 import org.apache.solr.jersey.PermissionName;
-import org.apache.solr.jersey.SolrJerseyResponse;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/BalanceShardUniqueAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/BalanceShardUniqueAPI.java
index 64f31ac2e59..62324f208c3 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/BalanceShardUniqueAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/BalanceShardUniqueAPI.java
@@ -34,6 +34,7 @@ import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
+import org.apache.solr.client.api.model.SubResponseAccumulatingJerseyResponse;
 import org.apache.solr.cloud.overseer.SliceMutator;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.annotation.JsonProperty;
@@ -44,7 +45,6 @@ import org.apache.solr.core.CoreContainer;
 import org.apache.solr.handler.api.V2ApiUtils;
 import org.apache.solr.jersey.JacksonReflectMapWriter;
 import org.apache.solr.jersey.PermissionName;
-import org.apache.solr.jersey.SubResponseAccumulatingJerseyResponse;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/CollectionPropertyAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/CollectionPropertyAPI.java
index 5302571dcad..a50e2895d52 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/CollectionPropertyAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/CollectionPropertyAPI.java
@@ -28,12 +28,12 @@ import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
 import javax.ws.rs.core.MediaType;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.cloud.CollectionProperties;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.jersey.JacksonReflectMapWriter;
 import org.apache.solr.jersey.PermissionName;
-import org.apache.solr.jersey.SolrJerseyResponse;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/CoreAdminAPIBase.java b/solr/core/src/java/org/apache/solr/handler/admin/api/CoreAdminAPIBase.java
index a5d9431cc7f..6a4130e9132 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/CoreAdminAPIBase.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/CoreAdminAPIBase.java
@@ -18,11 +18,11 @@ package org.apache.solr.handler.admin.api;
 
 import java.util.function.Supplier;
 import org.apache.solr.api.JerseyResource;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.handler.admin.CoreAdminHandler;
 import org.apache.solr.handler.api.V2ApiUtils;
-import org.apache.solr.jersey.SolrJerseyResponse;
 import org.apache.solr.logging.MDCLoggingContext;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/CoreReplicationAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/CoreReplicationAPI.java
index f2c4ac60cd6..9049a4adc7e 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/CoreReplicationAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/CoreReplicationAPI.java
@@ -29,10 +29,10 @@ import javax.ws.rs.Path;
 import javax.ws.rs.Produces;
 import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.MediaType;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.jersey.JacksonReflectMapWriter;
 import org.apache.solr.jersey.PermissionName;
-import org.apache.solr.jersey.SolrJerseyResponse;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/CoreSnapshotAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/CoreSnapshotAPI.java
index b5e028b8cf7..634c1f105b9 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/CoreSnapshotAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/CoreSnapshotAPI.java
@@ -37,6 +37,7 @@ import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
 import javax.ws.rs.QueryParam;
 import org.apache.lucene.index.IndexCommit;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.params.CoreAdminParams;
 import org.apache.solr.core.CoreContainer;
@@ -47,7 +48,6 @@ import org.apache.solr.core.snapshots.SolrSnapshotMetaDataManager;
 import org.apache.solr.handler.admin.CoreAdminHandler;
 import org.apache.solr.jersey.JacksonReflectMapWriter;
 import org.apache.solr.jersey.PermissionName;
-import org.apache.solr.jersey.SolrJerseyResponse;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/CreateAliasAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/CreateAliasAPI.java
index 95f191c485a..ad3f300b7c9 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/CreateAliasAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/CreateAliasAPI.java
@@ -47,6 +47,8 @@ import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.Produces;
 import javax.ws.rs.core.MediaType;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
+import org.apache.solr.client.api.model.SubResponseAccumulatingJerseyResponse;
 import org.apache.solr.client.solrj.RoutedAliasTypes;
 import org.apache.solr.client.solrj.SolrResponse;
 import org.apache.solr.client.solrj.util.SolrIdentifierValidator;
@@ -65,8 +67,6 @@ import org.apache.solr.core.CoreContainer;
 import org.apache.solr.handler.admin.CollectionsHandler;
 import org.apache.solr.jersey.JacksonReflectMapWriter;
 import org.apache.solr.jersey.PermissionName;
-import org.apache.solr.jersey.SolrJerseyResponse;
-import org.apache.solr.jersey.SubResponseAccumulatingJerseyResponse;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 import org.apache.solr.util.TimeZoneUtils;
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionAPI.java
index 52555417645..b8ae2dca46f 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionAPI.java
@@ -61,6 +61,7 @@ import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.Produces;
 import javax.ws.rs.core.MediaType;
+import org.apache.solr.client.api.model.SubResponseAccumulatingJerseyResponse;
 import org.apache.solr.client.solrj.SolrResponse;
 import org.apache.solr.client.solrj.request.beans.V2ApiConstants;
 import org.apache.solr.client.solrj.util.SolrIdentifierValidator;
@@ -79,7 +80,6 @@ import org.apache.solr.core.CoreContainer;
 import org.apache.solr.handler.admin.CollectionsHandler;
 import org.apache.solr.jersey.JacksonReflectMapWriter;
 import org.apache.solr.jersey.PermissionName;
-import org.apache.solr.jersey.SubResponseAccumulatingJerseyResponse;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 import org.apache.zookeeper.CreateMode;
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionBackupAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionBackupAPI.java
index 4a5f1991fde..5dd9fd96e09 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionBackupAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionBackupAPI.java
@@ -43,6 +43,8 @@ import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
+import org.apache.solr.client.api.model.SubResponseAccumulatingJerseyResponse;
 import org.apache.solr.client.solrj.SolrResponse;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.cloud.ZkNodeProps;
@@ -54,8 +56,6 @@ import org.apache.solr.handler.admin.CollectionsHandler;
 import org.apache.solr.jersey.JacksonReflectMapWriter;
 import org.apache.solr.jersey.PermissionName;
 import org.apache.solr.jersey.SolrJacksonMapper;
-import org.apache.solr.jersey.SolrJerseyResponse;
-import org.apache.solr.jersey.SubResponseAccumulatingJerseyResponse;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 import org.apache.zookeeper.common.StringUtils;
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionSnapshotAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionSnapshotAPI.java
index b331af1aa7c..e619ccf442b 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionSnapshotAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionSnapshotAPI.java
@@ -35,6 +35,7 @@ import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
+import org.apache.solr.client.api.model.AsyncJerseyResponse;
 import org.apache.solr.client.solrj.SolrResponse;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.cloud.SolrZkClient;
@@ -44,7 +45,6 @@ import org.apache.solr.common.params.CoreAdminParams;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.core.snapshots.SolrSnapshotManager;
 import org.apache.solr.handler.admin.CollectionsHandler;
-import org.apache.solr.jersey.AsyncJerseyResponse;
 import org.apache.solr.jersey.JacksonReflectMapWriter;
 import org.apache.solr.jersey.PermissionName;
 import org.apache.solr.request.SolrQueryRequest;
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/CreateReplicaAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/CreateReplicaAPI.java
index 76043bff05c..c119f27db60 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/CreateReplicaAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/CreateReplicaAPI.java
@@ -51,6 +51,7 @@ import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
+import org.apache.solr.client.api.model.SubResponseAccumulatingJerseyResponse;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.cloud.ZkNodeProps;
 import org.apache.solr.common.params.CollectionParams;
@@ -60,7 +61,6 @@ import org.apache.solr.common.util.CollectionUtil;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.jersey.JacksonReflectMapWriter;
 import org.apache.solr.jersey.PermissionName;
-import org.apache.solr.jersey.SubResponseAccumulatingJerseyResponse;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/CreateShardAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/CreateShardAPI.java
index 3f39404f45a..41be6e672d7 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/CreateShardAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/CreateShardAPI.java
@@ -47,6 +47,7 @@ import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
+import org.apache.solr.client.api.model.SubResponseAccumulatingJerseyResponse;
 import org.apache.solr.client.solrj.util.SolrIdentifierValidator;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.cloud.ClusterState;
@@ -59,7 +60,6 @@ import org.apache.solr.core.CoreContainer;
 import org.apache.solr.handler.api.V2ApiUtils;
 import org.apache.solr.jersey.JacksonReflectMapWriter;
 import org.apache.solr.jersey.PermissionName;
-import org.apache.solr.jersey.SubResponseAccumulatingJerseyResponse;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteAliasAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteAlias.java
similarity index 87%
rename from solr/core/src/java/org/apache/solr/handler/admin/api/DeleteAliasAPI.java
rename to solr/core/src/java/org/apache/solr/handler/admin/api/DeleteAlias.java
index 4b9a4480507..2c577453c93 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteAliasAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteAlias.java
@@ -17,7 +17,6 @@
 
 package org.apache.solr.handler.admin.api;
 
-import static org.apache.solr.client.solrj.impl.BinaryResponseParser.BINARY_CONTENT_TYPE_V2;
 import static org.apache.solr.cloud.Overseer.QUEUE_OPERATION;
 import static org.apache.solr.common.params.CommonAdminParams.ASYNC;
 import static org.apache.solr.common.params.CommonParams.NAME;
@@ -27,34 +26,30 @@ import static org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PER
 import java.util.HashMap;
 import java.util.Map;
 import javax.inject.Inject;
-import javax.ws.rs.DELETE;
-import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
-import javax.ws.rs.Produces;
 import javax.ws.rs.QueryParam;
+import org.apache.solr.client.api.endpoint.DeleteAliasApi;
+import org.apache.solr.client.api.model.AsyncJerseyResponse;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.client.solrj.SolrResponse;
 import org.apache.solr.common.cloud.ZkNodeProps;
 import org.apache.solr.common.params.CollectionParams;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.handler.admin.CollectionsHandler;
-import org.apache.solr.jersey.AsyncJerseyResponse;
 import org.apache.solr.jersey.PermissionName;
-import org.apache.solr.jersey.SolrJerseyResponse;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 
-@Path("/aliases/{aliasName}")
-public class DeleteAliasAPI extends AdminAPIBase {
+public class DeleteAlias extends AdminAPIBase implements DeleteAliasApi {
   @Inject
-  public DeleteAliasAPI(
+  public DeleteAlias(
       CoreContainer coreContainer,
       SolrQueryRequest solrQueryRequest,
       SolrQueryResponse solrQueryResponse) {
     super(coreContainer, solrQueryRequest, solrQueryResponse);
   }
 
-  @DELETE
-  @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2})
+  @Override
   @PermissionName(COLL_EDIT_PERM)
   public SolrJerseyResponse deleteAlias(
       @PathParam("aliasName") String aliasName, @QueryParam("async") String asyncId)
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollection.java
similarity index 84%
rename from solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionAPI.java
rename to solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollection.java
index 2c38da38a87..812f7255ad2 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollection.java
@@ -16,7 +16,6 @@
  */
 package org.apache.solr.handler.admin.api;
 
-import static org.apache.solr.client.solrj.impl.BinaryResponseParser.BINARY_CONTENT_TYPE_V2;
 import static org.apache.solr.cloud.Overseer.QUEUE_OPERATION;
 import static org.apache.solr.common.params.CollectionAdminParams.FOLLOW_ALIASES;
 import static org.apache.solr.common.params.CommonAdminParams.ASYNC;
@@ -27,18 +26,14 @@ import static org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PER
 import java.util.HashMap;
 import java.util.Map;
 import javax.inject.Inject;
-import javax.ws.rs.DELETE;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
-import javax.ws.rs.Produces;
-import javax.ws.rs.QueryParam;
+import org.apache.solr.client.api.endpoint.DeleteCollectionApi;
+import org.apache.solr.client.api.model.SubResponseAccumulatingJerseyResponse;
 import org.apache.solr.client.solrj.SolrResponse;
 import org.apache.solr.common.cloud.ZkNodeProps;
 import org.apache.solr.common.params.CollectionParams;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.handler.admin.CollectionsHandler;
 import org.apache.solr.jersey.PermissionName;
-import org.apache.solr.jersey.SubResponseAccumulatingJerseyResponse;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 
@@ -48,26 +43,20 @@ import org.apache.solr.response.SolrQueryResponse;
  * <p>This API (DELETE /v2/collections/collectionName) is equivalent to the v1
  * /admin/collections?action=DELETE command.
  */
-@Path("collections/")
-public class DeleteCollectionAPI extends AdminAPIBase {
+public class DeleteCollection extends AdminAPIBase implements DeleteCollectionApi {
 
   @Inject
-  public DeleteCollectionAPI(
+  public DeleteCollection(
       CoreContainer coreContainer,
       SolrQueryRequest solrQueryRequest,
       SolrQueryResponse solrQueryResponse) {
     super(coreContainer, solrQueryRequest, solrQueryResponse);
   }
 
-  @DELETE
-  @Path("{collectionName}")
-  @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2})
+  @Override
   @PermissionName(COLL_EDIT_PERM)
   public SubResponseAccumulatingJerseyResponse deleteCollection(
-      @PathParam("collectionName") String collectionName,
-      @QueryParam("followAliases") Boolean followAliases,
-      @QueryParam("async") String asyncId)
-      throws Exception {
+      String collectionName, Boolean followAliases, String asyncId) throws Exception {
     final SubResponseAccumulatingJerseyResponse response =
         instantiateJerseyResponse(SubResponseAccumulatingJerseyResponse.class);
     final CoreContainer coreContainer = fetchAndValidateZooKeeperAwareCoreContainer();
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionBackupAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionBackupAPI.java
index 4edc0ec9c6f..48a482dce3e 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionBackupAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionBackupAPI.java
@@ -44,6 +44,8 @@ import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
 import javax.ws.rs.QueryParam;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
+import org.apache.solr.client.api.model.SubResponseAccumulatingJerseyResponse;
 import org.apache.solr.client.solrj.SolrResponse;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.cloud.ZkNodeProps;
@@ -55,8 +57,6 @@ import org.apache.solr.handler.api.V2ApiUtils;
 import org.apache.solr.jersey.JacksonReflectMapWriter;
 import org.apache.solr.jersey.PermissionName;
 import org.apache.solr.jersey.SolrJacksonMapper;
-import org.apache.solr.jersey.SolrJerseyResponse;
-import org.apache.solr.jersey.SubResponseAccumulatingJerseyResponse;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionSnapshotAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionSnapshotAPI.java
index e591e7aed7e..d77a91cafca 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionSnapshotAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionSnapshotAPI.java
@@ -36,13 +36,13 @@ import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
 import javax.ws.rs.QueryParam;
+import org.apache.solr.client.api.model.AsyncJerseyResponse;
 import org.apache.solr.client.solrj.SolrResponse;
 import org.apache.solr.common.cloud.ZkNodeProps;
 import org.apache.solr.common.params.CollectionParams;
 import org.apache.solr.common.params.CoreAdminParams;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.handler.admin.CollectionsHandler;
-import org.apache.solr.jersey.AsyncJerseyResponse;
 import org.apache.solr.jersey.PermissionName;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteNodeAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteNodeAPI.java
index 868e2b93273..5416793ddba 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteNodeAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteNodeAPI.java
@@ -34,6 +34,7 @@ import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.client.solrj.SolrResponse;
 import org.apache.solr.common.cloud.ZkNodeProps;
 import org.apache.solr.common.params.CollectionParams;
@@ -43,7 +44,6 @@ import org.apache.solr.core.CoreContainer;
 import org.apache.solr.handler.admin.CollectionsHandler;
 import org.apache.solr.jersey.JacksonReflectMapWriter;
 import org.apache.solr.jersey.PermissionName;
-import org.apache.solr.jersey.SolrJerseyResponse;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteReplicaAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteReplicaAPI.java
index fab78e3ac3a..143acf95134 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteReplicaAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteReplicaAPI.java
@@ -40,6 +40,7 @@ import javax.ws.rs.PUT;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.QueryParam;
+import org.apache.solr.client.api.model.SubResponseAccumulatingJerseyResponse;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.cloud.ZkNodeProps;
 import org.apache.solr.common.params.CollectionParams;
@@ -48,7 +49,6 @@ import org.apache.solr.core.CoreContainer;
 import org.apache.solr.handler.api.V2ApiUtils;
 import org.apache.solr.jersey.JacksonReflectMapWriter;
 import org.apache.solr.jersey.PermissionName;
-import org.apache.solr.jersey.SubResponseAccumulatingJerseyResponse;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteReplicaPropertyAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteReplicaPropertyAPI.java
index 933d31e640d..bce721ce087 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteReplicaPropertyAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteReplicaPropertyAPI.java
@@ -30,6 +30,7 @@ import java.util.Map;
 import javax.inject.Inject;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.client.solrj.SolrResponse;
 import org.apache.solr.common.cloud.ZkNodeProps;
 import org.apache.solr.common.params.CollectionParams;
@@ -37,7 +38,6 @@ import org.apache.solr.common.params.RequiredSolrParams;
 import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.handler.admin.CollectionsHandler;
-import org.apache.solr.jersey.SolrJerseyResponse;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteShardAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteShardAPI.java
index afa4abe92cd..593e6ed5e81 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteShardAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteShardAPI.java
@@ -34,12 +34,12 @@ import javax.ws.rs.DELETE;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.QueryParam;
+import org.apache.solr.client.api.model.SubResponseAccumulatingJerseyResponse;
 import org.apache.solr.common.cloud.ZkNodeProps;
 import org.apache.solr.common.params.CollectionParams;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.handler.api.V2ApiUtils;
 import org.apache.solr.jersey.PermissionName;
-import org.apache.solr.jersey.SubResponseAccumulatingJerseyResponse;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/ForceLeaderAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/ForceLeaderAPI.java
index fd32b52a1f7..b0881a34bac 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/ForceLeaderAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/ForceLeaderAPI.java
@@ -32,6 +32,7 @@ import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
 import javax.ws.rs.core.MediaType;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.cloud.ZkController;
 import org.apache.solr.cloud.ZkShardTerms;
 import org.apache.solr.common.SolrException;
@@ -42,7 +43,6 @@ import org.apache.solr.common.cloud.Slice;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.handler.api.V2ApiUtils;
 import org.apache.solr.jersey.PermissionName;
-import org.apache.solr.jersey.SolrJerseyResponse;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 import org.slf4j.Logger;
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/GetSchemaAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/GetSchemaAPI.java
index 3a966866727..b474a9c80c2 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/GetSchemaAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/GetSchemaAPI.java
@@ -27,10 +27,10 @@ import javax.ws.rs.Path;
 import javax.ws.rs.Produces;
 import javax.ws.rs.core.MediaType;
 import org.apache.solr.api.JerseyResource;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.util.SimpleOrderedMap;
 import org.apache.solr.jersey.PermissionName;
-import org.apache.solr.jersey.SolrJerseyResponse;
 import org.apache.solr.schema.IndexSchema;
 import org.apache.solr.security.PermissionNameProvider;
 
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/GetSchemaFieldAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/GetSchemaFieldAPI.java
index 2ad7db4d863..d90512decc1 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/GetSchemaFieldAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/GetSchemaFieldAPI.java
@@ -29,6 +29,7 @@ import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
 import javax.ws.rs.core.MediaType;
 import org.apache.solr.api.JerseyResource;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.common.MapWriter;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.cloud.SolrClassLoader;
@@ -37,7 +38,6 @@ import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.SimpleOrderedMap;
 import org.apache.solr.core.PluginInfo;
 import org.apache.solr.jersey.PermissionName;
-import org.apache.solr.jersey.SolrJerseyResponse;
 import org.apache.solr.pkg.PackageListeningClassLoader;
 import org.apache.solr.schema.IndexSchema;
 import org.apache.solr.security.PermissionNameProvider;
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/GetSchemaZkVersionAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/GetSchemaZkVersionAPI.java
index 56a22f77783..9a97af7b3eb 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/GetSchemaZkVersionAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/GetSchemaZkVersionAPI.java
@@ -29,10 +29,10 @@ import javax.ws.rs.Produces;
 import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.MediaType;
 import org.apache.solr.api.JerseyResource;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.cloud.ZkSolrResourceLoader;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.jersey.PermissionName;
-import org.apache.solr.jersey.SolrJerseyResponse;
 import org.apache.solr.schema.ManagedIndexSchema;
 import org.apache.solr.schema.ZkIndexSchemaReader;
 import org.apache.solr.security.PermissionNameProvider;
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/InstallCoreDataAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/InstallCoreDataAPI.java
index 53c47824f13..c0cd11b3451 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/InstallCoreDataAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/InstallCoreDataAPI.java
@@ -27,6 +27,7 @@ import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.cloud.CloudDescriptor;
 import org.apache.solr.cloud.ZkController;
 import org.apache.solr.common.SolrException;
@@ -37,7 +38,6 @@ import org.apache.solr.handler.RestoreCore;
 import org.apache.solr.handler.admin.CoreAdminHandler;
 import org.apache.solr.jersey.JacksonReflectMapWriter;
 import org.apache.solr.jersey.PermissionName;
-import org.apache.solr.jersey.SolrJerseyResponse;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 import org.slf4j.Logger;
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/InstallShardDataAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/InstallShardDataAPI.java
index 10c50c05396..0b313b02712 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/InstallShardDataAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/InstallShardDataAPI.java
@@ -29,6 +29,7 @@ import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.client.solrj.SolrResponse;
 import org.apache.solr.cloud.api.collections.InstallShardDataCmd;
 import org.apache.solr.common.SolrException;
@@ -41,7 +42,6 @@ import org.apache.solr.core.CoreContainer;
 import org.apache.solr.handler.admin.CollectionsHandler;
 import org.apache.solr.jersey.JacksonReflectMapWriter;
 import org.apache.solr.jersey.PermissionName;
-import org.apache.solr.jersey.SolrJerseyResponse;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 import org.apache.zookeeper.common.StringUtils;
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/ListAliasesAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/ListAliasesAPI.java
index 0f7103e5155..0e2626e9668 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/ListAliasesAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/ListAliasesAPI.java
@@ -31,11 +31,11 @@ import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
 import javax.ws.rs.core.MediaType;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.common.cloud.Aliases;
 import org.apache.solr.common.cloud.ZkStateReader;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.jersey.PermissionName;
-import org.apache.solr.jersey.SolrJerseyResponse;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/ListCollectionBackupsAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/ListCollectionBackupsAPI.java
index 89827b9ac03..3ec3c599e0d 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/ListCollectionBackupsAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/ListCollectionBackupsAPI.java
@@ -38,6 +38,7 @@ import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
 import javax.ws.rs.QueryParam;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.common.params.CommonParams;
 import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.core.CoreContainer;
@@ -48,7 +49,6 @@ import org.apache.solr.handler.api.V2ApiUtils;
 import org.apache.solr.jersey.JacksonReflectMapWriter;
 import org.apache.solr.jersey.PermissionName;
 import org.apache.solr.jersey.SolrJacksonMapper;
-import org.apache.solr.jersey.SolrJerseyResponse;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/ListCollectionSnapshotsAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/ListCollectionSnapshotsAPI.java
index b3fa81fb2c0..47f9194b046 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/ListCollectionSnapshotsAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/ListCollectionSnapshotsAPI.java
@@ -29,12 +29,12 @@ import javax.ws.rs.GET;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
+import org.apache.solr.client.api.model.AsyncJerseyResponse;
 import org.apache.solr.common.cloud.SolrZkClient;
 import org.apache.solr.common.util.CollectionUtil;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.core.snapshots.CollectionSnapshotMetaData;
 import org.apache.solr.core.snapshots.SolrSnapshotManager;
-import org.apache.solr.jersey.AsyncJerseyResponse;
 import org.apache.solr.jersey.PermissionName;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/ListCollectionsAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/ListCollectionsAPI.java
index df4fc547bf2..e03b05a6f70 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/ListCollectionsAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/ListCollectionsAPI.java
@@ -29,10 +29,10 @@ import javax.inject.Inject;
 import javax.ws.rs.GET;
 import javax.ws.rs.Path;
 import javax.ws.rs.Produces;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.common.cloud.DocCollection;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.jersey.PermissionName;
-import org.apache.solr.jersey.SolrJerseyResponse;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/MigrateReplicasAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/MigrateReplicasAPI.java
index da1188ef89d..6f353707504 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/MigrateReplicasAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/MigrateReplicasAPI.java
@@ -36,6 +36,7 @@ import javax.inject.Inject;
 import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.Produces;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.client.solrj.SolrResponse;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.cloud.ZkNodeProps;
@@ -44,7 +45,6 @@ import org.apache.solr.core.CoreContainer;
 import org.apache.solr.handler.admin.CollectionsHandler;
 import org.apache.solr.jersey.JacksonReflectMapWriter;
 import org.apache.solr.jersey.PermissionName;
-import org.apache.solr.jersey.SolrJerseyResponse;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/NodeLoggingAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/NodeLoggingAPI.java
index 742a53d2d95..a6ffbdd2f4b 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/NodeLoggingAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/NodeLoggingAPI.java
@@ -35,12 +35,12 @@ import javax.ws.rs.Path;
 import javax.ws.rs.Produces;
 import javax.ws.rs.QueryParam;
 import org.apache.solr.api.JerseyResource;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.common.SolrDocumentList;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.jersey.JacksonReflectMapWriter;
 import org.apache.solr.jersey.PermissionName;
-import org.apache.solr.jersey.SolrJerseyResponse;
 import org.apache.solr.logging.LogWatcher;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/ReloadCollectionAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/ReloadCollectionAPI.java
index 009ee727969..22c2abdaca3 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/ReloadCollectionAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/ReloadCollectionAPI.java
@@ -33,13 +33,13 @@ import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
 import javax.ws.rs.core.MediaType;
+import org.apache.solr.client.api.model.SubResponseAccumulatingJerseyResponse;
 import org.apache.solr.common.cloud.ZkNodeProps;
 import org.apache.solr.common.params.CollectionParams;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.handler.api.V2ApiUtils;
 import org.apache.solr.jersey.JacksonReflectMapWriter;
 import org.apache.solr.jersey.PermissionName;
-import org.apache.solr.jersey.SubResponseAccumulatingJerseyResponse;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 import org.slf4j.Logger;
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/RenameCollectionAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/RenameCollectionAPI.java
index c63c518426b..2fd2d7fb405 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/RenameCollectionAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/RenameCollectionAPI.java
@@ -34,6 +34,7 @@ import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
 import javax.ws.rs.core.MediaType;
+import org.apache.solr.client.api.model.SubResponseAccumulatingJerseyResponse;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.cloud.ZkNodeProps;
 import org.apache.solr.common.params.CollectionParams;
@@ -41,7 +42,6 @@ import org.apache.solr.core.CoreContainer;
 import org.apache.solr.handler.api.V2ApiUtils;
 import org.apache.solr.jersey.JacksonReflectMapWriter;
 import org.apache.solr.jersey.PermissionName;
-import org.apache.solr.jersey.SubResponseAccumulatingJerseyResponse;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/ReplaceNodeAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/ReplaceNodeAPI.java
index dc4fca1707d..e2e63760ab7 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/ReplaceNodeAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/ReplaceNodeAPI.java
@@ -36,6 +36,7 @@ import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.client.solrj.SolrResponse;
 import org.apache.solr.common.cloud.ZkNodeProps;
 import org.apache.solr.common.params.CollectionParams;
@@ -44,7 +45,6 @@ import org.apache.solr.core.CoreContainer;
 import org.apache.solr.handler.admin.CollectionsHandler;
 import org.apache.solr.jersey.JacksonReflectMapWriter;
 import org.apache.solr.jersey.PermissionName;
-import org.apache.solr.jersey.SolrJerseyResponse;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/RestoreCollectionAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/RestoreCollectionAPI.java
index acd9ccf3a03..18caed1933a 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/RestoreCollectionAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/RestoreCollectionAPI.java
@@ -46,6 +46,8 @@ import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
+import org.apache.solr.client.api.model.SubResponseAccumulatingJerseyResponse;
 import org.apache.solr.client.solrj.SolrResponse;
 import org.apache.solr.client.solrj.util.SolrIdentifierValidator;
 import org.apache.solr.common.SolrException;
@@ -56,8 +58,6 @@ import org.apache.solr.core.CoreContainer;
 import org.apache.solr.handler.admin.CollectionsHandler;
 import org.apache.solr.jersey.JacksonReflectMapWriter;
 import org.apache.solr.jersey.PermissionName;
-import org.apache.solr.jersey.SolrJerseyResponse;
-import org.apache.solr.jersey.SubResponseAccumulatingJerseyResponse;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/RestoreCoreAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/RestoreCoreAPI.java
index 11243630cf7..03358880450 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/RestoreCoreAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/RestoreCoreAPI.java
@@ -27,6 +27,7 @@ import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.cloud.CloudDescriptor;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.cloud.Slice;
@@ -39,7 +40,6 @@ import org.apache.solr.handler.RestoreCore;
 import org.apache.solr.handler.admin.CoreAdminHandler;
 import org.apache.solr.jersey.JacksonReflectMapWriter;
 import org.apache.solr.jersey.PermissionName;
-import org.apache.solr.jersey.SolrJerseyResponse;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/SyncShardAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/SyncShardAPI.java
index 07488e3c5ed..1b7b44b0dea 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/SyncShardAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/SyncShardAPI.java
@@ -30,6 +30,7 @@ import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
 import javax.ws.rs.core.MediaType;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrServerException;
 import org.apache.solr.client.solrj.impl.HttpSolrClient;
@@ -41,7 +42,6 @@ import org.apache.solr.common.cloud.ZkNodeProps;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.handler.api.V2ApiUtils;
 import org.apache.solr.jersey.PermissionName;
-import org.apache.solr.jersey.SolrJerseyResponse;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 
diff --git a/solr/core/src/java/org/apache/solr/handler/api/V2ApiUtils.java b/solr/core/src/java/org/apache/solr/handler/api/V2ApiUtils.java
index 454a1bcbde6..2fa0ae4ed3f 100644
--- a/solr/core/src/java/org/apache/solr/handler/api/V2ApiUtils.java
+++ b/solr/core/src/java/org/apache/solr/handler/api/V2ApiUtils.java
@@ -20,12 +20,14 @@ package org.apache.solr.handler.api;
 import static org.apache.solr.client.solrj.impl.BinaryResponseParser.BINARY_CONTENT_TYPE_V2;
 import static org.apache.solr.common.params.CommonParams.WT;
 
-import java.io.IOException;
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonProperty;
 import java.util.List;
 import java.util.Map;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.common.MapWriter.EntryWriter;
 import org.apache.solr.common.util.NamedList;
-import org.apache.solr.jersey.JacksonReflectMapWriter;
+import org.apache.solr.common.util.Utils;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 
@@ -55,39 +57,34 @@ public class V2ApiUtils {
   }
 
   /**
-   * Convert a JacksonReflectMapWriter (typically a {@link
-   * org.apache.solr.jersey.SolrJerseyResponse}) into the NamedList on a SolrQueryResponse, omitting
-   * the response header
+   * Convert a JacksonReflectMapWriter (typically a {@link SolrJerseyResponse}) into the NamedList
+   * on a SolrQueryResponse, omitting the response header
    *
    * @param rsp the response to attach the resulting NamedList to
    * @param mw the input object to be converted into a NamedList
    */
-  public static void squashIntoSolrResponseWithoutHeader(
-      SolrQueryResponse rsp, JacksonReflectMapWriter mw) {
-    squashIntoNamedList(rsp.getValues(), mw, true);
+  public static void squashIntoSolrResponseWithoutHeader(SolrQueryResponse rsp, Object mw) {
+    squashObjectIntoNamedList(rsp.getValues(), mw, true);
   }
 
   /**
-   * Convert a JacksonReflectMapWriter (typically a {@link
-   * org.apache.solr.jersey.SolrJerseyResponse}) into the NamedList on a SolrQueryResponse,
-   * including the response header
+   * Convert a JacksonReflectMapWriter (typically a {@link SolrJerseyResponse}) into the NamedList
+   * on a SolrQueryResponse, including the response header
    *
    * @param rsp the response to attach the resulting NamedList to
    * @param mw the input object to be converted into a NamedList
    */
-  public static void squashIntoSolrResponseWithHeader(
-      SolrQueryResponse rsp, JacksonReflectMapWriter mw) {
-    squashIntoNamedList(rsp.getValues(), mw, false);
+  public static void squashIntoSolrResponseWithHeader(SolrQueryResponse rsp, Object mw) {
+    squashObjectIntoNamedList(rsp.getValues(), mw, false);
   }
 
-  public static void squashIntoNamedList(
-      NamedList<Object> destination, JacksonReflectMapWriter mw) {
-    squashIntoNamedList(destination, mw, false);
+  public static void squashIntoNamedList(NamedList<Object> destination, Object mw) {
+    squashObjectIntoNamedList(destination, mw, false);
   }
 
   public static void squashIntoNamedListWithoutHeader(
-      NamedList<Object> destination, JacksonReflectMapWriter mw) {
-    squashIntoNamedList(destination, mw, true);
+      NamedList<Object> destination, Object toSquash) {
+    squashObjectIntoNamedList(destination, toSquash, true);
   }
 
   public static String getMediaTypeFromWtParam(
@@ -106,23 +103,29 @@ public class V2ApiUtils {
     }
   }
 
-  private static void squashIntoNamedList(
-      NamedList<Object> destination, JacksonReflectMapWriter mw, boolean trimHeader) {
-    try {
-      mw.writeMap(
-          new EntryWriter() {
-            @Override
-            public EntryWriter put(CharSequence key, Object value) {
-              var kStr = key.toString();
-              if (trimHeader && kStr.equals("responseHeader")) {
-                return null;
-              }
-              destination.add(kStr, value);
-              return this; // returning "this" means we can't use a lambda :-(
+  public static void squashObjectIntoNamedList(
+      NamedList<Object> destination, Object o, boolean trimHeader) {
+    final var ew =
+        new EntryWriter() {
+          @Override
+          public EntryWriter put(CharSequence key, Object value) {
+            var kStr = key.toString();
+            if (trimHeader && kStr.equals("responseHeader")) {
+              return null;
             }
-          });
-    } catch (IOException e) {
-      throw new RuntimeException(e); // impossible
-    }
+            destination.add(kStr, value);
+            return this; // returning "this" means we can't use a lambda :-(
+          }
+        };
+    Utils.reflectWrite(
+        ew,
+        o,
+        // TODO Should we be lenient here and accept both the Jackson and our homegrown annotation?
+        field -> field.getAnnotation(JsonProperty.class) != null,
+        JsonAnyGetter.class,
+        field -> {
+          final JsonProperty prop = field.getAnnotation(JsonProperty.class);
+          return prop.value().isEmpty() ? field.getName() : prop.value();
+        });
   }
 }
diff --git a/solr/core/src/java/org/apache/solr/handler/configsets/ListConfigSetsAPI.java b/solr/core/src/java/org/apache/solr/handler/configsets/ListConfigSetsAPI.java
index 978f290d763..58dcdfec5e9 100644
--- a/solr/core/src/java/org/apache/solr/handler/configsets/ListConfigSetsAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/configsets/ListConfigSetsAPI.java
@@ -28,9 +28,9 @@ import javax.ws.rs.Produces;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.HttpHeaders;
 import org.apache.solr.api.JerseyResource;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.jersey.PermissionName;
-import org.apache.solr.jersey.SolrJerseyResponse;
 
 /**
  * V2 API for adding or updating a single file within a configset.
diff --git a/solr/core/src/java/org/apache/solr/jersey/CatchAllExceptionMapper.java b/solr/core/src/java/org/apache/solr/jersey/CatchAllExceptionMapper.java
index 6dfec0fe1ba..d3886ae8727 100644
--- a/solr/core/src/java/org/apache/solr/jersey/CatchAllExceptionMapper.java
+++ b/solr/core/src/java/org/apache/solr/jersey/CatchAllExceptionMapper.java
@@ -31,6 +31,7 @@ import javax.ws.rs.core.Context;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.ext.ExceptionMapper;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.handler.RequestHandlerBase;
 import org.apache.solr.handler.api.V2ApiUtils;
diff --git a/solr/core/src/java/org/apache/solr/jersey/ExperimentalResponse.java b/solr/core/src/java/org/apache/solr/jersey/ExperimentalResponse.java
index 657e3652064..7824b7bc314 100644
--- a/solr/core/src/java/org/apache/solr/jersey/ExperimentalResponse.java
+++ b/solr/core/src/java/org/apache/solr/jersey/ExperimentalResponse.java
@@ -18,6 +18,7 @@
 package org.apache.solr.jersey;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
 
 /**
  * {@link SolrJerseyResponse} implementation with a warning field indicating that the format may
diff --git a/solr/core/src/java/org/apache/solr/jersey/JerseyApplications.java b/solr/core/src/java/org/apache/solr/jersey/JerseyApplications.java
index bd4283c2dcf..6bb4b07c439 100644
--- a/solr/core/src/java/org/apache/solr/jersey/JerseyApplications.java
+++ b/solr/core/src/java/org/apache/solr/jersey/JerseyApplications.java
@@ -17,16 +17,12 @@
 
 package org.apache.solr.jersey;
 
-import io.swagger.v3.oas.annotations.OpenAPIDefinition;
-import io.swagger.v3.oas.annotations.info.Info;
-import io.swagger.v3.oas.annotations.info.License;
 import java.util.Map;
 import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 import org.apache.solr.schema.IndexSchema;
-import org.apache.solr.util.SolrVersion;
 import org.glassfish.hk2.utilities.binding.AbstractBinder;
 import org.glassfish.jersey.jackson.internal.jackson.jaxrs.json.JacksonJsonProvider;
 import org.glassfish.jersey.process.internal.RequestScoped;
@@ -36,13 +32,6 @@ import org.glassfish.jersey.server.ResourceConfig;
  * JAX-RS "application" configurations for Solr's {@link org.apache.solr.core.CoreContainer} and
  * {@link SolrCore} instances
  */
-@OpenAPIDefinition(
-    info =
-        @Info(
-            title = "v2 API",
-            description = "OpenAPI spec for Solr's v2 API endpoints",
-            license = @License(name = "ASL 2.0"),
-            version = SolrVersion.LATEST_STRING))
 public class JerseyApplications {
 
   public static class CoreContainerApp extends ResourceConfig {
diff --git a/solr/core/src/java/org/apache/solr/jersey/MessageBodyWriters.java b/solr/core/src/java/org/apache/solr/jersey/MessageBodyWriters.java
index 274f6b19fca..49fd716d09f 100644
--- a/solr/core/src/java/org/apache/solr/jersey/MessageBodyWriters.java
+++ b/solr/core/src/java/org/apache/solr/jersey/MessageBodyWriters.java
@@ -54,7 +54,7 @@ public class MessageBodyWriters {
 
   @Produces(MediaType.APPLICATION_XML)
   public static class XmlMessageBodyWriter extends BaseMessageBodyWriter
-      implements MessageBodyWriter<JacksonReflectMapWriter> {
+      implements MessageBodyWriter<Object> {
     @Override
     public QueryResponseWriter createResponseWriter() {
       return new XMLResponseWriter();
@@ -68,7 +68,7 @@ public class MessageBodyWriters {
 
   @Produces(BINARY_CONTENT_TYPE_V2)
   public static class JavabinMessageBodyWriter extends BaseMessageBodyWriter
-      implements MessageBodyWriter<JacksonReflectMapWriter> {
+      implements MessageBodyWriter<Object> {
     @Override
     public QueryResponseWriter createResponseWriter() {
       return new BinaryResponseWriter();
@@ -82,7 +82,7 @@ public class MessageBodyWriters {
 
   @Produces(RawResponseWriter.CONTENT_TYPE)
   public static class RawMessageBodyWriter extends BaseMessageBodyWriter
-      implements MessageBodyWriter<JacksonReflectMapWriter> {
+      implements MessageBodyWriter<Object> {
     @Override
     public QueryResponseWriter createResponseWriter() {
       return new RawResponseWriter();
@@ -96,7 +96,7 @@ public class MessageBodyWriters {
 
   @Produces(CONTENT_TYPE_TEXT_UTF8)
   public static class CsvMessageBodyWriter extends BaseMessageBodyWriter
-      implements MessageBodyWriter<JacksonReflectMapWriter> {
+      implements MessageBodyWriter<Object> {
     @Override
     public QueryResponseWriter createResponseWriter() {
       return new CSVResponseWriter();
@@ -108,8 +108,7 @@ public class MessageBodyWriters {
     }
   }
 
-  public abstract static class BaseMessageBodyWriter
-      implements MessageBodyWriter<JacksonReflectMapWriter> {
+  public abstract static class BaseMessageBodyWriter implements MessageBodyWriter<Object> {
 
     @Context protected ResourceContext resourceContext;
     private final QueryResponseWriter responseWriter = createResponseWriter();
@@ -126,7 +125,7 @@ public class MessageBodyWriters {
 
     @Override
     public void writeTo(
-        JacksonReflectMapWriter reflectMapWriter,
+        Object toWrite,
         Class<?> type,
         Type genericType,
         Annotation[] annotations,
@@ -141,7 +140,7 @@ public class MessageBodyWriters {
       final SolrQueryResponse solrQueryResponse =
           (SolrQueryResponse) requestContext.getProperty(SOLR_QUERY_RESPONSE);
 
-      V2ApiUtils.squashIntoSolrResponseWithHeader(solrQueryResponse, reflectMapWriter);
+      V2ApiUtils.squashIntoSolrResponseWithHeader(solrQueryResponse, toWrite);
       QueryResponseWriterUtil.writeQueryResponse(
           entityStream, responseWriter, solrQueryRequest, solrQueryResponse, mediaType.toString());
     }
diff --git a/solr/core/src/java/org/apache/solr/jersey/PostRequestDecorationFilter.java b/solr/core/src/java/org/apache/solr/jersey/PostRequestDecorationFilter.java
index b7ecd64720c..43215fefa42 100644
--- a/solr/core/src/java/org/apache/solr/jersey/PostRequestDecorationFilter.java
+++ b/solr/core/src/java/org/apache/solr/jersey/PostRequestDecorationFilter.java
@@ -26,6 +26,7 @@ import javax.annotation.Priority;
 import javax.ws.rs.container.ContainerRequestContext;
 import javax.ws.rs.container.ContainerResponseContext;
 import javax.ws.rs.container.ContainerResponseFilter;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.request.SolrRequestHandler;
diff --git a/solr/core/src/java/org/apache/solr/jersey/PostRequestLoggingFilter.java b/solr/core/src/java/org/apache/solr/jersey/PostRequestLoggingFilter.java
index f2d35e93573..4ce9490c115 100644
--- a/solr/core/src/java/org/apache/solr/jersey/PostRequestLoggingFilter.java
+++ b/solr/core/src/java/org/apache/solr/jersey/PostRequestLoggingFilter.java
@@ -38,6 +38,7 @@ import javax.ws.rs.container.ContainerResponseFilter;
 import javax.ws.rs.container.ResourceInfo;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.MultivaluedMap;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.common.util.CollectionUtil;
 import org.apache.solr.common.util.StrUtils;
 import org.apache.solr.core.SolrCore;
diff --git a/solr/core/src/java/org/apache/solr/jersey/RequestContextKeys.java b/solr/core/src/java/org/apache/solr/jersey/RequestContextKeys.java
index f300ffa4bbb..bf28cc0db71 100644
--- a/solr/core/src/java/org/apache/solr/jersey/RequestContextKeys.java
+++ b/solr/core/src/java/org/apache/solr/jersey/RequestContextKeys.java
@@ -21,6 +21,7 @@ import com.codahale.metrics.Timer;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.ws.rs.container.ContainerRequestContext;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.core.PluginBag;
diff --git a/solr/core/src/java/org/apache/solr/jersey/RequestMetricHandling.java b/solr/core/src/java/org/apache/solr/jersey/RequestMetricHandling.java
index a6e8b03919e..c50bb53d37c 100644
--- a/solr/core/src/java/org/apache/solr/jersey/RequestMetricHandling.java
+++ b/solr/core/src/java/org/apache/solr/jersey/RequestMetricHandling.java
@@ -30,6 +30,7 @@ import javax.ws.rs.container.ContainerResponseContext;
 import javax.ws.rs.container.ContainerResponseFilter;
 import javax.ws.rs.container.ResourceInfo;
 import javax.ws.rs.core.Context;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.core.PluginBag;
 import org.apache.solr.handler.RequestHandlerBase;
 import org.apache.solr.request.SolrQueryRequest;
diff --git a/solr/core/src/java/org/apache/solr/packagemanager/PackageManager.java b/solr/core/src/java/org/apache/solr/packagemanager/PackageManager.java
index a108dba6df9..2312ef862ca 100644
--- a/solr/core/src/java/org/apache/solr/packagemanager/PackageManager.java
+++ b/solr/core/src/java/org/apache/solr/packagemanager/PackageManager.java
@@ -38,6 +38,7 @@ import java.util.Set;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 import org.apache.solr.cli.SolrCLI;
+import org.apache.solr.client.api.util.SolrVersion;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrRequest;
 import org.apache.solr.client.solrj.SolrServerException;
@@ -62,7 +63,6 @@ import org.apache.solr.packagemanager.SolrPackage.Command;
 import org.apache.solr.packagemanager.SolrPackage.Manifest;
 import org.apache.solr.packagemanager.SolrPackage.Plugin;
 import org.apache.solr.pkg.SolrPackageLoader;
-import org.apache.solr.util.SolrVersion;
 import org.apache.zookeeper.KeeperException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -871,7 +871,7 @@ public class PackageManager implements Closeable {
         if (pkg.version.equals(version)) {
           return pkg;
         }
-        if (PackageUtils.compareVersions(latest.version, pkg.version) <= 0) {
+        if (SolrVersion.compareVersions(latest.version, pkg.version) <= 0) {
           latest = pkg;
         }
       }
@@ -919,20 +919,12 @@ public class PackageManager implements Closeable {
     }
 
     Manifest manifest = packageInstance.manifest;
-    try {
-      if (!SolrVersion.LATEST.satisfies(manifest.versionConstraint)) {
-        PackageUtils.printRed(
-            "Version incompatible! Solr version: "
-                + SolrVersion.LATEST
-                + ", package version constraint: "
-                + manifest.versionConstraint);
-        System.exit(1);
-      }
-    } catch (SolrVersion.InvalidSemVerExpressionException ex) {
+    if (!SolrVersion.LATEST.satisfies(manifest.versionConstraint)) {
       PackageUtils.printRed(
-          "Error in version constraint given in package manifest: "
-              + manifest.versionConstraint
-              + ". It does not a valid SemVer expression.");
+          "Version incompatible! Solr version: "
+              + SolrVersion.LATEST
+              + ", package version constraint: "
+              + manifest.versionConstraint);
       System.exit(1);
     }
 
diff --git a/solr/core/src/java/org/apache/solr/packagemanager/PackageUtils.java b/solr/core/src/java/org/apache/solr/packagemanager/PackageUtils.java
index 97379a3a114..9d330b49bb8 100644
--- a/solr/core/src/java/org/apache/solr/packagemanager/PackageUtils.java
+++ b/solr/core/src/java/org/apache/solr/packagemanager/PackageUtils.java
@@ -53,7 +53,6 @@ import org.apache.solr.filestore.DistribPackageStore;
 import org.apache.solr.filestore.PackageStoreAPI;
 import org.apache.solr.packagemanager.SolrPackage.Manifest;
 import org.apache.solr.util.SolrJacksonAnnotationInspector;
-import org.semver4j.Semver;
 
 public class PackageUtils {
 
@@ -226,14 +225,6 @@ public class PackageUtils {
     return str;
   }
 
-  /**
-   * Compares two versions v1 and v2. Returns negative if v1 isLessThan v2, positive if v1
-   * isGreaterThan v2 and 0 if equal.
-   */
-  public static int compareVersions(String v1, String v2) {
-    return new Semver(v1).compareTo(new Semver(v2));
-  }
-
   public static String BLACK = "\u001B[30m";
   public static String RED = "\u001B[31m";
   public static String GREEN = "\u001B[32m";
diff --git a/solr/core/src/java/org/apache/solr/packagemanager/RepositoryManager.java b/solr/core/src/java/org/apache/solr/packagemanager/RepositoryManager.java
index a480683eba6..8ef29ba5552 100644
--- a/solr/core/src/java/org/apache/solr/packagemanager/RepositoryManager.java
+++ b/solr/core/src/java/org/apache/solr/packagemanager/RepositoryManager.java
@@ -37,6 +37,7 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.stream.Collectors;
+import org.apache.solr.client.api.util.SolrVersion;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrRequest;
 import org.apache.solr.client.solrj.SolrServerException;
@@ -287,7 +288,7 @@ public class RepositoryManager {
       return getLastPackageRelease(pkg);
     }
     for (SolrPackageRelease release : pkg.versions) {
-      if (PackageUtils.compareVersions(version, release.version) == 0) {
+      if (SolrVersion.compareVersions(version, release.version) == 0) {
         return release;
       }
     }
@@ -310,7 +311,7 @@ public class RepositoryManager {
       if (latest == null) {
         latest = release;
       } else {
-        if (PackageUtils.compareVersions(latest.version, release.version) < 0) {
+        if (SolrVersion.compareVersions(latest.version, release.version) < 0) {
           latest = release;
         }
       }
@@ -329,7 +330,7 @@ public class RepositoryManager {
     }
     String installedVersion = packageManager.getPackageInstance(packageName, null).version;
     SolrPackageRelease last = getLastPackageRelease(packageName);
-    return last != null && PackageUtils.compareVersions(last.version, installedVersion) > 0;
+    return last != null && SolrVersion.compareVersions(last.version, installedVersion) > 0;
   }
 
   /**
diff --git a/solr/core/src/java/org/apache/solr/security/PublicKeyAPI.java b/solr/core/src/java/org/apache/solr/security/PublicKeyAPI.java
index 94ee5b099ac..46483462e5f 100644
--- a/solr/core/src/java/org/apache/solr/security/PublicKeyAPI.java
+++ b/solr/core/src/java/org/apache/solr/security/PublicKeyAPI.java
@@ -26,8 +26,8 @@ import javax.ws.rs.GET;
 import javax.ws.rs.Path;
 import javax.ws.rs.Produces;
 import org.apache.solr.api.JerseyResource;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.jersey.PermissionName;
-import org.apache.solr.jersey.SolrJerseyResponse;
 
 /**
  * V2 API for fetching the public key of the receiving node.
diff --git a/solr/core/src/java/org/apache/solr/servlet/CoreContainerProvider.java b/solr/core/src/java/org/apache/solr/servlet/CoreContainerProvider.java
index 8859c9a3a58..6cd261bc0f2 100644
--- a/solr/core/src/java/org/apache/solr/servlet/CoreContainerProvider.java
+++ b/solr/core/src/java/org/apache/solr/servlet/CoreContainerProvider.java
@@ -58,6 +58,7 @@ import javax.servlet.UnavailableException;
 import org.apache.http.client.HttpClient;
 import org.apache.lucene.store.MMapDirectory;
 import org.apache.lucene.util.VectorUtil;
+import org.apache.solr.client.api.util.SolrVersion;
 import org.apache.solr.cloud.ZkController;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.SolrException.ErrorCode;
@@ -75,7 +76,6 @@ import org.apache.solr.metrics.SolrMetricManager;
 import org.apache.solr.metrics.SolrMetricManager.ResolutionStrategy;
 import org.apache.solr.metrics.SolrMetricProducer;
 import org.apache.solr.servlet.RateLimitManager.Builder;
-import org.apache.solr.util.SolrVersion;
 import org.apache.solr.util.StartupLoggingUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
diff --git a/solr/core/src/java/org/apache/solr/servlet/ResponseUtils.java b/solr/core/src/java/org/apache/solr/servlet/ResponseUtils.java
index 9471b64befe..337243e25e6 100644
--- a/solr/core/src/java/org/apache/solr/servlet/ResponseUtils.java
+++ b/solr/core/src/java/org/apache/solr/servlet/ResponseUtils.java
@@ -19,9 +19,9 @@ package org.apache.solr.servlet;
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import org.apache.solr.api.ApiBag;
+import org.apache.solr.client.api.model.ErrorInfo;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.util.NamedList;
-import org.apache.solr.jersey.ErrorInfo;
 import org.slf4j.Logger;
 
 /** Response helper methods. */
@@ -50,9 +50,9 @@ public class ResponseUtils {
       if (errorMetadata == null) {
         errorMetadata = new NamedList<>();
       }
-      errorMetadata.add(SolrException.ERROR_CLASS, ex.getClass().getName());
+      errorMetadata.add(ErrorInfo.ERROR_CLASS, ex.getClass().getName());
       errorMetadata.add(
-          SolrException.ROOT_ERROR_CLASS, SolrException.getRootCause(ex).getClass().getName());
+          ErrorInfo.ROOT_ERROR_CLASS, SolrException.getRootCause(ex).getClass().getName());
       info.add("metadata", errorMetadata);
       if (ex instanceof ApiBag.ExceptionWithErrObject) {
         ApiBag.ExceptionWithErrObject exception = (ApiBag.ExceptionWithErrObject) ex;
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/AddReplicaPropertyAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/AddReplicaPropertyAPITest.java
index 01ca69ed6d3..501d22b130c 100644
--- a/solr/core/src/test/org/apache/solr/handler/admin/api/AddReplicaPropertyAPITest.java
+++ b/solr/core/src/test/org/apache/solr/handler/admin/api/AddReplicaPropertyAPITest.java
@@ -28,6 +28,7 @@ import io.opentracing.noop.NoopSpan;
 import java.util.Map;
 import java.util.Optional;
 import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.client.api.model.AddReplicaPropertyRequestBody;
 import org.apache.solr.cloud.OverseerSolrResponse;
 import org.apache.solr.cloud.api.collections.DistributedCollectionConfigSetCommandRunner;
 import org.apache.solr.common.SolrException;
@@ -41,11 +42,11 @@ import org.junit.BeforeClass;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 
-/** Unit tests for {@link AddReplicaPropertyAPI} */
+/** Unit tests for {@link AddReplicaProperty} */
 public class AddReplicaPropertyAPITest extends SolrTestCaseJ4 {
 
-  private static final AddReplicaPropertyAPI.AddReplicaPropertyRequestBody ANY_REQ_BODY =
-      new AddReplicaPropertyAPI.AddReplicaPropertyRequestBody("anyValue");
+  private static final AddReplicaPropertyRequestBody ANY_REQ_BODY =
+      new AddReplicaPropertyRequestBody("anyValue");
 
   private CoreContainer mockCoreContainer;
   private DistributedCollectionConfigSetCommandRunner mockCommandRunner;
@@ -53,7 +54,7 @@ public class AddReplicaPropertyAPITest extends SolrTestCaseJ4 {
   private SolrQueryResponse queryResponse;
   private ArgumentCaptor<ZkNodeProps> messageCapturer;
 
-  private AddReplicaPropertyAPI addReplicaPropApi;
+  private AddReplicaProperty addReplicaPropApi;
 
   @BeforeClass
   public static void ensureWorkingMockito() {
@@ -76,8 +77,7 @@ public class AddReplicaPropertyAPITest extends SolrTestCaseJ4 {
     queryResponse = new SolrQueryResponse();
     messageCapturer = ArgumentCaptor.forClass(ZkNodeProps.class);
 
-    addReplicaPropApi =
-        new AddReplicaPropertyAPI(mockCoreContainer, mockQueryRequest, queryResponse);
+    addReplicaPropApi = new AddReplicaProperty(mockCoreContainer, mockQueryRequest, queryResponse);
   }
 
   @Test
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/DeleteAliasAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/DeleteAliasAPITest.java
index 2dcaf14d183..dc93c29b487 100644
--- a/solr/core/src/test/org/apache/solr/handler/admin/api/DeleteAliasAPITest.java
+++ b/solr/core/src/test/org/apache/solr/handler/admin/api/DeleteAliasAPITest.java
@@ -25,18 +25,17 @@ import java.util.Map;
 import org.apache.solr.SolrTestCaseJ4;
 import org.junit.Test;
 
-/** Unit tests for {@link DeleteAliasAPI}. */
+/** Unit tests for {@link DeleteAlias}. */
 public class DeleteAliasAPITest extends SolrTestCaseJ4 {
 
   @Test
   public void testConstructsValidRemoteMessage() {
-    Map<String, Object> props =
-        DeleteAliasAPI.createRemoteMessage("aliasName", null).getProperties();
+    Map<String, Object> props = DeleteAlias.createRemoteMessage("aliasName", null).getProperties();
     assertEquals(2, props.size());
     assertEquals("deletealias", props.get(QUEUE_OPERATION));
     assertEquals("aliasName", props.get(NAME));
 
-    props = DeleteAliasAPI.createRemoteMessage("aliasName", "asyncId").getProperties();
+    props = DeleteAlias.createRemoteMessage("aliasName", "asyncId").getProperties();
     assertEquals(3, props.size());
     assertEquals("deletealias", props.get(QUEUE_OPERATION));
     assertEquals("aliasName", props.get(NAME));
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/DeleteCollectionAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/DeleteCollectionAPITest.java
index a87dad3c900..60f2dccff19 100644
--- a/solr/core/src/test/org/apache/solr/handler/admin/api/DeleteCollectionAPITest.java
+++ b/solr/core/src/test/org/apache/solr/handler/admin/api/DeleteCollectionAPITest.java
@@ -29,15 +29,14 @@ import org.apache.solr.common.cloud.ZkNodeProps;
 import org.hamcrest.MatcherAssert;
 import org.junit.Test;
 
-/** Unit tests for {@link DeleteCollectionAPI} */
+/** Unit tests for {@link DeleteCollection} */
 public class DeleteCollectionAPITest extends SolrTestCaseJ4 {
 
   @Test
   public void testConstructsValidOverseerMessage() {
     // Only required properties provided
     {
-      final ZkNodeProps message =
-          DeleteCollectionAPI.createRemoteMessage("someCollName", null, null);
+      final ZkNodeProps message = DeleteCollection.createRemoteMessage("someCollName", null, null);
       final Map<String, Object> rawMessage = message.getProperties();
       assertEquals(2, rawMessage.size());
       MatcherAssert.assertThat(rawMessage.keySet(), containsInAnyOrder(QUEUE_OPERATION, NAME));
@@ -48,7 +47,7 @@ public class DeleteCollectionAPITest extends SolrTestCaseJ4 {
     // Optional properties ('followAliases' and 'async') also provided
     {
       final ZkNodeProps message =
-          DeleteCollectionAPI.createRemoteMessage("someCollName", Boolean.TRUE, "someAsyncId");
+          DeleteCollection.createRemoteMessage("someCollName", Boolean.TRUE, "someAsyncId");
       final Map<String, Object> rawMessage = message.getProperties();
       assertEquals(4, rawMessage.size());
       MatcherAssert.assertThat(
diff --git a/solr/core/src/test/org/apache/solr/jersey/JacksonReflectMapWriterTest.java b/solr/core/src/test/org/apache/solr/jersey/JacksonReflectMapWriterTest.java
index 12c31f62f53..7e1ed6d8da7 100644
--- a/solr/core/src/test/org/apache/solr/jersey/JacksonReflectMapWriterTest.java
+++ b/solr/core/src/test/org/apache/solr/jersey/JacksonReflectMapWriterTest.java
@@ -21,9 +21,8 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import org.apache.solr.SolrTestCaseJ4;
-import org.apache.solr.common.util.NamedList;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.common.util.Utils;
-import org.apache.solr.handler.api.V2ApiUtils;
 import org.junit.Test;
 
 /**
@@ -64,27 +63,4 @@ public class JacksonReflectMapWriterTest extends SolrTestCaseJ4 {
     assertEquals(123, response.responseHeader.status);
     assertEquals("someVal", response.responseHeader.unknownProperties().get("someUnexpectedField"));
   }
-
-  @Test
-  public void testMapConversionHandlesUnknownProperties() {
-    final SolrJerseyResponse response = new SolrJerseyResponse();
-    response.responseHeader.status = 123;
-    response.responseHeader.setUnknownProperty("someField", "someVal");
-    response.responseHeader.setUnknownProperty("someNonStringField", 456);
-    final NamedList<Object> destination = new NamedList<>();
-
-    V2ApiUtils.squashIntoNamedList(destination, response);
-    assertEquals(
-        response.responseHeader,
-        destination.get(
-            "responseHeader")); // ResponseHeader copied as-is to NL since its a serializable type
-    final String jsonOutput = Utils.toJSONString(destination);
-
-    assertTrue(
-        "Expected JSON " + jsonOutput + " to contain different value",
-        jsonOutput.contains("\"someField\":\"someVal\""));
-    assertTrue(
-        "Expected JSON " + jsonOutput + " to contain different value",
-        jsonOutput.contains("\"someNonStringField\":456"));
-  }
 }
diff --git a/solr/solr-ref-guide/modules/query-guide/pages/stream-decorator-reference.adoc b/solr/solr-ref-guide/modules/query-guide/pages/stream-decorator-reference.adoc
index be9c83b970c..447bb1b8d56 100644
--- a/solr/solr-ref-guide/modules/query-guide/pages/stream-decorator-reference.adoc
+++ b/solr/solr-ref-guide/modules/query-guide/pages/stream-decorator-reference.adoc
@@ -428,7 +428,7 @@ classify(model(modelCollection,
              field="text_t")
 ----
 
-In the example above the `classify expression` is retrieving the model using the `model` function.
+In the example above the `classify expression` is retrieving the model using the `api` function.
 It is then classifying tuples returned by the `search` function.
 The `text_t` field is used for the text classification and the analyzer for the `text_t` field in the Solr schema is used to analyze the text and extract the features.
 
diff --git a/solr/solr-ref-guide/modules/query-guide/pages/stream-source-reference.adoc b/solr/solr-ref-guide/modules/query-guide/pages/stream-source-reference.adoc
index 45e0b6db726..3808a812f4c 100644
--- a/solr/solr-ref-guide/modules/query-guide/pages/stream-source-reference.adoc
+++ b/solr/solr-ref-guide/modules/query-guide/pages/stream-source-reference.adoc
@@ -310,8 +310,8 @@ knnSearch(collection1,
 
 == model
 
-The `model` function retrieves and caches logistic regression text classification models that are stored in a SolrCloud collection.
-The `model` function is designed to work with models that are created by the <<train,train function>>, but can also be used to retrieve text classification models trained outside of Solr, as long as they conform to the specified format.
+The `api` function retrieves and caches logistic regression text classification models that are stored in a SolrCloud collection.
+The `api` function is designed to work with models that are created by the <<train,train function>>, but can also be used to retrieve text classification models trained outside of Solr, as long as they conform to the specified format.
 After the model is retrieved it can be used by the xref:stream-decorator-reference.adoc#classify[classify function] to classify documents.
 
 A single model tuple is fetched and returned based on the *id* parameter.
@@ -320,14 +320,14 @@ If more then one iteration of the named model is stored in the index, the highes
 
 === Caching with model
 
-The `model` function has an internal LRU (least-recently-used) cache so models do not have to be retrieved with each invocation of the `model` function.
+The `api` function has an internal LRU (least-recently-used) cache so models do not have to be retrieved with each invocation of the `api` function.
 The time to cache for each model ID can be passed as a parameter to the function call.
 Retrieving a cached model does not reset the time for expiring the model ID in the cache.
 
 === Model Storage
 
 The storage format of the models in Solr is below.
-The `train` function outputs the format below so you only need to know schema details if you plan to use the `model` function with logistic regression models trained outside of Solr.
+The `train` function outputs the format below so you only need to know schema details if you plan to use the `api` function with logistic regression models trained outside of Solr.
 
 * `name_s` (Single value, String, Stored): The name of the model.
 * `iteration_i` (Single value, Integer, Stored): The iteration number of the model.
diff --git a/solr/solrj/build.gradle b/solr/solrj/build.gradle
index b8b6cb97bc5..0ae7888d70f 100644
--- a/solr/solrj/build.gradle
+++ b/solr/solrj/build.gradle
@@ -15,12 +15,22 @@
  * limitations under the License.
  */
 
+plugins {
+  id "org.openapi.generator" version "6.6.0"
+}
 
 apply plugin: 'java-library'
+apply plugin: 'com.diffplug.spotless'
+
+import com.diffplug.gradle.spotless.JavaExtension
+
 
 description = 'Solrj - Solr Java Client'
 
 dependencies {
+  implementation 'com.fasterxml.jackson.core:jackson-databind'
+  implementation 'com.fasterxml.jackson.core:jackson-annotations'
+  implementation project(":solr:api")
 
   implementation 'org.slf4j:slf4j-api'
   runtimeOnly 'org.slf4j:jcl-over-slf4j'
@@ -66,7 +76,6 @@ dependencies {
   testImplementation 'org.hamcrest:hamcrest'
 
   testImplementation 'commons-io:commons-io'
-  testImplementation 'com.fasterxml.jackson.core:jackson-databind'
   testImplementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-cbor'
 
   testImplementation 'org.eclipse.jetty.toolchain:jetty-servlet-api'
@@ -89,3 +98,73 @@ dependencies {
   testImplementation 'org.apache.commons:commons-lang3'
   testImplementation 'io.dropwizard.metrics:metrics-core'
 }
+
+/**
+ * Java Code Generation for Solr API
+ */
+
+evaluationDependsOn(":solr:api")
+
+configurations {
+  openApiSpecFile {
+    canBeConsumed = false
+  }
+}
+
+ext {
+  generatedCodeDir = "${buildDir}/generated/"
+  javaTemplateDir = "${projectDir}/src/resources/java-template"
+}
+
+dependencies {
+  openApiSpecFile(project(path: ":solr:api", configuration: "openapiSpec"))
+}
+
+/**
+ * Code Generation task
+ */
+openApiGenerate {
+  generatorName = "java"
+  inputSpec = project(":solr:api").openApiSpecFile
+
+  // Add 'debugModels: ""' or 'debugOperations: ""' to get the JSON input to mustache templating for those components
+  globalProperties.set([apis: "", models: "false"])
+  templateDir = project.javaTemplateDir
+  apiPackage = "org.apache.solr.client.solrj.request"
+  outputDir = project.generatedCodeDir
+  generateApiTests = false
+  generateModelTests = false
+  generateApiDocumentation = false
+  generateModelDocumentation = false
+  additionalProperties = ["modelPackage": "org.apache.solr.client.api.model"]
+}
+
+tasks.openApiGenerate.dependsOn configurations.openApiSpecFile
+
+def generatedFiles = files("${project.generatedCodeDir}/src/main/java") {
+  builtBy tasks.openApiGenerate
+}
+
+/**
+ * Setup Spotless (Code formatting) for the generated java files
+ */
+def generatedExt = new JavaExtension(spotless)
+project.spotlessJavaSetup.execute(generatedExt)
+generatedExt.target(generatedFiles)
+def generatedSpotlessTask = generatedExt.createIndependentApplyTask("generatedSpotless")
+generatedSpotlessTask.group("build")
+generatedSpotlessTask.description("Apply formatting for generated code")
+
+tasks.openApiGenerate.finalizedBy generatedSpotlessTask
+
+/**
+ * Add the Generated code to the SolrJ Source paths
+ */
+
+sourceSets {
+  main {
+    java {
+      srcDir generatedFiles
+    }
+  }
+}
diff --git a/solr/core/src/java/org/apache/solr/cli/VersionTool.java b/solr/solrj/src/java/org/apache/solr/client/solrj/JacksonContentWriter.java
similarity index 54%
copy from solr/core/src/java/org/apache/solr/cli/VersionTool.java
copy to solr/solrj/src/java/org/apache/solr/client/solrj/JacksonContentWriter.java
index 1b0e1fa4644..8ba0c29fa6b 100644
--- a/solr/core/src/java/org/apache/solr/cli/VersionTool.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/JacksonContentWriter.java
@@ -15,28 +15,32 @@
  * limitations under the License.
  */
 
-package org.apache.solr.cli;
+package org.apache.solr.client.solrj;
 
-import java.util.Collections;
-import java.util.List;
-import org.apache.commons.cli.CommandLine;
-import org.apache.commons.cli.Option;
-import org.apache.solr.util.SolrVersion;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.io.IOException;
+import java.io.OutputStream;
+import org.apache.solr.client.solrj.request.RequestWriter;
 
-public class VersionTool extends ToolBase {
+public class JacksonContentWriter implements RequestWriter.ContentWriter {
 
-  @Override
-  public String getName() {
-    return "version";
+  public static final ObjectMapper DEFAULT_MAPPER = new ObjectMapper();
+
+  private final Object toWrite;
+  private final String contentType;
+
+  public JacksonContentWriter(String contentType, Object toWrite) {
+    this.contentType = contentType;
+    this.toWrite = toWrite;
   }
 
   @Override
-  public List<Option> getOptions() {
-    return Collections.emptyList();
+  public void write(OutputStream os) throws IOException {
+    DEFAULT_MAPPER.writeValue(os, toWrite);
   }
 
   @Override
-  public void runImpl(CommandLine cli) throws Exception {
-    CLIO.out("Solr version is: " + SolrVersion.LATEST);
+  public String getContentType() {
+    return contentType;
   }
 }
diff --git a/solr/core/src/java/org/apache/solr/cli/VersionTool.java b/solr/solrj/src/java/org/apache/solr/client/solrj/JacksonParsingResponse.java
similarity index 50%
copy from solr/core/src/java/org/apache/solr/cli/VersionTool.java
copy to solr/solrj/src/java/org/apache/solr/client/solrj/JacksonParsingResponse.java
index 1b0e1fa4644..3e95da59657 100644
--- a/solr/core/src/java/org/apache/solr/cli/VersionTool.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/JacksonParsingResponse.java
@@ -15,28 +15,30 @@
  * limitations under the License.
  */
 
-package org.apache.solr.cli;
+package org.apache.solr.client.solrj;
 
-import java.util.Collections;
-import java.util.List;
-import org.apache.commons.cli.CommandLine;
-import org.apache.commons.cli.Option;
-import org.apache.solr.util.SolrVersion;
+import java.io.InputStream;
+import org.apache.solr.client.solrj.response.SimpleSolrResponse;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.common.util.ObjectReleaseTracker;
 
-public class VersionTool extends ToolBase {
+public class JacksonParsingResponse<T> extends SimpleSolrResponse {
 
-  @Override
-  public String getName() {
-    return "version";
-  }
+  private final Class<T> typeParam;
 
-  @Override
-  public List<Option> getOptions() {
-    return Collections.emptyList();
+  public JacksonParsingResponse(Class<T> typeParam) {
+    this.typeParam = typeParam;
   }
 
-  @Override
-  public void runImpl(CommandLine cli) throws Exception {
-    CLIO.out("Solr version is: " + SolrVersion.LATEST);
+  public T getParsed() {
+    final NamedList<Object> resp = getResponse();
+    final var stream = (InputStream) resp.get("stream");
+    try {
+      final T parsedVal = JacksonContentWriter.DEFAULT_MAPPER.readValue(stream, typeParam);
+      assert ObjectReleaseTracker.release(stream);
+      return parsedVal;
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
   }
 }
diff --git a/solr/core/src/java/org/apache/solr/jersey/ExperimentalResponse.java b/solr/solrj/src/java/org/apache/solr/common/DelegateMapWriter.java
similarity index 50%
copy from solr/core/src/java/org/apache/solr/jersey/ExperimentalResponse.java
copy to solr/solrj/src/java/org/apache/solr/common/DelegateMapWriter.java
index 657e3652064..9fe01b09b14 100644
--- a/solr/core/src/java/org/apache/solr/jersey/ExperimentalResponse.java
+++ b/solr/solrj/src/java/org/apache/solr/common/DelegateMapWriter.java
@@ -15,16 +15,32 @@
  * limitations under the License.
  */
 
-package org.apache.solr.jersey;
+package org.apache.solr.common;
 
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
 import com.fasterxml.jackson.annotation.JsonProperty;
+import java.io.IOException;
+import org.apache.solr.common.util.Utils;
 
-/**
- * {@link SolrJerseyResponse} implementation with a warning field indicating that the format may
- * change
- */
-public class ExperimentalResponse extends SolrJerseyResponse {
-  @JsonProperty("WARNING")
-  public String warning =
-      "This response format is experimental.  It is likely to change in the future.";
+public class DelegateMapWriter implements MapWriter {
+
+  private final Object delegate;
+
+  public DelegateMapWriter(Object delegate) {
+    this.delegate = delegate;
+  }
+
+  @Override
+  public void writeMap(EntryWriter ew) throws IOException {
+    Utils.reflectWrite(
+        ew,
+        delegate,
+        // TODO Should we be lenient here and accept both the Jackson and our homegrown annotation?
+        field -> field.getAnnotation(JsonProperty.class) != null,
+        JsonAnyGetter.class,
+        field -> {
+          final JsonProperty prop = field.getAnnotation(JsonProperty.class);
+          return prop.value().isEmpty() ? field.getName() : prop.value();
+        });
+  }
 }
diff --git a/solr/solrj/src/java/org/apache/solr/common/SolrException.java b/solr/solrj/src/java/org/apache/solr/common/SolrException.java
index 2b6fae56063..3093b835874 100644
--- a/solr/solrj/src/java/org/apache/solr/common/SolrException.java
+++ b/solr/solrj/src/java/org/apache/solr/common/SolrException.java
@@ -16,6 +16,9 @@
  */
 package org.apache.solr.common;
 
+import static org.apache.solr.client.api.model.ErrorInfo.ERROR_CLASS;
+import static org.apache.solr.client.api.model.ErrorInfo.ROOT_ERROR_CLASS;
+
 import java.util.Map;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.SuppressForbidden;
@@ -25,8 +28,6 @@ import org.slf4j.MDC;
 /** */
 public class SolrException extends RuntimeException {
 
-  public static final String ROOT_ERROR_CLASS = "root-error-class";
-  public static final String ERROR_CLASS = "error-class";
   private final Map<String, String> mdcContext;
 
   /**
diff --git a/solr/solrj/src/java/org/apache/solr/common/util/JavaBinCodec.java b/solr/solrj/src/java/org/apache/solr/common/util/JavaBinCodec.java
index aaf955b0aac..488aa8e8427 100644
--- a/solr/solrj/src/java/org/apache/solr/common/util/JavaBinCodec.java
+++ b/solr/solrj/src/java/org/apache/solr/common/util/JavaBinCodec.java
@@ -42,7 +42,9 @@ import java.util.concurrent.atomic.LongAdder;
 import java.util.function.BiConsumer;
 import java.util.function.Function;
 import java.util.function.Predicate;
+import org.apache.solr.client.api.util.ReflectWritable;
 import org.apache.solr.common.ConditionalKeyMapWriter;
+import org.apache.solr.common.DelegateMapWriter;
 import org.apache.solr.common.EnumFieldValue;
 import org.apache.solr.common.IteratorWriter;
 import org.apache.solr.common.IteratorWriter.ItemWriter;
@@ -391,6 +393,10 @@ public class JavaBinCodec implements PushWriter {
       writeMap((MapWriter) val);
       return true;
     }
+    if (val instanceof ReflectWritable) {
+      writeMap(new DelegateMapWriter(val));
+      return true;
+    }
     if (val instanceof Map) {
       writeMap((Map) val);
       return true;
diff --git a/solr/solrj/src/java/org/apache/solr/common/util/TextWriter.java b/solr/solrj/src/java/org/apache/solr/common/util/TextWriter.java
index 3ec8df2168b..c42240aeed5 100644
--- a/solr/solrj/src/java/org/apache/solr/common/util/TextWriter.java
+++ b/solr/solrj/src/java/org/apache/solr/common/util/TextWriter.java
@@ -34,6 +34,8 @@ import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.concurrent.atomic.LongAccumulator;
 import java.util.concurrent.atomic.LongAdder;
+import org.apache.solr.client.api.util.ReflectWritable;
+import org.apache.solr.common.DelegateMapWriter;
 import org.apache.solr.common.EnumFieldValue;
 import org.apache.solr.common.IteratorWriter;
 import org.apache.solr.common.MapSerializable;
@@ -85,6 +87,8 @@ public interface TextWriter extends PushWriter {
       writeIterator(name, (IteratorWriter) val, raw);
     } else if (val instanceof MapWriter) {
       writeMap(name, (MapWriter) val);
+    } else if (val instanceof ReflectWritable) {
+      writeMap(name, new DelegateMapWriter(val));
     } else if (val instanceof MapSerializable) {
       // todo find a better way to reuse the map more efficiently
       writeMap(name, ((MapSerializable) val).toMap(new LinkedHashMap<>()), false, true);
diff --git a/solr/solrj/src/resources/java-template/api.mustache b/solr/solrj/src/resources/java-template/api.mustache
new file mode 100644
index 00000000000..872e16f509d
--- /dev/null
+++ b/solr/solrj/src/resources/java-template/api.mustache
@@ -0,0 +1,152 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package {{package}};
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.solr.client.solrj.SolrClient;
+import org.apache.solr.client.solrj.SolrRequest;
+import org.apache.solr.client.solrj.response.SimpleSolrResponse;
+import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.client.solrj.JacksonParsingResponse;
+import org.apache.solr.client.solrj.JacksonContentWriter;
+import org.apache.solr.client.solrj.request.RequestWriter.ContentWriter;
+import org.apache.solr.client.solrj.impl.InputStreamResponseParser;
+import org.apache.solr.client.solrj.ResponseParser;
+{{#operations}}
+{{#operation}}
+{{#imports}}
+import {{modelPackage}}.{{.}};
+{{/imports}}
+{{/operation}}
+
+// WARNING: This class is generated from a Mustache template; any intended
+// changes should be made to the underlying template and not this file directly.
+
+public class {{classname}} {
+
+    {{#operation}}
+        public static class {{operationIdCamelCase}}Response extends JacksonParsingResponse<{{returnType}}> {
+          public {{operationIdCamelCase}}Response() {
+            super({{returnType}}.class);
+          }
+        }
+
+        public static class {{operationIdCamelCase}} extends SolrRequest<{{operationIdCamelCase}}Response> {
+            {{#requiredParams}}
+            {{#isBodyParam}}
+            private final {{{dataType}}} requestBody;
+            {{/isBodyParam}}
+            {{^isBodyParam}}
+            private final {{{dataType}}} {{paramName}};
+            {{/isBodyParam}}
+            {{/requiredParams}}
+            {{#optionalParams}}
+            private {{{dataType}}} {{paramName}};
+            {{/optionalParams}}
+
+            /**
+             * Create a {{operationIdCamelCase}} request object.
+             *
+             {{#requiredParams}}{{^isBodyParam}}* @param {{paramName}} Path param - {{description}}{{/isBodyParam}}
+             {{/requiredParams}}
+             */
+            public {{operationIdCamelCase}}({{#requiredParams}}{{^isBodyParam}}{{^-first}}, {{/-first}}{{{dataType}}} {{paramName}}{{/isBodyParam}}{{/requiredParams}}) {
+                super(
+                  SolrRequest.METHOD.valueOf("{{httpMethod}}"),
+                  "{{{path}}}"{{#pathParams}}
+                    .replace("{" + "{{baseName}}" + "}", {{paramName}}){{/pathParams}}
+                );
+
+                {{#requiredParams}}
+                {{^isBodyParam}}
+                    this.{{paramName}} = {{paramName}};
+                {{/isBodyParam}}
+                {{#isBodyParam}}
+                    this.requestBody = new {{baseName}}();
+                    addHeader("Content-type", "application/json");
+                {{/isBodyParam}}
+                {{/requiredParams}}
+            }
+
+            {{#optionalParams}}
+            {{#description}}
+            /**
+             * @param {{paramName}} {{description}}
+             */
+            {{/description}}
+            public void {{schema.setter}}({{dataType}} {{paramName}}) {
+                this.{{paramName}} = {{paramName}};
+            }
+
+            {{/optionalParams}}
+
+            {{#bodyParam}}
+            {{#vars}}
+            // TODO find a way to add required parameters in the request body to the class constructor
+            {{#description}}
+            /**
+             * @param {{baseName}} {{description}}
+             */
+             {{/description}}
+             public void {{setter}}({{dataType}} {{baseName}}) {
+               this.requestBody.{{baseName}} = {{baseName}};
+            }
+            {{/vars}}
+
+            @Override
+            public RequestWriter.ContentWriter getContentWriter(String expectedType) {
+                return new JacksonContentWriter(expectedType, requestBody);
+            }
+            {{/bodyParam}}
+
+            // TODO Hardcode this for now, but in reality we'll want to parse this out of the Operation data somehow
+            @Override
+            public String getRequestType() {
+              return SolrRequestType.ADMIN.toString();
+            }
+
+            @Override
+            public SolrParams getParams() {
+              final ModifiableSolrParams params = new ModifiableSolrParams();
+              {{#queryParams}}
+              if ({{paramName}} != null) {
+                  params.add("{{baseName}}", {{paramName}}{{^isString}}.toString(){{/isString}});
+              }
+              {{/queryParams}}
+              return params;
+            }
+
+            @Override
+            protected {{operationIdCamelCase}}Response createResponse(SolrClient client) {
+              return new {{operationIdCamelCase}}Response();
+            }
+
+            @Override
+            public ResponseParser getResponseParser() {
+              return new InputStreamResponseParser("json");
+            }
+        }
+    {{/operation}}
+}
+{{/operations}}