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 d06d5faa1f815acae7281550c1fa6e5f856c480e
Author: Jason Gerlowski <[email protected]>
AuthorDate: Tue Oct 3 14:38:47 2023 -0400

    SOLR-16825: Migrate v2 definitions to 'api' module, pt 4  (#1960)
    
    This commit covers the create APIs for collections, shards and replicas.
    It also covers the collection and core level "install-shard-data" APIs.
    
    Extracting annotated interfaces for these APIs includes them in the 
SolrRequest-
    generation we now do in SolrJ
---
 .../client/api/endpoint/CreateCollectionApi.java   |  34 +++
 .../solr/client/api/endpoint/CreateReplicaApi.java |  44 +++
 .../solr/client/api/endpoint/CreateShardApi.java   |  37 +++
 .../client/api/endpoint/InstallCoreDataApi.java    |  42 +++
 .../client/api/endpoint/InstallShardDataApi.java   |  38 +++
 .../api/model/CreateCollectionRequestBody.java     |  63 +++++
 .../model/CreateCollectionRouterProperties.java    |  25 ++
 .../client/api/model/CreateReplicaRequestBody.java |  48 ++++
 .../client/api/model/CreateShardRequestBody.java   |  50 ++++
 .../api/model/InstallCoreDataRequestBody.java      |  30 ++
 .../api/model/InstallShardDataRequestBody.java     |  28 ++
 .../solr/cloud/api/collections/AliasCmd.java       |   9 +-
 .../solr/handler/admin/CollectionsHandler.java     |  37 ++-
 .../solr/handler/admin/CoreAdminHandler.java       |   4 +-
 .../solr/handler/admin/InstallCoreDataOp.java      |  12 +-
 .../solr/handler/admin/api/CreateAliasAPI.java     |  12 +-
 ...ateCollectionAPI.java => CreateCollection.java} | 303 ++++++++-------------
 .../admin/api/CreateCollectionBackupAPI.java       |   2 +-
 .../{CreateReplicaAPI.java => CreateReplica.java}  | 108 +++-----
 .../api/{CreateShardAPI.java => CreateShard.java}  | 113 +++-----
 ...nstallCoreDataAPI.java => InstallCoreData.java} |  41 +--
 ...tallShardDataAPI.java => InstallShardData.java} |  45 +--
 .../handler/admin/api/RestoreCollectionAPI.java    |  11 +-
 .../solr/jersey/PostRequestLoggingFilter.java      |  27 +-
 .../solr/handler/admin/api/CreateAliasAPITest.java |  15 +-
 .../handler/admin/api/CreateCollectionAPITest.java |  34 +--
 .../handler/admin/api/CreateReplicaAPITest.java    |  23 +-
 .../solr/handler/admin/api/CreateShardAPITest.java |  27 +-
 .../admin/api/RestoreCollectionAPITest.java        |   3 +-
 .../solr/jersey/PostRequestLoggingFilterTest.java  |   6 +-
 .../org/apache/solr/gcs/GCSInstallShardTest.java   |   3 +-
 .../org/apache/solr/s3/S3InstallShardTest.java     |   3 +-
 .../solrj/src/resources/java-template/api.mustache |  19 +-
 .../api/collections/AbstractInstallShardTest.java  |   3 +-
 34 files changed, 779 insertions(+), 520 deletions(-)

diff --git 
a/solr/api/src/java/org/apache/solr/client/api/endpoint/CreateCollectionApi.java
 
b/solr/api/src/java/org/apache/solr/client/api/endpoint/CreateCollectionApi.java
new file mode 100644
index 00000000000..748b27972e6
--- /dev/null
+++ 
b/solr/api/src/java/org/apache/solr/client/api/endpoint/CreateCollectionApi.java
@@ -0,0 +1,34 @@
+/*
+ * 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 io.swagger.v3.oas.annotations.Operation;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import org.apache.solr.client.api.model.CreateCollectionRequestBody;
+import org.apache.solr.client.api.model.SubResponseAccumulatingJerseyResponse;
+
+/** V2 API definition for creating a SolrCloud collection */
+@Path("/collections")
+public interface CreateCollectionApi {
+  @POST
+  @Operation(
+      summary = "Creates a new SolrCloud collection.",
+      tags = {"collections"})
+  SubResponseAccumulatingJerseyResponse 
createCollection(CreateCollectionRequestBody requestBody)
+      throws Exception;
+}
diff --git 
a/solr/api/src/java/org/apache/solr/client/api/endpoint/CreateReplicaApi.java 
b/solr/api/src/java/org/apache/solr/client/api/endpoint/CreateReplicaApi.java
new file mode 100644
index 00000000000..21c26ea8f49
--- /dev/null
+++ 
b/solr/api/src/java/org/apache/solr/client/api/endpoint/CreateReplicaApi.java
@@ -0,0 +1,44 @@
+/*
+ * 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 io.swagger.v3.oas.annotations.Operation;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import org.apache.solr.client.api.model.CreateReplicaRequestBody;
+import org.apache.solr.client.api.model.SubResponseAccumulatingJerseyResponse;
+
+/**
+ * V2 API definition for adding a new replica to an existing shard.
+ *
+ * <p>This API (POST /v2/collections/cName/shards/sName/replicas {...}) is 
analogous to the v1
+ * /admin/collections?action=ADDREPLICA command.
+ */
+@Path("/collections/{collectionName}/shards/{shardName}/replicas")
+public interface CreateReplicaApi {
+
+  @POST
+  @Operation(
+      summary = "Creates a new replica of an existing shard.",
+      tags = {"replicas"})
+  SubResponseAccumulatingJerseyResponse createReplica(
+      @PathParam("collectionName") String collectionName,
+      @PathParam("shardName") String shardName,
+      CreateReplicaRequestBody requestBody)
+      throws Exception;
+}
diff --git 
a/solr/api/src/java/org/apache/solr/client/api/endpoint/CreateShardApi.java 
b/solr/api/src/java/org/apache/solr/client/api/endpoint/CreateShardApi.java
new file mode 100644
index 00000000000..0bd30763811
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/CreateShardApi.java
@@ -0,0 +1,37 @@
+/*
+ * 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 io.swagger.v3.oas.annotations.Operation;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import org.apache.solr.client.api.model.CreateShardRequestBody;
+import org.apache.solr.client.api.model.SubResponseAccumulatingJerseyResponse;
+
+/** V2 API definition for creating a new shard in a collection. */
+@Path("/collections/{collectionName}/shards")
+public interface CreateShardApi {
+
+  @POST
+  @Operation(
+      summary = "Create a new shard in an existing collection",
+      tags = {"shards"})
+  SubResponseAccumulatingJerseyResponse createShard(
+      @PathParam("collectionName") String collectionName, 
CreateShardRequestBody requestBody)
+      throws Exception;
+}
diff --git 
a/solr/api/src/java/org/apache/solr/client/api/endpoint/InstallCoreDataApi.java 
b/solr/api/src/java/org/apache/solr/client/api/endpoint/InstallCoreDataApi.java
new file mode 100644
index 00000000000..4ee95539571
--- /dev/null
+++ 
b/solr/api/src/java/org/apache/solr/client/api/endpoint/InstallCoreDataApi.java
@@ -0,0 +1,42 @@
+/*
+ * 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 io.swagger.v3.oas.annotations.Operation;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import org.apache.solr.client.api.model.InstallCoreDataRequestBody;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
+
+/**
+ * V2 API definition for installing an offline index to a single core of a 
shard.
+ *
+ * <p>This is an internal API intended for use only by the Collection Admin 
"Install Shard Data"
+ * API.
+ */
+@Path("/cores/{coreName}/install")
+public interface InstallCoreDataApi {
+
+  @POST
+  @Operation(
+      summary = "Install an offline index to a specified core",
+      tags = {"cores"})
+  SolrJerseyResponse installCoreData(
+      @PathParam("coreName") String coreName, InstallCoreDataRequestBody 
requestBody)
+      throws Exception;
+}
diff --git 
a/solr/api/src/java/org/apache/solr/client/api/endpoint/InstallShardDataApi.java
 
b/solr/api/src/java/org/apache/solr/client/api/endpoint/InstallShardDataApi.java
new file mode 100644
index 00000000000..65648ad9117
--- /dev/null
+++ 
b/solr/api/src/java/org/apache/solr/client/api/endpoint/InstallShardDataApi.java
@@ -0,0 +1,38 @@
+/*
+ * 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 io.swagger.v3.oas.annotations.Operation;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import org.apache.solr.client.api.model.InstallShardDataRequestBody;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
+
+/** V2 API definition allowing users to import offline-constructed index into 
a shard. */
+@Path("/collections/{collName}/shards/{shardName}/install")
+public interface InstallShardDataApi {
+  @POST
+  @Operation(
+      summary = "Install offline index into an existing shard",
+      tags = {"shards"})
+  SolrJerseyResponse installShardData(
+      @PathParam("collName") String collName,
+      @PathParam("shardName") String shardName,
+      InstallShardDataRequestBody requestBody)
+      throws Exception;
+}
diff --git 
a/solr/api/src/java/org/apache/solr/client/api/model/CreateCollectionRequestBody.java
 
b/solr/api/src/java/org/apache/solr/client/api/model/CreateCollectionRequestBody.java
new file mode 100644
index 00000000000..5881708ee34
--- /dev/null
+++ 
b/solr/api/src/java/org/apache/solr/client/api/model/CreateCollectionRequestBody.java
@@ -0,0 +1,63 @@
+/*
+ * 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 java.util.List;
+import java.util.Map;
+
+/** Request body for v2 "create collection" requests */
+public class CreateCollectionRequestBody {
+  @JsonProperty public String name;
+
+  @JsonProperty public Integer replicationFactor;
+
+  @JsonProperty public String config;
+
+  @JsonProperty public Integer numShards;
+
+  @JsonProperty public List<String> shardNames;
+
+  @JsonProperty public Integer pullReplicas;
+
+  @JsonProperty public Integer tlogReplicas;
+
+  @JsonProperty public Integer nrtReplicas;
+
+  @JsonProperty public Boolean waitForFinalState;
+
+  @JsonProperty public Boolean perReplicaState;
+
+  @JsonProperty public String alias;
+
+  @JsonProperty public Map<String, String> properties;
+
+  @JsonProperty public String async;
+
+  @JsonProperty public CreateCollectionRouterProperties router;
+
+  // Parameters below differ from v1 API
+  // V1 API uses createNodeSet
+  @JsonProperty("nodeSet")
+  public List<String> nodeSet;
+  // v1 API uses createNodeSet=EMPTY
+  @JsonProperty("createReplicas")
+  public Boolean createReplicas;
+  // V1 API uses 'createNodeSet.shuffle'
+  @JsonProperty("shuffleNodes")
+  public Boolean shuffleNodes;
+}
diff --git 
a/solr/api/src/java/org/apache/solr/client/api/model/CreateCollectionRouterProperties.java
 
b/solr/api/src/java/org/apache/solr/client/api/model/CreateCollectionRouterProperties.java
new file mode 100644
index 00000000000..382091ee60c
--- /dev/null
+++ 
b/solr/api/src/java/org/apache/solr/client/api/model/CreateCollectionRouterProperties.java
@@ -0,0 +1,25 @@
+/*
+ * 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;
+
+public class CreateCollectionRouterProperties {
+  @JsonProperty public String name;
+
+  @JsonProperty public String field;
+}
diff --git 
a/solr/api/src/java/org/apache/solr/client/api/model/CreateReplicaRequestBody.java
 
b/solr/api/src/java/org/apache/solr/client/api/model/CreateReplicaRequestBody.java
new file mode 100644
index 00000000000..a669cdaf414
--- /dev/null
+++ 
b/solr/api/src/java/org/apache/solr/client/api/model/CreateReplicaRequestBody.java
@@ -0,0 +1,48 @@
+/*
+ * 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 java.util.List;
+import java.util.Map;
+
+public class CreateReplicaRequestBody {
+  @JsonProperty public String name;
+  @JsonProperty public String type; // TODO Make this an enum - see SOLR-15796
+  @JsonProperty public String instanceDir;
+  @JsonProperty public String dataDir;
+  @JsonProperty public String ulogDir;
+  @JsonProperty public String route;
+  @JsonProperty public Integer nrtReplicas;
+  @JsonProperty public Integer tlogReplicas;
+  @JsonProperty public Integer pullReplicas;
+  @JsonProperty public Boolean waitForFinalState;
+  @JsonProperty public Boolean followAliases;
+
+  @JsonProperty public String async;
+
+  // TODO This cluster of properties could probably be simplified down to just 
"nodeSet".  See
+  // SOLR-15542
+  @JsonProperty public String node;
+
+  @JsonProperty("nodeSet")
+  public List<String> nodeSet;
+
+  @JsonProperty public Boolean skipNodeAssignment;
+
+  @JsonProperty public Map<String, String> properties;
+}
diff --git 
a/solr/api/src/java/org/apache/solr/client/api/model/CreateShardRequestBody.java
 
b/solr/api/src/java/org/apache/solr/client/api/model/CreateShardRequestBody.java
new file mode 100644
index 00000000000..ea871998cca
--- /dev/null
+++ 
b/solr/api/src/java/org/apache/solr/client/api/model/CreateShardRequestBody.java
@@ -0,0 +1,50 @@
+/*
+ * 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 java.util.List;
+import java.util.Map;
+
+public class CreateShardRequestBody {
+  @Schema(name = "shardName")
+  @JsonProperty("name")
+  public String shardName;
+
+  @JsonProperty public Integer replicationFactor;
+
+  @JsonProperty public Integer nrtReplicas;
+
+  @JsonProperty public Integer tlogReplicas;
+
+  @JsonProperty public Integer pullReplicas;
+
+  @JsonProperty("createReplicas")
+  public Boolean createReplicas;
+
+  @JsonProperty("nodeSet")
+  public List<String> nodeSet;
+
+  @JsonProperty public Boolean waitForFinalState;
+
+  @JsonProperty public Boolean followAliases;
+
+  @JsonProperty public String async;
+
+  @JsonProperty public Map<String, String> properties;
+}
diff --git 
a/solr/api/src/java/org/apache/solr/client/api/model/InstallCoreDataRequestBody.java
 
b/solr/api/src/java/org/apache/solr/client/api/model/InstallCoreDataRequestBody.java
new file mode 100644
index 00000000000..2ba9d09c1f6
--- /dev/null
+++ 
b/solr/api/src/java/org/apache/solr/client/api/model/InstallCoreDataRequestBody.java
@@ -0,0 +1,30 @@
+/*
+ * 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;
+
+public class InstallCoreDataRequestBody {
+  // Expected to point to an index directory (e.g. 
data/techproducts_shard1_replica_n1/data/index)
+  // for a single core that has previously been uploaded to the backup 
repository previously
+  // uploaded to the backup repository.
+  @JsonProperty public String location;
+
+  @JsonProperty public String repository;
+
+  @JsonProperty public String asyncId;
+}
diff --git 
a/solr/api/src/java/org/apache/solr/client/api/model/InstallShardDataRequestBody.java
 
b/solr/api/src/java/org/apache/solr/client/api/model/InstallShardDataRequestBody.java
new file mode 100644
index 00000000000..31bec8eb434
--- /dev/null
+++ 
b/solr/api/src/java/org/apache/solr/client/api/model/InstallShardDataRequestBody.java
@@ -0,0 +1,28 @@
+/*
+ * 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;
+
+public class InstallShardDataRequestBody {
+  @JsonProperty(value = "location", required = true)
+  public String location;
+
+  @JsonProperty public String repository;
+
+  @JsonProperty public String async;
+}
diff --git 
a/solr/core/src/java/org/apache/solr/cloud/api/collections/AliasCmd.java 
b/solr/core/src/java/org/apache/solr/cloud/api/collections/AliasCmd.java
index 92b18b53a74..4815a76b807 100644
--- a/solr/core/src/java/org/apache/solr/cloud/api/collections/AliasCmd.java
+++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/AliasCmd.java
@@ -31,7 +31,7 @@ import org.apache.solr.common.cloud.ZkNodeProps;
 import org.apache.solr.common.params.ModifiableSolrParams;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.handler.admin.CollectionsHandler;
-import org.apache.solr.handler.admin.api.CreateCollectionAPI;
+import org.apache.solr.handler.admin.api.CreateCollection;
 
 /**
  * Common superclass for commands that maintain or manipulate aliases. In the 
routed alias parlance,
@@ -73,10 +73,9 @@ abstract class AliasCmd implements 
CollApiCmds.CollectionApiCommand {
     // a CollectionOperation reads params and produces a message (Map) that is 
supposed to be sent
     // to the Overseer. Although we could create the Map without it, there are 
a fair amount of
     // rules we don't want to reproduce.
-    final var createReqBody =
-        
CreateCollectionAPI.CreateCollectionRequestBody.fromV1Params(createReqParams, 
true);
-    CreateCollectionAPI.populateDefaultsIfNecessary(ccc.getCoreContainer(), 
createReqBody);
-    final ZkNodeProps createMessage = 
CreateCollectionAPI.createRemoteMessage(createReqBody);
+    final var createReqBody = 
CreateCollection.createRequestBodyFromV1Params(createReqParams, true);
+    CreateCollection.populateDefaultsIfNecessary(ccc.getCoreContainer(), 
createReqBody);
+    final ZkNodeProps createMessage = 
CreateCollection.createRemoteMessage(createReqBody);
 
     NamedList<Object> results = new NamedList<>();
     try {
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 97de7a36bb3..d91f4e1914b 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
@@ -123,6 +123,7 @@ 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.InstallShardDataRequestBody;
 import org.apache.solr.client.api.model.ReplaceNodeRequestBody;
 import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.client.api.model.UpdateAliasPropertiesRequestBody;
@@ -172,11 +173,11 @@ import 
org.apache.solr.handler.admin.api.BalanceShardUniqueAPI;
 import org.apache.solr.handler.admin.api.CollectionPropertyAPI;
 import org.apache.solr.handler.admin.api.CollectionStatusAPI;
 import org.apache.solr.handler.admin.api.CreateAliasAPI;
-import org.apache.solr.handler.admin.api.CreateCollectionAPI;
+import org.apache.solr.handler.admin.api.CreateCollection;
 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.CreateReplica;
+import org.apache.solr.handler.admin.api.CreateShard;
 import org.apache.solr.handler.admin.api.DeleteAlias;
 import org.apache.solr.handler.admin.api.DeleteCollection;
 import org.apache.solr.handler.admin.api.DeleteCollectionBackup;
@@ -186,7 +187,7 @@ import org.apache.solr.handler.admin.api.DeleteReplica;
 import org.apache.solr.handler.admin.api.DeleteReplicaProperty;
 import org.apache.solr.handler.admin.api.DeleteShardAPI;
 import org.apache.solr.handler.admin.api.ForceLeader;
-import org.apache.solr.handler.admin.api.InstallShardDataAPI;
+import org.apache.solr.handler.admin.api.InstallShardData;
 import org.apache.solr.handler.admin.api.ListAliases;
 import org.apache.solr.handler.admin.api.ListCollectionBackups;
 import org.apache.solr.handler.admin.api.ListCollectionSnapshotsAPI;
@@ -507,9 +508,9 @@ public class CollectionsHandler extends RequestHandlerBase 
implements Permission
     CREATE_OP(
         CREATE,
         (req, rsp, h) -> {
-          final CreateCollectionAPI.CreateCollectionRequestBody requestBody =
-              
CreateCollectionAPI.CreateCollectionRequestBody.fromV1Params(req.getParams(), 
true);
-          final CreateCollectionAPI createApi = new 
CreateCollectionAPI(h.coreContainer, req, rsp);
+          final var requestBody =
+              CreateCollection.createRequestBodyFromV1Params(req.getParams(), 
true);
+          final CreateCollection createApi = new 
CreateCollection(h.coreContainer, req, rsp);
           final SolrJerseyResponse response = 
createApi.createCollection(requestBody);
 
           // 'rsp' may be null, as when overseer commands execute 
CollectionAction impl's directly.
@@ -729,7 +730,7 @@ public class CollectionsHandler extends RequestHandlerBase 
implements Permission
     CREATESHARD_OP(
         CREATESHARD,
         (req, rsp, h) -> {
-          CreateShardAPI.invokeFromV1Params(h.coreContainer, req, rsp);
+          CreateShard.invokeFromV1Params(h.coreContainer, req, rsp);
           return null;
         }),
     DELETEREPLICA_OP(
@@ -942,9 +943,8 @@ public class CollectionsHandler extends RequestHandlerBase 
implements Permission
           final var params = req.getParams();
           params.required().check(COLLECTION_PROP, SHARD_ID_PROP);
 
-          final var api = new CreateReplicaAPI(h.coreContainer, req, rsp);
-          final var requestBody =
-              
CreateReplicaAPI.AddReplicaRequestBody.fromV1Params(req.getParams());
+          final var api = new CreateReplica(h.coreContainer, req, rsp);
+          final var requestBody = 
CreateReplica.createRequestBodyFromV1Params(req.getParams());
           final var response =
               api.createReplica(
                   params.get(COLLECTION_PROP), params.get(SHARD_ID_PROP), 
requestBody);
@@ -1081,13 +1081,12 @@ public class CollectionsHandler extends 
RequestHandlerBase implements Permission
           req.getParams().required().check(COLLECTION, SHARD);
           final String collectionName = req.getParams().get(COLLECTION);
           final String shardName = req.getParams().get(SHARD);
-          final InstallShardDataAPI.InstallShardRequestBody reqBody =
-              new InstallShardDataAPI.InstallShardRequestBody();
-          reqBody.asyncId = req.getParams().get(ASYNC);
+          final InstallShardDataRequestBody reqBody = new 
InstallShardDataRequestBody();
+          reqBody.async = req.getParams().get(ASYNC);
           reqBody.repository = req.getParams().get(BACKUP_REPOSITORY);
           reqBody.location = req.getParams().get(BACKUP_LOCATION);
 
-          final InstallShardDataAPI installApi = new 
InstallShardDataAPI(h.coreContainer, req, rsp);
+          final InstallShardData installApi = new 
InstallShardData(h.coreContainer, req, rsp);
           final SolrJerseyResponse installResponse =
               installApi.installShardData(collectionName, shardName, reqBody);
           V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, installResponse);
@@ -1362,13 +1361,13 @@ public class CollectionsHandler extends 
RequestHandlerBase implements Permission
   @Override
   public Collection<Class<? extends JerseyResource>> getJerseyResources() {
     return List.of(
-        CreateReplicaAPI.class,
+        CreateReplica.class,
         AddReplicaProperty.class,
         BalanceShardUniqueAPI.class,
         CreateAliasAPI.class,
-        CreateCollectionAPI.class,
+        CreateCollection.class,
         CreateCollectionBackupAPI.class,
-        CreateShardAPI.class,
+        CreateShard.class,
         DeleteAlias.class,
         DeleteCollectionBackup.class,
         DeleteCollection.class,
@@ -1376,7 +1375,7 @@ public class CollectionsHandler extends 
RequestHandlerBase implements Permission
         DeleteReplicaProperty.class,
         DeleteShardAPI.class,
         ForceLeader.class,
-        InstallShardDataAPI.class,
+        InstallShardData.class,
         ListCollections.class,
         ListCollectionBackups.class,
         ReloadCollectionAPI.class,
diff --git 
a/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminHandler.java 
b/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminHandler.java
index d928282df5c..d9ece272f75 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/CoreAdminHandler.java
@@ -58,7 +58,7 @@ import org.apache.solr.handler.admin.api.AllCoresStatusAPI;
 import org.apache.solr.handler.admin.api.BackupCoreAPI;
 import org.apache.solr.handler.admin.api.CoreSnapshotAPI;
 import org.apache.solr.handler.admin.api.CreateCoreAPI;
-import org.apache.solr.handler.admin.api.InstallCoreDataAPI;
+import org.apache.solr.handler.admin.api.InstallCoreData;
 import org.apache.solr.handler.admin.api.MergeIndexesAPI;
 import org.apache.solr.handler.admin.api.OverseerOperationAPI;
 import org.apache.solr.handler.admin.api.PrepareCoreRecoveryAPI;
@@ -403,7 +403,7 @@ public class CoreAdminHandler extends RequestHandlerBase 
implements PermissionNa
   public Collection<Class<? extends JerseyResource>> getJerseyResources() {
     return List.of(
         CoreSnapshotAPI.class,
-        InstallCoreDataAPI.class,
+        InstallCoreData.class,
         BackupCoreAPI.class,
         RestoreCoreAPI.class,
         ReloadCoreAPI.class);
diff --git 
a/solr/core/src/java/org/apache/solr/handler/admin/InstallCoreDataOp.java 
b/solr/core/src/java/org/apache/solr/handler/admin/InstallCoreDataOp.java
index c115739f6a8..7d7ada266a3 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/InstallCoreDataOp.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/InstallCoreDataOp.java
@@ -21,9 +21,10 @@ import static 
org.apache.solr.common.params.CommonAdminParams.ASYNC;
 import static org.apache.solr.common.params.CoreAdminParams.BACKUP_LOCATION;
 import static org.apache.solr.common.params.CoreAdminParams.BACKUP_REPOSITORY;
 
+import org.apache.solr.client.api.model.InstallCoreDataRequestBody;
 import org.apache.solr.common.params.CoreAdminParams;
 import org.apache.solr.common.params.SolrParams;
-import org.apache.solr.handler.admin.api.InstallCoreDataAPI;
+import org.apache.solr.handler.admin.api.InstallCoreData;
 import org.apache.solr.handler.api.V2ApiUtils;
 
 /**
@@ -31,7 +32,7 @@ import org.apache.solr.handler.api.V2ApiUtils;
  * "Install Shard Data" Collection-Admin functionality
  *
  * <p>Converts v1-style query parameters into a v2-style request body and 
delegating to {@link
- * InstallCoreDataAPI}.
+ * InstallCoreData}.
  */
 public class InstallCoreDataOp implements CoreAdminHandler.CoreAdminOp {
   @Override
@@ -39,11 +40,10 @@ public class InstallCoreDataOp implements 
CoreAdminHandler.CoreAdminOp {
     final SolrParams params = it.req.getParams();
     final String coreName = params.required().get(CoreAdminParams.CORE);
 
-    final InstallCoreDataAPI api =
-        new InstallCoreDataAPI(
+    final InstallCoreData api =
+        new InstallCoreData(
             it.handler.getCoreContainer(), 
it.handler.getCoreAdminAsyncTracker(), it.req, it.rsp);
-    final InstallCoreDataAPI.InstallCoreDataRequestBody requestBody =
-        new InstallCoreDataAPI.InstallCoreDataRequestBody();
+    final InstallCoreDataRequestBody requestBody = new 
InstallCoreDataRequestBody();
     requestBody.repository = params.get(BACKUP_REPOSITORY);
     requestBody.location = params.get(BACKUP_LOCATION);
     requestBody.asyncId = params.get(ASYNC);
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 ad3f300b7c9..dfc09e917c1 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,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.CreateCollectionRequestBody;
 import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.client.api.model.SubResponseAccumulatingJerseyResponse;
 import org.apache.solr.client.solrj.RoutedAliasTypes;
@@ -159,8 +160,8 @@ public class CreateAliasAPI extends AdminAPIBase {
     }
 
     if (requestBody.collCreationParameters != null) {
-      requestBody.collCreationParameters.addToRemoteMessageWithPrefix(
-          remoteMessage, "create-collection.");
+      CreateCollection.addToRemoteMessageWithPrefix(
+          requestBody.collCreationParameters, remoteMessage, 
"create-collection.");
     }
     return new ZkNodeProps(remoteMessage);
   }
@@ -203,7 +204,7 @@ public class CreateAliasAPI extends AdminAPIBase {
     final SolrParams createCollectionParams =
         getHierarchicalParametersByPrefix(params, CREATE_COLLECTION_PREFIX);
     createBody.collCreationParameters =
-        
CreateCollectionAPI.CreateCollectionRequestBody.fromV1Params(createCollectionParams,
 false);
+        CreateCollection.createRequestBodyFromV1Params(createCollectionParams, 
false);
 
     return createBody;
   }
@@ -239,7 +240,7 @@ public class CreateAliasAPI extends AdminAPIBase {
     public List<RoutedAliasProperties> routers;
 
     @JsonProperty("create-collection")
-    public CreateCollectionAPI.CreateCollectionRequestBody 
collCreationParameters;
+    public CreateCollectionRequestBody collCreationParameters;
 
     public void validate() {
       SolrIdentifierValidator.validateAliasName(name);
@@ -257,8 +258,7 @@ public class CreateAliasAPI extends AdminAPIBase {
               BAD_REQUEST, "Collections cannot be specified when creating a 
routed alias.");
         }
 
-        final CreateCollectionAPI.CreateCollectionRequestBody 
createCollReqBody =
-            collCreationParameters;
+        final var createCollReqBody = collCreationParameters;
         if (createCollReqBody != null) {
           if (createCollReqBody.name != null) {
             throw new SolrException(
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/CreateCollection.java
similarity index 62%
rename from 
solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionAPI.java
rename to 
solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollection.java
index b8ae2dca46f..8ce1eca623e 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/CreateCollection.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.client.solrj.request.beans.V2ApiConstants.ROUTER_KEY;
 import static 
org.apache.solr.client.solrj.request.beans.V2ApiConstants.SHARD_NAMES;
 import static org.apache.solr.cloud.Overseer.QUEUE_OPERATION;
@@ -36,15 +35,12 @@ import static 
org.apache.solr.common.params.CollectionAdminParams.REPLICATION_FA
 import static 
org.apache.solr.common.params.CollectionAdminParams.TLOG_REPLICAS;
 import static org.apache.solr.common.params.CommonAdminParams.ASYNC;
 import static 
org.apache.solr.common.params.CommonAdminParams.WAIT_FOR_FINAL_STATE;
-import static org.apache.solr.common.params.CoreAdminParams.CONFIG;
 import static org.apache.solr.common.params.CoreAdminParams.NAME;
 import static 
org.apache.solr.handler.admin.CollectionsHandler.DEFAULT_COLLECTION_OP_TIMEOUT;
 import static 
org.apache.solr.handler.admin.CollectionsHandler.waitForActiveCollection;
 import static org.apache.solr.handler.api.V2ApiUtils.flattenMapWithPrefix;
-import static org.apache.solr.schema.IndexSchema.FIELD;
 import static 
org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM;
 
-import com.fasterxml.jackson.annotation.JsonProperty;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
@@ -57,10 +53,9 @@ import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
 import javax.inject.Inject;
-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.endpoint.CreateCollectionApi;
+import org.apache.solr.client.api.model.CreateCollectionRequestBody;
+import org.apache.solr.client.api.model.CreateCollectionRouterProperties;
 import org.apache.solr.client.api.model.SubResponseAccumulatingJerseyResponse;
 import org.apache.solr.client.solrj.SolrResponse;
 import org.apache.solr.client.solrj.request.beans.V2ApiConstants;
@@ -76,9 +71,9 @@ import org.apache.solr.common.params.CollectionParams;
 import org.apache.solr.common.params.CommonParams;
 import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.common.util.CollectionUtil;
+import org.apache.solr.common.util.Utils;
 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.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
@@ -90,19 +85,17 @@ import org.apache.zookeeper.KeeperException;
  *
  * <p>This API is analogous to the v1 /admin/collections?action=CREATE command.
  */
-@Path("/collections")
-public class CreateCollectionAPI extends AdminAPIBase {
+public class CreateCollection extends AdminAPIBase implements 
CreateCollectionApi {
 
   @Inject
-  public CreateCollectionAPI(
+  public CreateCollection(
       CoreContainer coreContainer,
       SolrQueryRequest solrQueryRequest,
       SolrQueryResponse solrQueryResponse) {
     super(coreContainer, solrQueryRequest, solrQueryResponse);
   }
 
-  @POST
-  @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, 
BINARY_CONTENT_TYPE_V2})
+  @Override
   @PermissionName(COLL_EDIT_PERM)
   public SubResponseAccumulatingJerseyResponse createCollection(
       CreateCollectionRequestBody requestBody) throws Exception {
@@ -123,7 +116,7 @@ public class CreateCollectionAPI extends AdminAPIBase {
       createSysConfigSet(coreContainer);
     }
 
-    requestBody.validate();
+    validateRequestBody(requestBody);
 
     // Populate any 'null' creation parameters that support COLLECTIONPROP 
defaults.
     populateDefaultsIfNecessary(coreContainer, requestBody);
@@ -224,7 +217,7 @@ public class CreateCollectionAPI extends AdminAPIBase {
     }
 
     if (reqBody.router != null) {
-      final RouterProperties routerProps = reqBody.router;
+      final var routerProps = reqBody.router;
       rawProperties.put("router.name", routerProps.name);
       rawProperties.put("router.field", routerProps.field);
     }
@@ -297,185 +290,127 @@ public class CreateCollectionAPI extends AdminAPIBase {
     }
   }
 
-  /** Request body for v2 "create collection" requests */
-  public static class CreateCollectionRequestBody implements 
JacksonReflectMapWriter {
-    @JsonProperty(NAME)
-    public String name;
-
-    @JsonProperty(REPLICATION_FACTOR)
-    public Integer replicationFactor;
-
-    @JsonProperty(CONFIG)
-    public String config;
-
-    @JsonProperty(NUM_SLICES)
-    public Integer numShards;
-
-    @JsonProperty(SHARD_NAMES)
-    public List<String> shardNames;
-
-    @JsonProperty(PULL_REPLICAS)
-    public Integer pullReplicas;
-
-    @JsonProperty(TLOG_REPLICAS)
-    public Integer tlogReplicas;
-
-    @JsonProperty(NRT_REPLICAS)
-    public Integer nrtReplicas;
-
-    @JsonProperty(WAIT_FOR_FINAL_STATE)
-    public Boolean waitForFinalState;
-
-    @JsonProperty(PER_REPLICA_STATE)
-    public Boolean perReplicaState;
-
-    @JsonProperty(ALIAS)
-    public String alias;
-
-    @JsonProperty("properties")
-    public Map<String, String> properties;
-
-    @JsonProperty(ASYNC)
-    public String async;
-
-    @JsonProperty("router")
-    public RouterProperties router;
-
-    // Parameters below differ from v1 API
-    // V1 API uses createNodeSet
-    @JsonProperty("nodeSet")
-    public List<String> nodeSet;
-    // v1 API uses createNodeSet=EMPTY
-    @JsonProperty("createReplicas")
-    public Boolean createReplicas;
-    // V1 API uses 'createNodeSet.shuffle'
-    @JsonProperty("shuffleNodes")
-    public Boolean shuffleNodes;
-
-    public static CreateCollectionRequestBody fromV1Params(
-        SolrParams params, boolean nameRequired) {
-      final var requestBody = new CreateCollectionRequestBody();
-      requestBody.name =
-          nameRequired ? params.required().get(CommonParams.NAME) : 
params.get(CommonParams.NAME);
-      requestBody.replicationFactor = 
params.getInt(ZkStateReader.REPLICATION_FACTOR);
-      requestBody.config = params.get(COLL_CONF);
-      requestBody.numShards = params.getInt(NUM_SLICES);
-      if (params.get(CREATE_NODE_SET) != null) {
-        final String commaDelimNodeSet = params.get(CREATE_NODE_SET);
-        if ("EMPTY".equals(commaDelimNodeSet)) {
-          requestBody.createReplicas = false;
-        } else {
-          requestBody.nodeSet = 
Arrays.asList(params.get(CREATE_NODE_SET).split(","));
-        }
-      }
-      requestBody.shuffleNodes = params.getBool(CREATE_NODE_SET_SHUFFLE);
-      requestBody.shardNames =
-          params.get(SHARDS_PROP) != null
-              ? 
Arrays.stream(params.get(SHARDS_PROP).split(",")).collect(Collectors.toList())
-              : new ArrayList<>();
-      requestBody.tlogReplicas = params.getInt(ZkStateReader.TLOG_REPLICAS);
-      requestBody.pullReplicas = params.getInt(ZkStateReader.PULL_REPLICAS);
-      requestBody.nrtReplicas = params.getInt(ZkStateReader.NRT_REPLICAS);
-      requestBody.waitForFinalState = params.getBool(WAIT_FOR_FINAL_STATE);
-      requestBody.perReplicaState = params.getBool(PER_REPLICA_STATE);
-      requestBody.alias = params.get(ALIAS);
-      requestBody.async = params.get(ASYNC);
-      requestBody.properties =
-          copyPrefixedPropertiesWithoutPrefix(params, new HashMap<>(), 
PROPERTY_PREFIX);
-      if (params.get("router.name") != null || params.get("router.field") != 
null) {
-        final RouterProperties routerProperties = new RouterProperties();
-        routerProperties.name = params.get("router.name");
-        routerProperties.field = params.get("router.field");
-        requestBody.router = routerProperties;
+  public static CreateCollectionRequestBody createRequestBodyFromV1Params(
+      SolrParams params, boolean nameRequired) {
+    final var requestBody = new CreateCollectionRequestBody();
+    requestBody.name =
+        nameRequired ? params.required().get(CommonParams.NAME) : 
params.get(CommonParams.NAME);
+    requestBody.replicationFactor = 
params.getInt(ZkStateReader.REPLICATION_FACTOR);
+    requestBody.config = params.get(COLL_CONF);
+    requestBody.numShards = params.getInt(NUM_SLICES);
+    if (params.get(CREATE_NODE_SET) != null) {
+      final String commaDelimNodeSet = params.get(CREATE_NODE_SET);
+      if ("EMPTY".equals(commaDelimNodeSet)) {
+        requestBody.createReplicas = false;
+      } else {
+        requestBody.nodeSet = 
Arrays.asList(params.get(CREATE_NODE_SET).split(","));
       }
-
-      return requestBody;
+    }
+    requestBody.shuffleNodes = params.getBool(CREATE_NODE_SET_SHUFFLE);
+    requestBody.shardNames =
+        params.get(SHARDS_PROP) != null
+            ? 
Arrays.stream(params.get(SHARDS_PROP).split(",")).collect(Collectors.toList())
+            : new ArrayList<>();
+    requestBody.tlogReplicas = params.getInt(ZkStateReader.TLOG_REPLICAS);
+    requestBody.pullReplicas = params.getInt(ZkStateReader.PULL_REPLICAS);
+    requestBody.nrtReplicas = params.getInt(ZkStateReader.NRT_REPLICAS);
+    requestBody.waitForFinalState = params.getBool(WAIT_FOR_FINAL_STATE);
+    requestBody.perReplicaState = params.getBool(PER_REPLICA_STATE);
+    requestBody.alias = params.get(ALIAS);
+    requestBody.async = params.get(ASYNC);
+    requestBody.properties =
+        copyPrefixedPropertiesWithoutPrefix(params, new HashMap<>(), 
PROPERTY_PREFIX);
+    if (params.get("router.name") != null || params.get("router.field") != 
null) {
+      final var routerProperties = new CreateCollectionRouterProperties();
+      routerProperties.name = params.get("router.name");
+      routerProperties.field = params.get("router.field");
+      requestBody.router = routerProperties;
     }
 
-    public void validate() {
-      if (replicationFactor != null
-          && nrtReplicas != null
-          && (!replicationFactor.equals(nrtReplicas))) {
-        throw new SolrException(
-            SolrException.ErrorCode.BAD_REQUEST,
-            "Cannot specify both replicationFactor and nrtReplicas as they 
mean the same thing");
-      }
-
-      SolrIdentifierValidator.validateCollectionName(name);
+    return requestBody;
+  }
 
-      if (shardNames != null && !shardNames.isEmpty()) {
-        verifyShardsParam(shardNames);
-      }
+  public static void validateRequestBody(CreateCollectionRequestBody 
requestBody) {
+    if (requestBody.replicationFactor != null
+        && requestBody.nrtReplicas != null
+        && (!requestBody.replicationFactor.equals(requestBody.nrtReplicas))) {
+      throw new SolrException(
+          SolrException.ErrorCode.BAD_REQUEST,
+          "Cannot specify both replicationFactor and nrtReplicas as they mean 
the same thing");
     }
 
-    public void addToRemoteMessageWithPrefix(Map<String, Object> 
remoteMessage, String prefix) {
-      final Map<String, Object> v1Params = toMap(new HashMap<>());
-      convertV2CreateCollectionMapToV1ParamMap(v1Params);
-      for (Map.Entry<String, Object> v1Param : v1Params.entrySet()) {
-        remoteMessage.put(prefix + v1Param.getKey(), v1Param.getValue());
-      }
+    SolrIdentifierValidator.validateCollectionName(requestBody.name);
+
+    if (requestBody.shardNames != null && !requestBody.shardNames.isEmpty()) {
+      verifyShardsParam(requestBody.shardNames);
     }
+  }
 
-    /**
-     * Convert a map representing the v2 request body into v1-appropriate 
query-parameters.
-     *
-     * <p>Most v2 APIs using the legacy (i.e. non-JAX-RS) framework implement 
the v2 API by
-     * restructuring the provided parameters so that the v1 codepath can be 
called. This utility
-     * method is provided in pursuit of that usecase. It's not used directly 
CreateCollectionAPI,
-     * which uses the JAX-RS framework, but it's kept here so that logic 
surrounding
-     * collection-creation parameters can be kept in a single place.
-     */
-    @SuppressWarnings("unchecked")
-    public static void convertV2CreateCollectionMapToV1ParamMap(Map<String, 
Object> v2MapVals) {
-      // Keys are copied so that map can be modified as keys are looped 
through.
-      final Set<String> v2Keys = 
v2MapVals.keySet().stream().collect(Collectors.toSet());
-      for (String key : v2Keys) {
-        switch (key) {
-          case V2ApiConstants.PROPERTIES_KEY:
-            final Map<String, Object> propertiesMap =
-                (Map<String, Object>) 
v2MapVals.remove(V2ApiConstants.PROPERTIES_KEY);
-            flattenMapWithPrefix(propertiesMap, v2MapVals, 
CollectionAdminParams.PROPERTY_PREFIX);
-            break;
-          case ROUTER_KEY:
-            final Map<String, Object> routerProperties =
-                (Map<String, Object>) 
v2MapVals.remove(V2ApiConstants.ROUTER_KEY);
-            flattenMapWithPrefix(routerProperties, v2MapVals, 
CollectionAdminParams.ROUTER_PREFIX);
-            break;
-          case V2ApiConstants.CONFIG:
-            v2MapVals.put(CollectionAdminParams.COLL_CONF, 
v2MapVals.remove(V2ApiConstants.CONFIG));
-            break;
-          case SHARD_NAMES:
-            final String shardsValue =
-                String.join(",", (Collection<String>) 
v2MapVals.remove(SHARD_NAMES));
-            v2MapVals.put(SHARDS_PROP, shardsValue);
-            break;
-          case V2ApiConstants.SHUFFLE_NODES:
-            v2MapVals.put(
-                CollectionAdminParams.CREATE_NODE_SET_SHUFFLE_PARAM,
-                v2MapVals.remove(V2ApiConstants.SHUFFLE_NODES));
-            break;
-          case V2ApiConstants.NODE_SET:
-            final Object nodeSetValUncast = 
v2MapVals.remove(V2ApiConstants.NODE_SET);
-            if (nodeSetValUncast instanceof String) {
-              v2MapVals.put(CollectionAdminParams.CREATE_NODE_SET_PARAM, 
nodeSetValUncast);
-            } else {
-              final List<String> nodeSetList = (List<String>) nodeSetValUncast;
-              final String nodeSetStr = String.join(",", nodeSetList);
-              v2MapVals.put(CollectionAdminParams.CREATE_NODE_SET_PARAM, 
nodeSetStr);
-            }
-            break;
-          default:
-            break;
-        }
+  /**
+   * Convert a map representing the v2 request body into v1-appropriate 
query-parameters.
+   *
+   * <p>Most v2 APIs using the legacy (i.e. non-JAX-RS) framework implement 
the v2 API by
+   * restructuring the provided parameters so that the v1 codepath can be 
called. This utility
+   * method is provided in pursuit of that usecase. It's not used directly 
CreateCollectionAPI,
+   * which uses the JAX-RS framework, but it's kept here so that logic 
surrounding
+   * collection-creation parameters can be kept in a single place.
+   */
+  @SuppressWarnings("unchecked")
+  public static void convertV2CreateCollectionMapToV1ParamMap(Map<String, 
Object> v2MapVals) {
+    // Keys are copied so that map can be modified as keys are looped through.
+    final Set<String> v2Keys = 
v2MapVals.keySet().stream().collect(Collectors.toSet());
+    for (String key : v2Keys) {
+      switch (key) {
+        case V2ApiConstants.PROPERTIES_KEY:
+          final Map<String, Object> propertiesMap =
+              (Map<String, Object>) 
v2MapVals.remove(V2ApiConstants.PROPERTIES_KEY);
+          flattenMapWithPrefix(propertiesMap, v2MapVals, 
CollectionAdminParams.PROPERTY_PREFIX);
+          break;
+        case ROUTER_KEY:
+          final var routerProperties =
+              (CreateCollectionRouterProperties) v2MapVals.remove(ROUTER_KEY);
+          final Map<String, Object> routerPropertiesAsMap =
+              ((Utils.DelegateReflectWriter) 
Utils.getReflectWriter(routerProperties))
+                  .toMap(new HashMap<>());
+          flattenMapWithPrefix(
+              routerPropertiesAsMap, v2MapVals, 
CollectionAdminParams.ROUTER_PREFIX);
+          break;
+        case V2ApiConstants.CONFIG:
+          v2MapVals.put(CollectionAdminParams.COLL_CONF, 
v2MapVals.remove(V2ApiConstants.CONFIG));
+          break;
+        case SHARD_NAMES:
+          final String shardsValue =
+              String.join(",", (Collection<String>) 
v2MapVals.remove(SHARD_NAMES));
+          v2MapVals.put(SHARDS_PROP, shardsValue);
+          break;
+        case V2ApiConstants.SHUFFLE_NODES:
+          v2MapVals.put(
+              CollectionAdminParams.CREATE_NODE_SET_SHUFFLE_PARAM,
+              v2MapVals.remove(V2ApiConstants.SHUFFLE_NODES));
+          break;
+        case V2ApiConstants.NODE_SET:
+          final Object nodeSetValUncast = 
v2MapVals.remove(V2ApiConstants.NODE_SET);
+          if (nodeSetValUncast instanceof String) {
+            v2MapVals.put(CollectionAdminParams.CREATE_NODE_SET_PARAM, 
nodeSetValUncast);
+          } else {
+            final List<String> nodeSetList = (List<String>) nodeSetValUncast;
+            final String nodeSetStr = String.join(",", nodeSetList);
+            v2MapVals.put(CollectionAdminParams.CREATE_NODE_SET_PARAM, 
nodeSetStr);
+          }
+          break;
+        default:
+          break;
       }
     }
   }
 
-  public static class RouterProperties implements JacksonReflectMapWriter {
-    @JsonProperty(NAME)
-    public String name;
-
-    @JsonProperty(FIELD)
-    public String field;
+  public static void addToRemoteMessageWithPrefix(
+      CreateCollectionRequestBody requestBody, Map<String, Object> 
remoteMessage, String prefix) {
+    final Map<String, Object> v1Params =
+        ((Utils.DelegateReflectWriter) 
Utils.getReflectWriter(requestBody)).toMap(new HashMap<>());
+    convertV2CreateCollectionMapToV1ParamMap(v1Params);
+    for (Map.Entry<String, Object> v1Param : v1Params.entrySet()) {
+      remoteMessage.put(prefix + v1Param.getKey(), v1Param.getValue());
+    }
   }
 }
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 30ad9d7eab9..d37688f2162 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
@@ -32,7 +32,7 @@ import static 
org.apache.solr.common.params.CoreAdminParams.BACKUP_REPOSITORY;
 import static org.apache.solr.common.params.CoreAdminParams.COMMIT_NAME;
 import static 
org.apache.solr.common.params.CoreAdminParams.MAX_NUM_BACKUP_POINTS;
 import static 
org.apache.solr.handler.admin.CollectionsHandler.DEFAULT_COLLECTION_OP_TIMEOUT;
-import static 
org.apache.solr.handler.admin.api.CreateCollectionAPI.copyPrefixedPropertiesWithoutPrefix;
+import static 
org.apache.solr.handler.admin.api.CreateCollection.copyPrefixedPropertiesWithoutPrefix;
 import static 
org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
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/CreateReplica.java
similarity index 65%
rename from 
solr/core/src/java/org/apache/solr/handler/admin/api/CreateReplicaAPI.java
rename to 
solr/core/src/java/org/apache/solr/handler/admin/api/CreateReplica.java
index c119f27db60..180a0a335d4 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/CreateReplica.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.cloud.api.collections.CollectionHandlingUtils.CREATE_NODE_SET;
 import static org.apache.solr.common.cloud.ZkStateReader.COLLECTION_PROP;
@@ -38,19 +37,15 @@ import static 
org.apache.solr.common.params.CoreAdminParams.NAME;
 import static org.apache.solr.common.params.CoreAdminParams.NODE;
 import static org.apache.solr.common.params.CoreAdminParams.ULOG_DIR;
 import static org.apache.solr.common.params.ShardParams._ROUTE_;
-import static 
org.apache.solr.handler.admin.api.CreateCollectionAPI.copyPrefixedPropertiesWithoutPrefix;
+import static 
org.apache.solr.handler.admin.api.CreateCollection.copyPrefixedPropertiesWithoutPrefix;
 import static 
org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM;
 
-import com.fasterxml.jackson.annotation.JsonProperty;
 import java.util.Arrays;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 import javax.inject.Inject;
-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.endpoint.CreateReplicaApi;
+import org.apache.solr.client.api.model.CreateReplicaRequestBody;
 import org.apache.solr.client.api.model.SubResponseAccumulatingJerseyResponse;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.cloud.ZkNodeProps;
@@ -59,35 +54,30 @@ import org.apache.solr.common.params.CoreAdminParams;
 import org.apache.solr.common.params.SolrParams;
 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.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 
 /**
- * V2 API for adding a new replica to an existing shard.
+ * V2 API implementation for adding a new replica to an existing shard.
  *
  * <p>This API (POST /v2/collections/cName/shards/sName/replicas {...}) is 
analogous to the v1
  * /admin/collections?action=ADDREPLICA command.
  */
-@Path("/collections/{collectionName}/shards/{shardName}/replicas")
-public class CreateReplicaAPI extends AdminAPIBase {
+public class CreateReplica extends AdminAPIBase implements CreateReplicaApi {
 
   @Inject
-  public CreateReplicaAPI(
+  public CreateReplica(
       CoreContainer coreContainer,
       SolrQueryRequest solrQueryRequest,
       SolrQueryResponse solrQueryResponse) {
     super(coreContainer, solrQueryRequest, solrQueryResponse);
   }
 
-  @POST
-  @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2})
+  @Override
   @PermissionName(COLL_EDIT_PERM)
   public SubResponseAccumulatingJerseyResponse createReplica(
-      @PathParam("collectionName") String collectionName,
-      @PathParam("shardName") String shardName,
-      AddReplicaRequestBody requestBody)
+      String collectionName, String shardName, CreateReplicaRequestBody 
requestBody)
       throws Exception {
     final var response = 
instantiateJerseyResponse(SubResponseAccumulatingJerseyResponse.class);
     if (requestBody == null) {
@@ -103,12 +93,12 @@ public class CreateReplicaAPI extends AdminAPIBase {
     final ZkNodeProps remoteMessage =
         createRemoteMessage(resolvedCollectionName, shardName, requestBody);
     submitRemoteMessageAndHandleResponse(
-        response, CollectionParams.CollectionAction.ADDREPLICA, remoteMessage, 
requestBody.asyncId);
+        response, CollectionParams.CollectionAction.ADDREPLICA, remoteMessage, 
requestBody.async);
     return response;
   }
 
   public static ZkNodeProps createRemoteMessage(
-      String collectionName, String shardName, AddReplicaRequestBody 
requestBody) {
+      String collectionName, String shardName, CreateReplicaRequestBody 
requestBody) {
     final Map<String, Object> remoteMessage = new HashMap<>();
     remoteMessage.put(QUEUE_OPERATION, 
CollectionParams.CollectionAction.ADDREPLICA.toLower());
     remoteMessage.put(COLLECTION_PROP, collectionName);
@@ -129,7 +119,7 @@ public class CreateReplicaAPI extends AdminAPIBase {
     insertIfNotNull(remoteMessage, TLOG_REPLICAS, requestBody.tlogReplicas);
     insertIfNotNull(remoteMessage, PULL_REPLICAS, requestBody.pullReplicas);
     insertIfNotNull(remoteMessage, FOLLOW_ALIASES, requestBody.followAliases);
-    insertIfNotNull(remoteMessage, ASYNC, requestBody.asyncId);
+    insertIfNotNull(remoteMessage, ASYNC, requestBody.async);
 
     if (requestBody.properties != null) {
       requestBody
@@ -144,59 +134,31 @@ public class CreateReplicaAPI extends AdminAPIBase {
     return new ZkNodeProps(remoteMessage);
   }
 
-  public static class AddReplicaRequestBody implements JacksonReflectMapWriter 
{
-    @JsonProperty public String name;
-    @JsonProperty public String type; // TODO Make this an enum - see 
SOLR-15796
-    @JsonProperty public String instanceDir;
-    @JsonProperty public String dataDir;
-    @JsonProperty public String ulogDir;
-    @JsonProperty public String route;
-    @JsonProperty public Integer nrtReplicas;
-    @JsonProperty public Integer tlogReplicas;
-    @JsonProperty public Integer pullReplicas;
-    @JsonProperty public Boolean waitForFinalState;
-    @JsonProperty public Boolean followAliases;
-
-    @JsonProperty(ASYNC)
-    public String asyncId;
-
-    // TODO This cluster of properties could probably be simplified down to 
just "nodeSet".  See
-    // SOLR-15542
-    @JsonProperty public String node;
-
-    @JsonProperty("nodeSet")
-    public List<String> nodeSet;
-
-    @JsonProperty public Boolean skipNodeAssignment;
-
-    @JsonProperty public Map<String, String> properties;
-
-    public static AddReplicaRequestBody fromV1Params(SolrParams params) {
-      final var requestBody = new AddReplicaRequestBody();
-
-      requestBody.name = params.get(NAME);
-      requestBody.type = params.get(REPLICA_TYPE);
-      requestBody.instanceDir = params.get(INSTANCE_DIR);
-      requestBody.dataDir = params.get(DATA_DIR);
-      requestBody.ulogDir = params.get(ULOG_DIR);
-      requestBody.route = params.get(_ROUTE_);
-      requestBody.nrtReplicas = params.getInt(NRT_REPLICAS);
-      requestBody.tlogReplicas = params.getInt(TLOG_REPLICAS);
-      requestBody.pullReplicas = params.getInt(PULL_REPLICAS);
-      requestBody.waitForFinalState = params.getBool(WAIT_FOR_FINAL_STATE);
-      requestBody.followAliases = params.getBool(FOLLOW_ALIASES);
-      requestBody.asyncId = params.get(ASYNC);
-
-      requestBody.node = params.get(NODE);
-      if (params.get(CREATE_NODE_SET_PARAM) != null) {
-        requestBody.nodeSet = 
Arrays.asList(params.get(CREATE_NODE_SET).split(","));
-      }
-      requestBody.skipNodeAssignment = params.getBool(SKIP_NODE_ASSIGNMENT);
+  public static CreateReplicaRequestBody 
createRequestBodyFromV1Params(SolrParams params) {
+    final var requestBody = new CreateReplicaRequestBody();
+
+    requestBody.name = params.get(NAME);
+    requestBody.type = params.get(REPLICA_TYPE);
+    requestBody.instanceDir = params.get(INSTANCE_DIR);
+    requestBody.dataDir = params.get(DATA_DIR);
+    requestBody.ulogDir = params.get(ULOG_DIR);
+    requestBody.route = params.get(_ROUTE_);
+    requestBody.nrtReplicas = params.getInt(NRT_REPLICAS);
+    requestBody.tlogReplicas = params.getInt(TLOG_REPLICAS);
+    requestBody.pullReplicas = params.getInt(PULL_REPLICAS);
+    requestBody.waitForFinalState = params.getBool(WAIT_FOR_FINAL_STATE);
+    requestBody.followAliases = params.getBool(FOLLOW_ALIASES);
+    requestBody.async = params.get(ASYNC);
+
+    requestBody.node = params.get(NODE);
+    if (params.get(CREATE_NODE_SET_PARAM) != null) {
+      requestBody.nodeSet = 
Arrays.asList(params.get(CREATE_NODE_SET).split(","));
+    }
+    requestBody.skipNodeAssignment = params.getBool(SKIP_NODE_ASSIGNMENT);
 
-      requestBody.properties =
-          copyPrefixedPropertiesWithoutPrefix(params, new HashMap<>(), 
PROPERTY_PREFIX);
+    requestBody.properties =
+        copyPrefixedPropertiesWithoutPrefix(params, new HashMap<>(), 
PROPERTY_PREFIX);
 
-      return requestBody;
-    }
+    return requestBody;
   }
 }
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/CreateShard.java
similarity index 69%
rename from 
solr/core/src/java/org/apache/solr/handler/admin/api/CreateShardAPI.java
rename to solr/core/src/java/org/apache/solr/handler/admin/api/CreateShard.java
index 41be6e672d7..ffa2f55ef25 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/CreateShard.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.cloud.api.collections.CollectionHandlingUtils.CREATE_NODE_SET;
 import static org.apache.solr.common.cloud.ZkStateReader.COLLECTION_PROP;
@@ -34,19 +33,15 @@ import static 
org.apache.solr.common.params.CollectionAdminParams.SHARD;
 import static org.apache.solr.common.params.CommonAdminParams.ASYNC;
 import static 
org.apache.solr.common.params.CommonAdminParams.WAIT_FOR_FINAL_STATE;
 import static org.apache.solr.common.params.CommonParams.NAME;
-import static 
org.apache.solr.handler.admin.api.CreateCollectionAPI.copyPrefixedPropertiesWithoutPrefix;
+import static 
org.apache.solr.handler.admin.api.CreateCollection.copyPrefixedPropertiesWithoutPrefix;
 import static 
org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM;
 
-import com.fasterxml.jackson.annotation.JsonProperty;
 import java.util.Arrays;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 import javax.inject.Inject;
-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.endpoint.CreateShardApi;
+import org.apache.solr.client.api.model.CreateShardRequestBody;
 import org.apache.solr.client.api.model.SubResponseAccumulatingJerseyResponse;
 import org.apache.solr.client.solrj.util.SolrIdentifierValidator;
 import org.apache.solr.common.SolrException;
@@ -58,7 +53,6 @@ import org.apache.solr.common.params.CollectionParams;
 import org.apache.solr.common.params.SolrParams;
 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.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
@@ -69,23 +63,20 @@ import org.apache.solr.response.SolrQueryResponse;
  * <p>This API (POST /v2/collections/collectionName/shards {...}) is analogous 
to the v1
  * /admin/collections?action=CREATESHARD command.
  */
-@Path("/collections/{collectionName}/shards")
-public class CreateShardAPI extends AdminAPIBase {
+public class CreateShard extends AdminAPIBase implements CreateShardApi {
 
   @Inject
-  public CreateShardAPI(
+  public CreateShard(
       CoreContainer coreContainer,
       SolrQueryRequest solrQueryRequest,
       SolrQueryResponse solrQueryResponse) {
     super(coreContainer, solrQueryRequest, solrQueryResponse);
   }
 
-  @POST
-  @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2})
+  @Override
   @PermissionName(COLL_EDIT_PERM)
   public SubResponseAccumulatingJerseyResponse createShard(
-      @PathParam("collectionName") String collectionName, 
CreateShardRequestBody requestBody)
-      throws Exception {
+      String collectionName, CreateShardRequestBody requestBody) throws 
Exception {
     final var response = 
instantiateJerseyResponse(SubResponseAccumulatingJerseyResponse.class);
     if (requestBody == null) {
       throw new SolrException(
@@ -101,71 +92,34 @@ public class CreateShardAPI extends AdminAPIBase {
 
     final ZkNodeProps remoteMessage = 
createRemoteMessage(resolvedCollectionName, requestBody);
     submitRemoteMessageAndHandleResponse(
-        response,
-        CollectionParams.CollectionAction.CREATESHARD,
-        remoteMessage,
-        requestBody.asyncId);
+        response, CollectionParams.CollectionAction.CREATESHARD, 
remoteMessage, requestBody.async);
     return response;
   }
 
-  public static class CreateShardRequestBody implements 
JacksonReflectMapWriter {
-    @JsonProperty(NAME)
-    public String shardName;
-
-    @JsonProperty(REPLICATION_FACTOR)
-    public Integer replicationFactor;
-
-    @JsonProperty(NRT_REPLICAS)
-    public Integer nrtReplicas;
-
-    @JsonProperty(TLOG_REPLICAS)
-    public Integer tlogReplicas;
-
-    @JsonProperty(PULL_REPLICAS)
-    public Integer pullReplicas;
-
-    @JsonProperty("createReplicas")
-    public Boolean createReplicas;
-
-    @JsonProperty("nodeSet")
-    public List<String> nodeSet;
-
-    @JsonProperty(WAIT_FOR_FINAL_STATE)
-    public Boolean waitForFinalState;
-
-    @JsonProperty(FOLLOW_ALIASES)
-    public Boolean followAliases;
-
-    @JsonProperty(ASYNC)
-    public String asyncId;
-
-    @JsonProperty public Map<String, String> properties;
-
-    public static CreateShardRequestBody fromV1Params(SolrParams params) {
-      params.required().check(COLLECTION, SHARD);
-
-      final var requestBody = new CreateShardRequestBody();
-      requestBody.shardName = params.get(SHARD);
-      requestBody.replicationFactor = params.getInt(REPLICATION_FACTOR);
-      requestBody.nrtReplicas = params.getInt(NRT_REPLICAS);
-      requestBody.tlogReplicas = params.getInt(TLOG_REPLICAS);
-      requestBody.pullReplicas = params.getInt(PULL_REPLICAS);
-      if (params.get(CREATE_NODE_SET_PARAM) != null) {
-        final String nodeSetStr = params.get(CREATE_NODE_SET_PARAM);
-        if ("EMPTY".equals(nodeSetStr)) {
-          requestBody.createReplicas = false;
-        } else {
-          requestBody.nodeSet = Arrays.asList(nodeSetStr.split(","));
-        }
+  public static CreateShardRequestBody 
createRequestBodyFromV1Params(SolrParams params) {
+    params.required().check(COLLECTION, SHARD);
+
+    final var requestBody = new CreateShardRequestBody();
+    requestBody.shardName = params.get(SHARD);
+    requestBody.replicationFactor = params.getInt(REPLICATION_FACTOR);
+    requestBody.nrtReplicas = params.getInt(NRT_REPLICAS);
+    requestBody.tlogReplicas = params.getInt(TLOG_REPLICAS);
+    requestBody.pullReplicas = params.getInt(PULL_REPLICAS);
+    if (params.get(CREATE_NODE_SET_PARAM) != null) {
+      final String nodeSetStr = params.get(CREATE_NODE_SET_PARAM);
+      if ("EMPTY".equals(nodeSetStr)) {
+        requestBody.createReplicas = false;
+      } else {
+        requestBody.nodeSet = Arrays.asList(nodeSetStr.split(","));
       }
-      requestBody.waitForFinalState = params.getBool(WAIT_FOR_FINAL_STATE);
-      requestBody.followAliases = params.getBool(FOLLOW_ALIASES);
-      requestBody.asyncId = params.get(ASYNC);
-      requestBody.properties =
-          copyPrefixedPropertiesWithoutPrefix(params, new HashMap<>(), 
PROPERTY_PREFIX);
-
-      return requestBody;
     }
+    requestBody.waitForFinalState = params.getBool(WAIT_FOR_FINAL_STATE);
+    requestBody.followAliases = params.getBool(FOLLOW_ALIASES);
+    requestBody.async = params.get(ASYNC);
+    requestBody.properties =
+        copyPrefixedPropertiesWithoutPrefix(params, new HashMap<>(), 
PROPERTY_PREFIX);
+
+    return requestBody;
   }
 
   public static void invokeFromV1Params(
@@ -173,9 +127,8 @@ public class CreateShardAPI extends AdminAPIBase {
       SolrQueryRequest solrQueryRequest,
       SolrQueryResponse solrQueryResponse)
       throws Exception {
-    final var requestBody = 
CreateShardRequestBody.fromV1Params(solrQueryRequest.getParams());
-    final var createShardApi =
-        new CreateShardAPI(coreContainer, solrQueryRequest, solrQueryResponse);
+    final var requestBody = 
CreateShard.createRequestBodyFromV1Params(solrQueryRequest.getParams());
+    final var createShardApi = new CreateShard(coreContainer, 
solrQueryRequest, solrQueryResponse);
     final var response =
         
createShardApi.createShard(solrQueryRequest.getParams().get(COLLECTION), 
requestBody);
     V2ApiUtils.squashIntoSolrResponseWithoutHeader(solrQueryResponse, 
response);
@@ -201,7 +154,7 @@ public class CreateShardAPI extends AdminAPIBase {
     insertIfNotNull(remoteMessage, PULL_REPLICAS, requestBody.pullReplicas);
     insertIfNotNull(remoteMessage, WAIT_FOR_FINAL_STATE, 
requestBody.waitForFinalState);
     insertIfNotNull(remoteMessage, FOLLOW_ALIASES, requestBody.followAliases);
-    insertIfNotNull(remoteMessage, ASYNC, requestBody.asyncId);
+    insertIfNotNull(remoteMessage, ASYNC, requestBody.async);
 
     if (requestBody.properties != null) {
       requestBody
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/InstallCoreData.java
similarity index 73%
rename from 
solr/core/src/java/org/apache/solr/handler/admin/api/InstallCoreDataAPI.java
rename to 
solr/core/src/java/org/apache/solr/handler/admin/api/InstallCoreData.java
index c0cd11b3451..a91a0688e0a 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/InstallCoreData.java
@@ -17,16 +17,11 @@
 
 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.security.PermissionNameProvider.Name.CORE_EDIT_PERM;
 
-import com.fasterxml.jackson.annotation.JsonProperty;
-import java.lang.invoke.MethodHandles;
 import java.net.URI;
-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.endpoint.InstallCoreDataApi;
+import org.apache.solr.client.api.model.InstallCoreDataRequestBody;
 import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.cloud.CloudDescriptor;
 import org.apache.solr.cloud.ZkController;
@@ -36,25 +31,19 @@ import org.apache.solr.core.SolrCore;
 import org.apache.solr.core.backup.repository.BackupRepository;
 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.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 /**
- * v2 implementation of the "Install Core Data" Core-Admin API
+ * V2 API implementation of the "Install Core Data" Core-Admin API
  *
  * <p>This is an internal API intended for use only by the Collection Admin 
"Install Shard Data"
  * API.
  */
-@Path("/cores/{coreName}/install")
-public class InstallCoreDataAPI extends CoreAdminAPIBase {
+public class InstallCoreData extends CoreAdminAPIBase implements 
InstallCoreDataApi {
 
-  private static final Logger log = 
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
-
-  public InstallCoreDataAPI(
+  public InstallCoreData(
       CoreContainer coreContainer,
       CoreAdminHandler.CoreAdminAsyncTracker coreAdminAsyncTracker,
       SolrQueryRequest req,
@@ -62,11 +51,9 @@ public class InstallCoreDataAPI extends CoreAdminAPIBase {
     super(coreContainer, coreAdminAsyncTracker, req, rsp);
   }
 
-  @POST
-  @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2})
+  @Override
   @PermissionName(CORE_EDIT_PERM)
-  public SolrJerseyResponse installCoreData(
-      @PathParam("coreName") String coreName, InstallCoreDataRequestBody 
requestBody)
+  public SolrJerseyResponse installCoreData(String coreName, 
InstallCoreDataRequestBody requestBody)
       throws Exception {
     final SolrJerseyResponse response = 
instantiateJerseyResponse(SolrJerseyResponse.class);
 
@@ -118,18 +105,4 @@ public class InstallCoreDataAPI extends CoreAdminAPIBase {
 
     return response;
   }
-
-  public static class InstallCoreDataRequestBody implements 
JacksonReflectMapWriter {
-    // Expected to point to an index directory (e.g. 
data/techproducts_shard1_replica_n1/data/index)
-    // for a single core that has previously been uploaded to the backup 
repository previously
-    // uploaded to the backup repository.
-    @JsonProperty("location")
-    public String location;
-
-    @JsonProperty("repository")
-    public String repository;
-
-    @JsonProperty("async")
-    public String asyncId;
-  }
 }
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/InstallShardData.java
similarity index 78%
rename from 
solr/core/src/java/org/apache/solr/handler/admin/api/InstallShardDataAPI.java
rename to 
solr/core/src/java/org/apache/solr/handler/admin/api/InstallShardData.java
index 0b313b02712..05df6a2b15e 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/InstallShardData.java
@@ -17,18 +17,13 @@
 
 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.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 java.lang.invoke.MethodHandles;
 import java.util.HashMap;
 import javax.inject.Inject;
-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.endpoint.InstallShardDataApi;
+import org.apache.solr.client.api.model.InstallShardDataRequestBody;
 import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.client.solrj.SolrResponse;
 import org.apache.solr.cloud.api.collections.InstallShardDataCmd;
@@ -40,43 +35,32 @@ 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.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 import org.apache.zookeeper.common.StringUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 /**
- * A V2 API that allows users to import an index constructed offline into a 
shard of their
- * collection
+ * V2 API implementation allowing users to import offline-constructed index 
into a shard.
  *
  * <p>Particularly useful for installing (per-shard) indices constructed 
offline into a SolrCloud
  * deployment. Callers are required to put the collection into read-only mode 
prior to installing
  * data into any shards of that collection, and should exit read only mode 
when completed.
  */
-@Path("/collections/{collName}/shards/{shardName}/install")
-public class InstallShardDataAPI extends AdminAPIBase {
-
-  private static final Logger log = 
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+public class InstallShardData extends AdminAPIBase implements 
InstallShardDataApi {
 
   @Inject
-  public InstallShardDataAPI(
+  public InstallShardData(
       CoreContainer coreContainer,
       SolrQueryRequest solrQueryRequest,
       SolrQueryResponse solrQueryResponse) {
     super(coreContainer, solrQueryRequest, solrQueryResponse);
   }
 
-  @POST
-  @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2})
+  @Override
   @PermissionName(COLL_EDIT_PERM)
   public SolrJerseyResponse installShardData(
-      @PathParam("collName") String collName,
-      @PathParam("shardName") String shardName,
-      InstallShardRequestBody requestBody)
-      throws Exception {
+      String collName, String shardName, InstallShardDataRequestBody 
requestBody) throws Exception {
     final SolrJerseyResponse response = 
instantiateJerseyResponse(SolrJerseyResponse.class);
     final CoreContainer coreContainer = 
fetchAndValidateZooKeeperAwareCoreContainer();
     recordCollectionForLogAndTracing(collName, solrQueryRequest);
@@ -130,28 +114,17 @@ public class InstallShardDataAPI extends AdminAPIBase {
   }
 
   public static ZkNodeProps createRemoteMessage(
-      String collectionName, String shardName, InstallShardRequestBody 
requestBody) {
+      String collectionName, String shardName, InstallShardDataRequestBody 
requestBody) {
     final InstallShardDataCmd.RemoteMessage messageTyped = new 
InstallShardDataCmd.RemoteMessage();
     messageTyped.collection = collectionName;
     messageTyped.shard = shardName;
     if (requestBody != null) {
       messageTyped.location = requestBody.location;
       messageTyped.repository = requestBody.repository;
-      messageTyped.asyncId = requestBody.asyncId;
+      messageTyped.asyncId = requestBody.async;
     }
 
     messageTyped.validate();
     return new ZkNodeProps(messageTyped.toMap(new HashMap<>()));
   }
-
-  public static class InstallShardRequestBody implements 
JacksonReflectMapWriter {
-    @JsonProperty(defaultValue = "location", required = true)
-    public String location;
-
-    @JsonProperty("repository")
-    public String repository;
-
-    @JsonProperty("async")
-    public String asyncId;
-  }
 }
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 1809d83f6b9..b950abf44ec 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,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.CreateCollectionRequestBody;
 import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.client.api.model.SubResponseAccumulatingJerseyResponse;
 import org.apache.solr.client.solrj.SolrResponse;
@@ -121,8 +122,8 @@ public class RestoreCollectionAPI extends BackupAPIBase {
 
     final var createRequestBody = requestBody.createCollectionParams;
     if (createRequestBody != null) {
-      CreateCollectionAPI.populateDefaultsIfNecessary(coreContainer, 
createRequestBody);
-      createRequestBody.validate();
+      CreateCollection.populateDefaultsIfNecessary(coreContainer, 
createRequestBody);
+      CreateCollection.validateRequestBody(createRequestBody);
       if (Boolean.FALSE.equals(createRequestBody.createReplicas)) {
         throw new SolrException(
             SolrException.ErrorCode.BAD_REQUEST,
@@ -160,7 +161,7 @@ public class RestoreCollectionAPI extends BackupAPIBase {
       // RESTORE only supports a subset of collection-creation params, so 
filter by those when
       // constructing the remote message
       remoteMessage.remove("create-collection");
-      
CreateCollectionAPI.createRemoteMessage(createReqBody).getProperties().entrySet().stream()
+      
CreateCollection.createRemoteMessage(createReqBody).getProperties().entrySet().stream()
           .filter(
               e ->
                   CREATE_PARAM_ALLOWLIST.contains(e.getKey())
@@ -204,7 +205,7 @@ public class RestoreCollectionAPI extends BackupAPIBase {
     @JsonProperty public Integer backupId;
 
     @JsonProperty(CREATE_COLLECTION_KEY)
-    public CreateCollectionAPI.CreateCollectionRequestBody 
createCollectionParams;
+    public CreateCollectionRequestBody createCollectionParams;
 
     @JsonProperty public String async;
 
@@ -217,7 +218,7 @@ public class RestoreCollectionAPI extends BackupAPIBase {
       restoreBody.async = solrParams.get(ASYNC);
 
       restoreBody.createCollectionParams =
-          
CreateCollectionAPI.CreateCollectionRequestBody.fromV1Params(solrParams, false);
+          CreateCollection.createRequestBodyFromV1Params(solrParams, false);
 
       return restoreBody;
     }
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 3925b297d6a..a7a6632fa65 100644
--- a/solr/core/src/java/org/apache/solr/jersey/PostRequestLoggingFilter.java
+++ b/solr/core/src/java/org/apache/solr/jersey/PostRequestLoggingFilter.java
@@ -41,6 +41,7 @@ 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.common.util.Utils;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.servlet.HttpSolrCall;
@@ -141,19 +142,23 @@ public class PostRequestLoggingFilter implements 
ContainerResponseFilter {
       return "{}";
     }
 
-    if (!(requestContext.getProperty(DESERIALIZED_REQUEST_BODY_KEY)
-        instanceof JacksonReflectMapWriter)) {
-      log.warn(
-          "Encountered unexpected request-body type {} for request {}; only {} 
expected.",
-          
requestContext.getProperty(DESERIALIZED_REQUEST_BODY_KEY).getClass().getName(),
-          requestContext.getUriInfo().getPath(),
-          JacksonReflectMapWriter.class.getName());
-      return "{}";
+    final Object deserializedBody = 
requestContext.getProperty(DESERIALIZED_REQUEST_BODY_KEY);
+    if (deserializedBody instanceof JacksonReflectMapWriter) {
+      return ((JacksonReflectMapWriter) 
requestContext.getProperty(DESERIALIZED_REQUEST_BODY_KEY))
+          .jsonStr()
+          .replace("\n", "");
+    }
+
+    final Object reflectWritable = Utils.getReflectWriter(deserializedBody);
+    if (reflectWritable instanceof Utils.DelegateReflectWriter) {
+      return Utils.toJSONString(reflectWritable).replaceAll("\n", "");
     }
 
-    return ((JacksonReflectMapWriter) 
requestContext.getProperty(DESERIALIZED_REQUEST_BODY_KEY))
-        .jsonStr()
-        .replace("\n", "");
+    log.warn(
+        "No reflection data found for request-body type {} for request {}; 
omitting request-body details from logging",
+        deserializedBody.getClass().getName(),
+        requestContext.getUriInfo().getPath());
+    return "{}";
   }
 
   public static String filterAndStringifyQueryParameters(
diff --git 
a/solr/core/src/test/org/apache/solr/handler/admin/api/CreateAliasAPITest.java 
b/solr/core/src/test/org/apache/solr/handler/admin/api/CreateAliasAPITest.java
index ce4b5b51544..1e97d61a543 100644
--- 
a/solr/core/src/test/org/apache/solr/handler/admin/api/CreateAliasAPITest.java
+++ 
b/solr/core/src/test/org/apache/solr/handler/admin/api/CreateAliasAPITest.java
@@ -24,6 +24,7 @@ import static org.hamcrest.Matchers.containsString;
 
 import java.util.List;
 import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.client.api.model.CreateCollectionRequestBody;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.params.ModifiableSolrParams;
 import org.hamcrest.MatcherAssert;
@@ -95,7 +96,7 @@ public class CreateAliasAPITest extends SolrTestCaseJ4 {
     final var categoryRouter = new 
CreateAliasAPI.CategoryRoutedAliasProperties();
     categoryRouter.field = "someField";
     requestBody.routers = List.of(categoryRouter);
-    final var createParams = new 
CreateCollectionAPI.CreateCollectionRequestBody();
+    final var createParams = new CreateCollectionRequestBody();
     createParams.numShards = 3;
     requestBody.collCreationParameters = createParams;
 
@@ -112,7 +113,7 @@ public class CreateAliasAPITest extends SolrTestCaseJ4 {
     final var categoryRouter = new 
CreateAliasAPI.CategoryRoutedAliasProperties();
     categoryRouter.field = "someField";
     requestBody.routers = List.of(categoryRouter);
-    final var createParams = new 
CreateCollectionAPI.CreateCollectionRequestBody();
+    final var createParams = new CreateCollectionRequestBody();
     createParams.numShards = 3;
     createParams.config = "someConfig";
     // Not allowed since routed-aliases-created collections have semantically 
meaningful names
@@ -130,7 +131,7 @@ public class CreateAliasAPITest extends SolrTestCaseJ4 {
   public void 
testReportsErrorIfCategoryRoutedAliasDoesntSpecifyAllRequiredParameters() {
     final var requestBody = new CreateAliasAPI.CreateAliasRequestBody();
     requestBody.name = "validName";
-    final var createParams = new 
CreateCollectionAPI.CreateCollectionRequestBody();
+    final var createParams = new CreateCollectionRequestBody();
     createParams.numShards = 3;
     createParams.config = "someConfig";
     requestBody.collCreationParameters = createParams;
@@ -212,7 +213,7 @@ public class CreateAliasAPITest extends SolrTestCaseJ4 {
     final var categoryRouter = new 
CreateAliasAPI.CategoryRoutedAliasProperties();
     categoryRouter.field = "someField";
     requestBody.routers = List.of(categoryRouter);
-    final var createParams = new 
CreateCollectionAPI.CreateCollectionRequestBody();
+    final var createParams = new CreateCollectionRequestBody();
     createParams.numShards = 3;
     createParams.config = "someConfig";
     requestBody.collCreationParameters = createParams;
@@ -239,7 +240,7 @@ public class CreateAliasAPITest extends SolrTestCaseJ4 {
     timeRouter.interval = "+1MONTH";
     timeRouter.maxFutureMs = 123456L;
     requestBody.routers = List.of(timeRouter);
-    final var createParams = new 
CreateCollectionAPI.CreateCollectionRequestBody();
+    final var createParams = new CreateCollectionRequestBody();
     createParams.numShards = 3;
     createParams.config = "someConfig";
     requestBody.collCreationParameters = createParams;
@@ -271,7 +272,7 @@ public class CreateAliasAPITest extends SolrTestCaseJ4 {
     final var categoryRouter = new 
CreateAliasAPI.CategoryRoutedAliasProperties();
     categoryRouter.field = "someField";
     requestBody.routers = List.of(timeRouter, categoryRouter);
-    final var createParams = new 
CreateCollectionAPI.CreateCollectionRequestBody();
+    final var createParams = new CreateCollectionRequestBody();
     createParams.numShards = 3;
     createParams.config = "someConfig";
     requestBody.collCreationParameters = createParams;
@@ -297,7 +298,7 @@ public class CreateAliasAPITest extends SolrTestCaseJ4 {
       CreateAliasAPI.RoutedAliasProperties router) {
     final var requestBody = new CreateAliasAPI.CreateAliasRequestBody();
     requestBody.name = "validName";
-    final var createParams = new 
CreateCollectionAPI.CreateCollectionRequestBody();
+    final var createParams = new CreateCollectionRequestBody();
     createParams.numShards = 3;
     createParams.config = "someConfig";
     requestBody.collCreationParameters = createParams;
diff --git 
a/solr/core/src/test/org/apache/solr/handler/admin/api/CreateCollectionAPITest.java
 
b/solr/core/src/test/org/apache/solr/handler/admin/api/CreateCollectionAPITest.java
index c5eb27f0835..80a2200131a 100644
--- 
a/solr/core/src/test/org/apache/solr/handler/admin/api/CreateCollectionAPITest.java
+++ 
b/solr/core/src/test/org/apache/solr/handler/admin/api/CreateCollectionAPITest.java
@@ -37,11 +37,13 @@ import static 
org.apache.solr.common.params.CoreAdminParams.NAME;
 import java.util.List;
 import java.util.Map;
 import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.client.api.model.CreateCollectionRequestBody;
+import org.apache.solr.client.api.model.CreateCollectionRouterProperties;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.params.ModifiableSolrParams;
 import org.junit.Test;
 
-/** Unit tests for {@link CreateCollectionAPI}. */
+/** Unit tests for {@link CreateCollection}. */
 public class CreateCollectionAPITest extends SolrTestCaseJ4 {
 
   @Test
@@ -50,7 +52,7 @@ public class CreateCollectionAPITest extends SolrTestCaseJ4 {
         expectThrows(
             SolrException.class,
             () -> {
-              final var api = new CreateCollectionAPI(null, null, null);
+              final var api = new CreateCollection(null, null, null);
               api.createCollection(null);
             });
 
@@ -61,7 +63,7 @@ public class CreateCollectionAPITest extends SolrTestCaseJ4 {
   @Test
   public void testReportsErrorIfReplicationFactorAndNrtReplicasConflict() {
     // Valid request body...
-    final var requestBody = new 
CreateCollectionAPI.CreateCollectionRequestBody();
+    final var requestBody = new CreateCollectionRequestBody();
     requestBody.name = "someName";
     requestBody.config = "someConfig";
     // ...except for a replicationFactor and nrtReplicas conflicting
@@ -72,7 +74,7 @@ public class CreateCollectionAPITest extends SolrTestCaseJ4 {
         expectThrows(
             SolrException.class,
             () -> {
-              requestBody.validate();
+              CreateCollection.validateRequestBody(requestBody);
             });
 
     assertEquals(400, thrown.code());
@@ -83,7 +85,7 @@ public class CreateCollectionAPITest extends SolrTestCaseJ4 {
 
   @Test
   public void testReportsErrorIfCollectionNameInvalid() {
-    final var requestBody = new 
CreateCollectionAPI.CreateCollectionRequestBody();
+    final var requestBody = new CreateCollectionRequestBody();
     requestBody.name = "$invalid@collection+name";
     requestBody.config = "someConfig";
 
@@ -91,7 +93,7 @@ public class CreateCollectionAPITest extends SolrTestCaseJ4 {
         expectThrows(
             SolrException.class,
             () -> {
-              requestBody.validate();
+              CreateCollection.validateRequestBody(requestBody);
             });
 
     assertEquals(400, thrown.code());
@@ -103,7 +105,7 @@ public class CreateCollectionAPITest extends SolrTestCaseJ4 
{
   @Test
   public void testReportsErrorIfShardNamesInvalid() {
     // Valid request body...
-    final var requestBody = new 
CreateCollectionAPI.CreateCollectionRequestBody();
+    final var requestBody = new CreateCollectionRequestBody();
     requestBody.name = "someName";
     requestBody.config = "someConfig";
     // ...except for a bad shard name
@@ -113,7 +115,7 @@ public class CreateCollectionAPITest extends SolrTestCaseJ4 
{
         expectThrows(
             SolrException.class,
             () -> {
-              requestBody.validate();
+              CreateCollection.validateRequestBody(requestBody);
             });
 
     assertEquals(400, thrown.code());
@@ -124,7 +126,7 @@ public class CreateCollectionAPITest extends SolrTestCaseJ4 
{
 
   @Test
   public void testCreateRemoteMessageAllProperties() {
-    final var requestBody = new 
CreateCollectionAPI.CreateCollectionRequestBody();
+    final var requestBody = new CreateCollectionRequestBody();
     requestBody.name = "someName";
     requestBody.replicationFactor = 123;
     requestBody.config = "someConfig";
@@ -137,13 +139,13 @@ public class CreateCollectionAPITest extends 
SolrTestCaseJ4 {
     requestBody.alias = "someAliasName";
     requestBody.properties = Map.of("propName", "propValue");
     requestBody.async = "someAsyncId";
-    requestBody.router = new CreateCollectionAPI.RouterProperties();
+    requestBody.router = new CreateCollectionRouterProperties();
     requestBody.router.name = "someRouterName";
     requestBody.router.field = "someField";
     requestBody.nodeSet = List.of("node1", "node2");
     requestBody.shuffleNodes = false;
 
-    final var remoteMessage = 
CreateCollectionAPI.createRemoteMessage(requestBody).getProperties();
+    final var remoteMessage = 
CreateCollection.createRemoteMessage(requestBody).getProperties();
 
     assertEquals("create", remoteMessage.get(QUEUE_OPERATION));
     assertEquals("true", remoteMessage.get("fromApi"));
@@ -168,11 +170,11 @@ public class CreateCollectionAPITest extends 
SolrTestCaseJ4 {
 
   @Test
   public void testNoReplicaCreationMessage() {
-    final var requestBody = new 
CreateCollectionAPI.CreateCollectionRequestBody();
+    final var requestBody = new CreateCollectionRequestBody();
     requestBody.name = "someName";
     requestBody.createReplicas = false;
 
-    final var remoteMessage = 
CreateCollectionAPI.createRemoteMessage(requestBody).getProperties();
+    final var remoteMessage = 
CreateCollection.createRemoteMessage(requestBody).getProperties();
 
     assertEquals("create", remoteMessage.get(QUEUE_OPERATION));
     assertEquals("someName", remoteMessage.get(NAME));
@@ -196,8 +198,7 @@ public class CreateCollectionAPITest extends SolrTestCaseJ4 
{
     solrParams.set("router.field", "someField");
     solrParams.set("property.somePropName", "somePropValue");
 
-    final var v2RequestBody =
-        
CreateCollectionAPI.CreateCollectionRequestBody.fromV1Params(solrParams, true);
+    final var v2RequestBody = 
CreateCollection.createRequestBodyFromV1Params(solrParams, true);
 
     assertEquals("someName", v2RequestBody.name);
     assertEquals(Integer.valueOf(123), v2RequestBody.numShards);
@@ -223,8 +224,7 @@ public class CreateCollectionAPITest extends SolrTestCaseJ4 
{
     solrParams.set(NAME, "someName");
     solrParams.set(CREATE_NODE_SET, "EMPTY");
 
-    final var v2RequestBody =
-        
CreateCollectionAPI.CreateCollectionRequestBody.fromV1Params(solrParams, true);
+    final var v2RequestBody = 
CreateCollection.createRequestBodyFromV1Params(solrParams, true);
 
     assertEquals("someName", v2RequestBody.name);
     assertEquals(Boolean.FALSE, v2RequestBody.createReplicas);
diff --git 
a/solr/core/src/test/org/apache/solr/handler/admin/api/CreateReplicaAPITest.java
 
b/solr/core/src/test/org/apache/solr/handler/admin/api/CreateReplicaAPITest.java
index d4e9772e866..2abe5d13397 100644
--- 
a/solr/core/src/test/org/apache/solr/handler/admin/api/CreateReplicaAPITest.java
+++ 
b/solr/core/src/test/org/apache/solr/handler/admin/api/CreateReplicaAPITest.java
@@ -39,11 +39,12 @@ import static 
org.apache.solr.common.params.ShardParams._ROUTE_;
 import java.util.List;
 import java.util.Map;
 import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.client.api.model.CreateReplicaRequestBody;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.params.ModifiableSolrParams;
 import org.junit.Test;
 
-/** Unit tests for {@link CreateReplicaAPI} */
+/** Unit tests for {@link CreateReplica} */
 public class CreateReplicaAPITest extends SolrTestCaseJ4 {
   @Test
   public void testReportsErrorIfRequestBodyMissing() {
@@ -51,7 +52,7 @@ public class CreateReplicaAPITest extends SolrTestCaseJ4 {
         expectThrows(
             SolrException.class,
             () -> {
-              final var api = new CreateReplicaAPI(null, null, null);
+              final var api = new CreateReplica(null, null, null);
               api.createReplica("someCollName", "someShardName", null);
             });
 
@@ -61,12 +62,12 @@ public class CreateReplicaAPITest extends SolrTestCaseJ4 {
 
   @Test
   public void testReportsErrorIfCollectionNameMissing() {
-    final var requestBody = new CreateReplicaAPI.AddReplicaRequestBody();
+    final var requestBody = new CreateReplicaRequestBody();
     final SolrException thrown =
         expectThrows(
             SolrException.class,
             () -> {
-              final var api = new CreateReplicaAPI(null, null, null);
+              final var api = new CreateReplica(null, null, null);
               api.createReplica(null, "shardName", requestBody);
             });
 
@@ -76,12 +77,12 @@ public class CreateReplicaAPITest extends SolrTestCaseJ4 {
 
   @Test
   public void testReportsErrorIfShardNameMissing() {
-    final var requestBody = new CreateReplicaAPI.AddReplicaRequestBody();
+    final var requestBody = new CreateReplicaRequestBody();
     final SolrException thrown =
         expectThrows(
             SolrException.class,
             () -> {
-              final var api = new CreateReplicaAPI(null, null, null);
+              final var api = new CreateReplica(null, null, null);
               api.createReplica("someCollectionName", null, requestBody);
             });
 
@@ -91,7 +92,7 @@ public class CreateReplicaAPITest extends SolrTestCaseJ4 {
 
   @Test
   public void testCreateRemoteMessageAllProperties() {
-    final var requestBody = new CreateReplicaAPI.AddReplicaRequestBody();
+    final var requestBody = new CreateReplicaRequestBody();
     requestBody.name = "someName";
     requestBody.type = "NRT";
     requestBody.instanceDir = "/some/dir1";
@@ -106,11 +107,11 @@ public class CreateReplicaAPITest extends SolrTestCaseJ4 {
     requestBody.skipNodeAssignment = Boolean.TRUE;
     requestBody.waitForFinalState = true;
     requestBody.followAliases = true;
-    requestBody.asyncId = "someAsyncId";
+    requestBody.async = "someAsyncId";
     requestBody.properties = Map.of("propName1", "propVal1", "propName2", 
"propVal2");
 
     final var remoteMessage =
-        CreateReplicaAPI.createRemoteMessage("someCollectionName", 
"someShardName", requestBody)
+        CreateReplica.createRemoteMessage("someCollectionName", 
"someShardName", requestBody)
             .getProperties();
 
     assertEquals(20, remoteMessage.size());
@@ -159,7 +160,7 @@ public class CreateReplicaAPITest extends SolrTestCaseJ4 {
     v1Params.add("property.propName1", "propVal1");
     v1Params.add("property.propName2", "propVal2");
 
-    final var requestBody = 
CreateReplicaAPI.AddReplicaRequestBody.fromV1Params(v1Params);
+    final var requestBody = 
CreateReplica.createRequestBodyFromV1Params(v1Params);
 
     assertEquals("someName", requestBody.name);
     assertEquals("NRT", requestBody.type);
@@ -175,7 +176,7 @@ public class CreateReplicaAPITest extends SolrTestCaseJ4 {
     assertEquals(Boolean.TRUE, requestBody.skipNodeAssignment);
     assertEquals(Boolean.TRUE, requestBody.waitForFinalState);
     assertEquals(Boolean.TRUE, requestBody.followAliases);
-    assertEquals("someAsyncId", requestBody.asyncId);
+    assertEquals("someAsyncId", requestBody.async);
     assertEquals("propVal1", requestBody.properties.get("propName1"));
     assertEquals("propVal2", requestBody.properties.get("propName2"));
   }
diff --git 
a/solr/core/src/test/org/apache/solr/handler/admin/api/CreateShardAPITest.java 
b/solr/core/src/test/org/apache/solr/handler/admin/api/CreateShardAPITest.java
index 07b71d8a805..bb89fd326b5 100644
--- 
a/solr/core/src/test/org/apache/solr/handler/admin/api/CreateShardAPITest.java
+++ 
b/solr/core/src/test/org/apache/solr/handler/admin/api/CreateShardAPITest.java
@@ -33,12 +33,13 @@ import static org.hamcrest.Matchers.containsString;
 import java.util.List;
 import java.util.Map;
 import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.client.api.model.CreateShardRequestBody;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.params.ModifiableSolrParams;
 import org.hamcrest.MatcherAssert;
 import org.junit.Test;
 
-/** Unit tests for {@link CreateShardAPI} */
+/** Unit tests for {@link CreateShard} */
 public class CreateShardAPITest extends SolrTestCaseJ4 {
 
   @Test
@@ -47,7 +48,7 @@ public class CreateShardAPITest extends SolrTestCaseJ4 {
         expectThrows(
             SolrException.class,
             () -> {
-              final var api = new CreateShardAPI(null, null, null);
+              final var api = new CreateShard(null, null, null);
               api.createShard("someCollName", null);
             });
 
@@ -57,13 +58,13 @@ public class CreateShardAPITest extends SolrTestCaseJ4 {
 
   @Test
   public void testReportsErrorIfCollectionNameMissing() {
-    final var requestBody = new CreateShardAPI.CreateShardRequestBody();
+    final var requestBody = new CreateShardRequestBody();
     requestBody.shardName = "someShardName";
     final SolrException thrown =
         expectThrows(
             SolrException.class,
             () -> {
-              final var api = new CreateShardAPI(null, null, null);
+              final var api = new CreateShard(null, null, null);
               api.createShard(null, requestBody);
             });
 
@@ -73,13 +74,13 @@ public class CreateShardAPITest extends SolrTestCaseJ4 {
 
   @Test
   public void testReportsErrorIfShardNameMissing() {
-    final var requestBody = new CreateShardAPI.CreateShardRequestBody();
+    final var requestBody = new CreateShardRequestBody();
     requestBody.shardName = null;
     final SolrException thrown =
         expectThrows(
             SolrException.class,
             () -> {
-              final var api = new CreateShardAPI(null, null, null);
+              final var api = new CreateShard(null, null, null);
               api.createShard("someCollectionName", requestBody);
             });
 
@@ -89,13 +90,13 @@ public class CreateShardAPITest extends SolrTestCaseJ4 {
 
   @Test
   public void testReportsErrorIfShardNameIsInvalid() {
-    final var requestBody = new CreateShardAPI.CreateShardRequestBody();
+    final var requestBody = new CreateShardRequestBody();
     requestBody.shardName = "invalid$shard@name";
     final SolrException thrown =
         expectThrows(
             SolrException.class,
             () -> {
-              final var api = new CreateShardAPI(null, null, null);
+              final var api = new CreateShard(null, null, null);
               api.createShard("someCollectionName", requestBody);
             });
 
@@ -106,7 +107,7 @@ public class CreateShardAPITest extends SolrTestCaseJ4 {
 
   @Test
   public void testCreateRemoteMessageAllProperties() {
-    final var requestBody = new CreateShardAPI.CreateShardRequestBody();
+    final var requestBody = new CreateShardRequestBody();
     requestBody.shardName = "someShardName";
     requestBody.replicationFactor = 123;
     requestBody.nrtReplicas = 123;
@@ -116,11 +117,11 @@ public class CreateShardAPITest extends SolrTestCaseJ4 {
     requestBody.nodeSet = List.of("node1", "node2");
     requestBody.waitForFinalState = true;
     requestBody.followAliases = true;
-    requestBody.asyncId = "someAsyncId";
+    requestBody.async = "someAsyncId";
     requestBody.properties = Map.of("propName1", "propVal1", "propName2", 
"propVal2");
 
     final var remoteMessage =
-        CreateShardAPI.createRemoteMessage("someCollectionName", 
requestBody).getProperties();
+        CreateShard.createRemoteMessage("someCollectionName", 
requestBody).getProperties();
 
     assertEquals(13, remoteMessage.size());
     assertEquals("createshard", remoteMessage.get(QUEUE_OPERATION));
@@ -154,7 +155,7 @@ public class CreateShardAPITest extends SolrTestCaseJ4 {
     v1Params.add("property.propName1", "propVal1");
     v1Params.add("property.propName2", "propVal2");
 
-    final var requestBody = 
CreateShardAPI.CreateShardRequestBody.fromV1Params(v1Params);
+    final var requestBody = 
CreateShard.createRequestBodyFromV1Params(v1Params);
 
     assertEquals("someShardName", requestBody.shardName);
     assertEquals(Integer.valueOf(123), requestBody.replicationFactor);
@@ -165,7 +166,7 @@ public class CreateShardAPITest extends SolrTestCaseJ4 {
     assertEquals(List.of("node1", "node2"), requestBody.nodeSet);
     assertEquals(Boolean.TRUE, requestBody.waitForFinalState);
     assertEquals(Boolean.TRUE, requestBody.followAliases);
-    assertEquals("someAsyncId", requestBody.asyncId);
+    assertEquals("someAsyncId", requestBody.async);
     assertEquals("propVal1", requestBody.properties.get("propName1"));
     assertEquals("propVal2", requestBody.properties.get("propName2"));
   }
diff --git 
a/solr/core/src/test/org/apache/solr/handler/admin/api/RestoreCollectionAPITest.java
 
b/solr/core/src/test/org/apache/solr/handler/admin/api/RestoreCollectionAPITest.java
index 52321b3f3c8..d4a303f2599 100644
--- 
a/solr/core/src/test/org/apache/solr/handler/admin/api/RestoreCollectionAPITest.java
+++ 
b/solr/core/src/test/org/apache/solr/handler/admin/api/RestoreCollectionAPITest.java
@@ -35,6 +35,7 @@ import static org.hamcrest.Matchers.containsString;
 import java.util.List;
 import java.util.Map;
 import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.client.api.model.CreateCollectionRequestBody;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.params.ModifiableSolrParams;
 import org.hamcrest.MatcherAssert;
@@ -136,7 +137,7 @@ public class RestoreCollectionAPITest extends 
SolrTestCaseJ4 {
     requestBody.backupId = 123;
     requestBody.repository = "someRepositoryName";
     requestBody.async = "someAsyncId";
-    final var createParams = new 
CreateCollectionAPI.CreateCollectionRequestBody();
+    final var createParams = new CreateCollectionRequestBody();
     requestBody.createCollectionParams = createParams;
     createParams.config = "someConfig";
     createParams.nrtReplicas = 123;
diff --git 
a/solr/core/src/test/org/apache/solr/jersey/PostRequestLoggingFilterTest.java 
b/solr/core/src/test/org/apache/solr/jersey/PostRequestLoggingFilterTest.java
index c0770c4912e..985085b7829 100644
--- 
a/solr/core/src/test/org/apache/solr/jersey/PostRequestLoggingFilterTest.java
+++ 
b/solr/core/src/test/org/apache/solr/jersey/PostRequestLoggingFilterTest.java
@@ -26,7 +26,7 @@ import javax.ws.rs.container.ContainerRequestContext;
 import javax.ws.rs.core.MultivaluedHashMap;
 import javax.ws.rs.core.UriInfo;
 import org.apache.solr.SolrTestCaseJ4;
-import org.apache.solr.handler.admin.api.CreateReplicaAPI;
+import org.apache.solr.client.api.model.CreateReplicaRequestBody;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
@@ -122,10 +122,10 @@ public class PostRequestLoggingFilterTest extends 
SolrTestCaseJ4 {
 
   @Test
   public void testRequestBodyRepresentedAsJsonWhenFound() {
-    final var requestBody = new CreateReplicaAPI.AddReplicaRequestBody();
+    final var requestBody = new CreateReplicaRequestBody();
     requestBody.name = "someReplicaName";
     requestBody.type = "NRT";
-    requestBody.asyncId = "someAsyncId";
+    requestBody.async = "someAsyncId";
     final var mockContext = mock(ContainerRequestContext.class);
     
when(mockContext.getProperty(DESERIALIZED_REQUEST_BODY_KEY)).thenReturn(requestBody);
 
diff --git 
a/solr/modules/gcs-repository/src/test/org/apache/solr/gcs/GCSInstallShardTest.java
 
b/solr/modules/gcs-repository/src/test/org/apache/solr/gcs/GCSInstallShardTest.java
index 51dc59d2ea0..3d0d30262ed 100644
--- 
a/solr/modules/gcs-repository/src/test/org/apache/solr/gcs/GCSInstallShardTest.java
+++ 
b/solr/modules/gcs-repository/src/test/org/apache/solr/gcs/GCSInstallShardTest.java
@@ -20,6 +20,7 @@ package org.apache.solr.gcs;
 import com.carrotsearch.randomizedtesting.annotations.ThreadLeakLingering;
 import org.apache.lucene.tests.util.LuceneTestCase;
 import org.apache.solr.cloud.api.collections.AbstractInstallShardTest;
+import org.apache.solr.handler.admin.api.InstallShardData;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 
@@ -27,7 +28,7 @@ import org.junit.BeforeClass;
  * Tests validating that the 'Install Shard API' works when used with {@link 
GCSBackupRepository}
  *
  * @see org.apache.solr.cloud.api.collections.AbstractInstallShardTest
- * @see org.apache.solr.handler.admin.api.InstallShardDataAPI
+ * @see InstallShardData
  */
 // Backups do checksum validation against a footer value not present in 
'SimpleText'
 @LuceneTestCase.SuppressCodecs({"SimpleText"})
diff --git 
a/solr/modules/s3-repository/src/test/org/apache/solr/s3/S3InstallShardTest.java
 
b/solr/modules/s3-repository/src/test/org/apache/solr/s3/S3InstallShardTest.java
index 303e414d84e..11a58e2100a 100644
--- 
a/solr/modules/s3-repository/src/test/org/apache/solr/s3/S3InstallShardTest.java
+++ 
b/solr/modules/s3-repository/src/test/org/apache/solr/s3/S3InstallShardTest.java
@@ -21,6 +21,7 @@ import com.adobe.testing.s3mock.junit4.S3MockRule;
 import com.carrotsearch.randomizedtesting.annotations.ThreadLeakLingering;
 import org.apache.lucene.tests.util.LuceneTestCase;
 import org.apache.solr.cloud.api.collections.AbstractInstallShardTest;
+import org.apache.solr.handler.admin.api.InstallShardData;
 import org.junit.BeforeClass;
 import org.junit.ClassRule;
 import software.amazon.awssdk.regions.Region;
@@ -29,7 +30,7 @@ import software.amazon.awssdk.regions.Region;
  * Tests validating that the 'Install Shard API' works when used with {@link 
S3BackupRepository}
  *
  * @see org.apache.solr.cloud.api.collections.AbstractInstallShardTest
- * @see org.apache.solr.handler.admin.api.InstallShardDataAPI
+ * @see InstallShardData
  */
 // Backups do checksum validation against a footer value not present in 
'SimpleText'
 @LuceneTestCase.SuppressCodecs({"SimpleText"})
diff --git a/solr/solrj/src/resources/java-template/api.mustache 
b/solr/solrj/src/resources/java-template/api.mustache
index 93108eae513..7f40f2332d7 100644
--- a/solr/solrj/src/resources/java-template/api.mustache
+++ b/solr/solrj/src/resources/java-template/api.mustache
@@ -34,13 +34,26 @@ 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}}
+
+{{! Covers all top-level request/response model classes, but not necessarily 
any types nested in those classes }}
 {{#imports}}
-import {{modelPackage}}.{{.}};
+import {{import}};
 {{/imports}}
+
+{{! Imports any model types nested in request body POJOs }}
+{{#operations}}
+{{#operation}}
+{{#bodyParam}}
+{{#vars}}
+{{#isModel}}
+import {{modelPackage}}.{{dataType}};
+{{/isModel}}
+{{/vars}}
+{{/bodyParam}}
 {{/operation}}
+{{/operations}}
 
+{{#operations}}
 // WARNING: This class is generated from a Mustache template; any intended
 // changes should be made to the underlying template and not this file 
directly.
 
diff --git 
a/solr/test-framework/src/java/org/apache/solr/cloud/api/collections/AbstractInstallShardTest.java
 
b/solr/test-framework/src/java/org/apache/solr/cloud/api/collections/AbstractInstallShardTest.java
index 8bcd85077b7..497e8e62cf8 100644
--- 
a/solr/test-framework/src/java/org/apache/solr/cloud/api/collections/AbstractInstallShardTest.java
+++ 
b/solr/test-framework/src/java/org/apache/solr/cloud/api/collections/AbstractInstallShardTest.java
@@ -48,6 +48,7 @@ import org.apache.solr.core.CoreDescriptor;
 import org.apache.solr.core.DirectoryFactory;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.core.backup.repository.BackupRepository;
+import org.apache.solr.handler.admin.api.InstallShardData;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.BeforeClass;
@@ -62,7 +63,7 @@ import org.slf4j.LoggerFactory;
  * repository. This base-class will populate that backup repository all data 
necessary for these
  * tests.
  *
- * @see org.apache.solr.handler.admin.api.InstallShardDataAPI
+ * @see InstallShardData
  */
 public abstract class AbstractInstallShardTest extends SolrCloudTestCase {
 

Reply via email to