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

(solr) branch branch_9x updated: SOLR-16825: Migrate v2 definitions to 'api' module, pt 7 (#2116)

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


The following commit(s) were added to refs/heads/branch_9x by this push:
     new 8be4782cf88 SOLR-16825: Migrate v2 definitions to 'api' module, pt 7 (#2116)
8be4782cf88 is described below

commit 8be4782cf889f15aa0f3246b428d3e003794ca24
Author: Jason Gerlowski <ge...@apache.org>
AuthorDate: Wed Dec 6 09:42:15 2023 -0500

    SOLR-16825: Migrate v2 definitions to 'api' module, pt 7 (#2116)
    
    This commit covers the core and collection-level create-backup APIs, the
    create-snapshot API, and "balance-shard-unique".
    
    Extracting annotated interfaces for these APIs includes them in our Java, JS,
    and Python client generation.
---
 .../client/api/endpoint/BalanceShardUniqueApi.java | 43 ++++++++++++
 .../api/endpoint/CreateCollectionBackupApi.java    | 43 ++++++++++++
 ...otApi.java => CreateCollectionSnapshotApi.java} | 34 ++++-----
 .../CreateCoreBackupApi.java}                      | 40 +++++------
 .../api/endpoint/DeleteCollectionSnapshotApi.java  |  8 ++-
 ...nse.java => BalanceShardUniqueRequestBody.java} | 26 ++-----
 ...nse.java => CreateCollectionBackupDetails.java} | 35 +++++-----
 ...java => CreateCollectionBackupRequestBody.java} | 33 ++++-----
 ...ava => CreateCollectionBackupResponseBody.java} | 26 ++-----
 ...va => CreateCollectionSnapshotRequestBody.java} | 24 ++-----
 ....java => CreateCollectionSnapshotResponse.java} | 14 +---
 ...ponse.java => CreateCoreBackupRequestBody.java} | 48 +++++++------
 .../model/DeleteCollectionSnapshotResponse.java    |  4 +-
 .../apache/solr/handler/admin/BackupCoreOp.java    | 10 +--
 .../solr/handler/admin/CollectionsHandler.java     | 35 +++++-----
 .../solr/handler/admin/CoreAdminHandler.java       |  4 +-
 ...ShardUniqueAPI.java => BalanceShardUnique.java} | 48 ++++---------
 .../solr/handler/admin/api/CreateCollection.java   |  7 +-
 ...nBackupAPI.java => CreateCollectionBackup.java} | 80 ++++------------------
 ...pshotAPI.java => CreateCollectionSnapshot.java} | 74 ++++----------------
 .../{BackupCoreAPI.java => CreateCoreBackup.java}  | 60 ++--------------
 .../admin/api/DeleteCollectionSnapshot.java        |  2 +-
 .../solr/handler/admin/api/BackupCoreAPITest.java  | 20 +++---
 .../admin/api/BalanceShardUniqueAPITest.java       | 19 ++---
 .../admin/api/CreateCollectionSnapshotAPITest.java |  5 +-
 .../admin/api/V2CollectionBackupApiTest.java       | 13 ++--
 .../java/org/apache/solr/common/util/Utils.java    | 10 +++
 27 files changed, 316 insertions(+), 449 deletions(-)

diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/BalanceShardUniqueApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/BalanceShardUniqueApi.java
new file mode 100644
index 00000000000..5c10d957640
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/BalanceShardUniqueApi.java
@@ -0,0 +1,43 @@
+/*
+ * 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.BalanceShardUniqueRequestBody;
+import org.apache.solr.client.api.model.SubResponseAccumulatingJerseyResponse;
+
+/**
+ * V2 API definition for insuring that a specified per-shard property is distributed evenly amongst
+ * the physical nodes comprising a collection.
+ *
+ * <p>The new API (POST /v2/collections/collectionName/balance-shard-unique {...} ) is analogous to
+ * the v1 /admin/collections?action=BALANCESHARDUNIQUE command.
+ */
+@Path("/collections/{collectionName}/balance-shard-unique")
+public interface BalanceShardUniqueApi {
+  @POST
+  @Operation(
+      summary =
+          "Ensure a specified per-shard property is distributed evenly amongst physical nodes comprising a collection",
+      tags = {"collections"})
+  SubResponseAccumulatingJerseyResponse balanceShardUnique(
+      @PathParam("collectionName") String collectionName, BalanceShardUniqueRequestBody requestBody)
+      throws Exception;
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/CreateCollectionBackupApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/CreateCollectionBackupApi.java
new file mode 100644
index 00000000000..802b8df7db0
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/CreateCollectionBackupApi.java
@@ -0,0 +1,43 @@
+/*
+ * 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.CreateCollectionBackupRequestBody;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
+
+/**
+ * V2 API definition for creating a new "backup" of a specified collection
+ *
+ * <p>This API is analogous to the v1 /admin/collections?action=BACKUP command.
+ */
+@Path("/collections/{collectionName}/backups/{backupName}/versions")
+public interface CreateCollectionBackupApi {
+
+  @POST
+  @Operation(
+      summary = "Creates a new backup point for a collection",
+      tags = {"collection-backups"})
+  SolrJerseyResponse createCollectionBackup(
+      @PathParam("collectionName") String collectionName,
+      @PathParam("backupName") String backupName,
+      CreateCollectionBackupRequestBody requestBody)
+      throws Exception;
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteCollectionSnapshotApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/CreateCollectionSnapshotApi.java
similarity index 57%
copy from solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteCollectionSnapshotApi.java
copy to solr/api/src/java/org/apache/solr/client/api/endpoint/CreateCollectionSnapshotApi.java
index fb77fbd6dc8..80440ee92c0 100644
--- a/solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteCollectionSnapshotApi.java
+++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/CreateCollectionSnapshotApi.java
@@ -14,33 +14,33 @@
  * 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 io.swagger.v3.oas.annotations.Parameter;
-import javax.ws.rs.DELETE;
-import javax.ws.rs.DefaultValue;
+import io.swagger.v3.oas.annotations.parameters.RequestBody;
+import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
-import javax.ws.rs.QueryParam;
-import org.apache.solr.client.api.model.DeleteCollectionSnapshotResponse;
-
-public interface DeleteCollectionSnapshotApi {
+import org.apache.solr.client.api.model.CreateCollectionSnapshotRequestBody;
+import org.apache.solr.client.api.model.CreateCollectionSnapshotResponse;
 
-  /** This API is analogous to V1's (POST /solr/admin/collections?action=DELETESNAPSHOT) */
-  @DELETE
-  @Path("/collections/{collName}/snapshots/{snapshotName}")
-  DeleteCollectionSnapshotResponse deleteSnapshot(
+/** V2 API definition for creating a collection-level snapshot. */
+@Path("/collections/{collName}/snapshots")
+public interface CreateCollectionSnapshotApi {
+  @POST
+  @Path("/{snapshotName}")
+  @Operation(
+      summary = "Creates a new snapshot of the specified collection.",
+      tags = {"collection-snapshots"})
+  CreateCollectionSnapshotResponse createCollectionSnapshot(
       @Parameter(description = "The name of the collection.", required = true)
           @PathParam("collName")
           String collName,
-      @Parameter(description = "The name of the snapshot to be deleted.", required = true)
+      @Parameter(description = "The name of the snapshot to be created.", required = true)
           @PathParam("snapshotName")
           String snapshotName,
-      @Parameter(description = "A flag that treats the collName parameter as a collection alias.")
-          @DefaultValue("false")
-          @QueryParam("followAliases")
-          boolean followAliases,
-      @QueryParam("async") String asyncId)
+      @RequestBody(description = "Contains user provided parameters", required = true)
+          CreateCollectionSnapshotRequestBody requestBody)
       throws Exception;
 }
diff --git a/solr/api/src/java/org/apache/solr/client/api/model/DeleteCollectionSnapshotResponse.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/CreateCoreBackupApi.java
similarity index 51%
copy from solr/api/src/java/org/apache/solr/client/api/model/DeleteCollectionSnapshotResponse.java
copy to solr/api/src/java/org/apache/solr/client/api/endpoint/CreateCoreBackupApi.java
index 2b134a2877c..113ffb722ca 100644
--- a/solr/api/src/java/org/apache/solr/client/api/model/DeleteCollectionSnapshotResponse.java
+++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/CreateCoreBackupApi.java
@@ -14,29 +14,27 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.apache.solr.client.api.endpoint;
 
-package org.apache.solr.client.api.model;
-
-import static org.apache.solr.client.api.model.Constants.COLLECTION;
-
-import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.media.Schema;
-import org.apache.solr.client.api.endpoint.DeleteCollectionSnapshotApi;
-
-/**
- * The Response for {@link DeleteCollectionSnapshotApi#deleteSnapshot(String, String, boolean,
- * String)}
- */
-public class DeleteCollectionSnapshotResponse extends AsyncJerseyResponse {
-  @Schema(description = "The name of the collection.")
-  @JsonProperty(COLLECTION)
-  public String collection;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import org.apache.solr.client.api.model.CreateCoreBackupRequestBody;
+import org.apache.solr.client.api.model.SolrJerseyResponse;
 
-  @Schema(description = "The name of the snapshot to be deleted.")
-  @JsonProperty("snapshot")
-  public String snapshotName;
+@Path("/cores/{coreName}/backups")
+public interface CreateCoreBackupApi {
 
-  @Schema(description = "A flag that treats the collName parameter as a collection alias.")
-  @JsonProperty("followAliases")
-  public boolean followAliases;
+  @POST
+  @Operation(
+      summary = "Creates a core-level backup",
+      tags = {"core-backups"})
+  SolrJerseyResponse createBackup(
+      @Parameter(description = "The name of the core.") @PathParam("coreName") String coreName,
+      @Schema(description = "Additional backup params")
+          CreateCoreBackupRequestBody backupCoreRequestBody)
+      throws Exception;
 }
diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteCollectionSnapshotApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteCollectionSnapshotApi.java
index fb77fbd6dc8..c00ed95da8d 100644
--- a/solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteCollectionSnapshotApi.java
+++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteCollectionSnapshotApi.java
@@ -17,6 +17,7 @@
 
 package org.apache.solr.client.api.endpoint;
 
+import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import javax.ws.rs.DELETE;
 import javax.ws.rs.DefaultValue;
@@ -25,12 +26,15 @@ import javax.ws.rs.PathParam;
 import javax.ws.rs.QueryParam;
 import org.apache.solr.client.api.model.DeleteCollectionSnapshotResponse;
 
+@Path("/collections/{collName}/snapshots/{snapshotName}")
 public interface DeleteCollectionSnapshotApi {
 
   /** This API is analogous to V1's (POST /solr/admin/collections?action=DELETESNAPSHOT) */
   @DELETE
-  @Path("/collections/{collName}/snapshots/{snapshotName}")
-  DeleteCollectionSnapshotResponse deleteSnapshot(
+  @Operation(
+      summary = "Delete an existing collection-snapshot by name.",
+      tags = {"collection-snapshots"})
+  DeleteCollectionSnapshotResponse deleteCollectionSnapshot(
       @Parameter(description = "The name of the collection.", required = true)
           @PathParam("collName")
           String collName,
diff --git a/solr/api/src/java/org/apache/solr/client/api/model/DeleteCollectionSnapshotResponse.java b/solr/api/src/java/org/apache/solr/client/api/model/BalanceShardUniqueRequestBody.java
similarity index 53%
copy from solr/api/src/java/org/apache/solr/client/api/model/DeleteCollectionSnapshotResponse.java
copy to solr/api/src/java/org/apache/solr/client/api/model/BalanceShardUniqueRequestBody.java
index 2b134a2877c..155c173ebd6 100644
--- a/solr/api/src/java/org/apache/solr/client/api/model/DeleteCollectionSnapshotResponse.java
+++ b/solr/api/src/java/org/apache/solr/client/api/model/BalanceShardUniqueRequestBody.java
@@ -14,29 +14,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package org.apache.solr.client.api.model;
 
-import static org.apache.solr.client.api.model.Constants.COLLECTION;
-
 import com.fasterxml.jackson.annotation.JsonProperty;
-import io.swagger.v3.oas.annotations.media.Schema;
-import org.apache.solr.client.api.endpoint.DeleteCollectionSnapshotApi;
 
-/**
- * The Response for {@link DeleteCollectionSnapshotApi#deleteSnapshot(String, String, boolean,
- * String)}
- */
-public class DeleteCollectionSnapshotResponse extends AsyncJerseyResponse {
-  @Schema(description = "The name of the collection.")
-  @JsonProperty(COLLECTION)
-  public String collection;
+public class BalanceShardUniqueRequestBody {
+  @JsonProperty(required = true)
+  public String property;
+
+  public Boolean onlyActiveNodes;
 
-  @Schema(description = "The name of the snapshot to be deleted.")
-  @JsonProperty("snapshot")
-  public String snapshotName;
+  public Boolean shardUnique;
 
-  @Schema(description = "A flag that treats the collName parameter as a collection alias.")
-  @JsonProperty("followAliases")
-  public boolean followAliases;
+  public String async;
 }
diff --git a/solr/api/src/java/org/apache/solr/client/api/model/DeleteCollectionSnapshotResponse.java b/solr/api/src/java/org/apache/solr/client/api/model/CreateCollectionBackupDetails.java
similarity index 53%
copy from solr/api/src/java/org/apache/solr/client/api/model/DeleteCollectionSnapshotResponse.java
copy to solr/api/src/java/org/apache/solr/client/api/model/CreateCollectionBackupDetails.java
index 2b134a2877c..37b56d51bc3 100644
--- a/solr/api/src/java/org/apache/solr/client/api/model/DeleteCollectionSnapshotResponse.java
+++ b/solr/api/src/java/org/apache/solr/client/api/model/CreateCollectionBackupDetails.java
@@ -14,29 +14,26 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package org.apache.solr.client.api.model;
 
-import static org.apache.solr.client.api.model.Constants.COLLECTION;
-
 import com.fasterxml.jackson.annotation.JsonProperty;
-import io.swagger.v3.oas.annotations.media.Schema;
-import org.apache.solr.client.api.endpoint.DeleteCollectionSnapshotApi;
+import java.util.List;
+import java.util.Map;
 
-/**
- * The Response for {@link DeleteCollectionSnapshotApi#deleteSnapshot(String, String, boolean,
- * String)}
- */
-public class DeleteCollectionSnapshotResponse extends AsyncJerseyResponse {
-  @Schema(description = "The name of the collection.")
-  @JsonProperty(COLLECTION)
-  public String collection;
+public class CreateCollectionBackupDetails {
+  @JsonProperty public String collection;
+  @JsonProperty public Integer numShards;
+  @JsonProperty public Integer backupId;
+  @JsonProperty public String indexVersion;
+  @JsonProperty public String startTime;
+  @JsonProperty public String endTime;
+  @JsonProperty public Integer indexFileCount;
+  @JsonProperty public Integer uploadedIndexFileCount;
+  @JsonProperty public Double indexSizeMB;
+  @JsonProperty public Map<String, String> extraProperties;
 
-  @Schema(description = "The name of the snapshot to be deleted.")
-  @JsonProperty("snapshot")
-  public String snapshotName;
+  @JsonProperty("uploadedIndexFileMB")
+  public Double uploadedIndexSizeMB;
 
-  @Schema(description = "A flag that treats the collName parameter as a collection alias.")
-  @JsonProperty("followAliases")
-  public boolean followAliases;
+  @JsonProperty public List<String> shardBackupIds;
 }
diff --git a/solr/api/src/java/org/apache/solr/client/api/model/DeleteCollectionSnapshotResponse.java b/solr/api/src/java/org/apache/solr/client/api/model/CreateCollectionBackupRequestBody.java
similarity index 53%
copy from solr/api/src/java/org/apache/solr/client/api/model/DeleteCollectionSnapshotResponse.java
copy to solr/api/src/java/org/apache/solr/client/api/model/CreateCollectionBackupRequestBody.java
index 2b134a2877c..96fc262ef7f 100644
--- a/solr/api/src/java/org/apache/solr/client/api/model/DeleteCollectionSnapshotResponse.java
+++ b/solr/api/src/java/org/apache/solr/client/api/model/CreateCollectionBackupRequestBody.java
@@ -14,29 +14,20 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package org.apache.solr.client.api.model;
 
-import static org.apache.solr.client.api.model.Constants.COLLECTION;
-
 import com.fasterxml.jackson.annotation.JsonProperty;
-import io.swagger.v3.oas.annotations.media.Schema;
-import org.apache.solr.client.api.endpoint.DeleteCollectionSnapshotApi;
-
-/**
- * The Response for {@link DeleteCollectionSnapshotApi#deleteSnapshot(String, String, boolean,
- * String)}
- */
-public class DeleteCollectionSnapshotResponse extends AsyncJerseyResponse {
-  @Schema(description = "The name of the collection.")
-  @JsonProperty(COLLECTION)
-  public String collection;
-
-  @Schema(description = "The name of the snapshot to be deleted.")
-  @JsonProperty("snapshot")
-  public String snapshotName;
+import java.util.Map;
 
-  @Schema(description = "A flag that treats the collName parameter as a collection alias.")
-  @JsonProperty("followAliases")
-  public boolean followAliases;
+public class CreateCollectionBackupRequestBody {
+  @JsonProperty public String location;
+  @JsonProperty public String repository;
+  @JsonProperty public Boolean followAliases;
+  @JsonProperty public String backupStrategy;
+  @JsonProperty public String snapshotName;
+  @JsonProperty public Boolean incremental;
+  @JsonProperty public Boolean backupConfigset;
+  @JsonProperty public Integer maxNumBackupPoints;
+  @JsonProperty public String async;
+  @JsonProperty public Map<String, String> extraProperties;
 }
diff --git a/solr/api/src/java/org/apache/solr/client/api/model/DeleteCollectionSnapshotResponse.java b/solr/api/src/java/org/apache/solr/client/api/model/CreateCollectionBackupResponseBody.java
similarity index 53%
copy from solr/api/src/java/org/apache/solr/client/api/model/DeleteCollectionSnapshotResponse.java
copy to solr/api/src/java/org/apache/solr/client/api/model/CreateCollectionBackupResponseBody.java
index 2b134a2877c..389e8609d6d 100644
--- a/solr/api/src/java/org/apache/solr/client/api/model/DeleteCollectionSnapshotResponse.java
+++ b/solr/api/src/java/org/apache/solr/client/api/model/CreateCollectionBackupResponseBody.java
@@ -14,29 +14,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package org.apache.solr.client.api.model;
 
-import static org.apache.solr.client.api.model.Constants.COLLECTION;
-
 import com.fasterxml.jackson.annotation.JsonProperty;
-import io.swagger.v3.oas.annotations.media.Schema;
-import org.apache.solr.client.api.endpoint.DeleteCollectionSnapshotApi;
+import java.util.List;
 
-/**
- * The Response for {@link DeleteCollectionSnapshotApi#deleteSnapshot(String, String, boolean,
- * String)}
- */
-public class DeleteCollectionSnapshotResponse extends AsyncJerseyResponse {
-  @Schema(description = "The name of the collection.")
-  @JsonProperty(COLLECTION)
-  public String collection;
+public class CreateCollectionBackupResponseBody extends SubResponseAccumulatingJerseyResponse {
+  @JsonProperty("response")
+  public CreateCollectionBackupDetails backupDataResponse;
 
-  @Schema(description = "The name of the snapshot to be deleted.")
-  @JsonProperty("snapshot")
-  public String snapshotName;
+  @JsonProperty("deleted")
+  public List<BackupDeletionData> deleted;
 
-  @Schema(description = "A flag that treats the collName parameter as a collection alias.")
-  @JsonProperty("followAliases")
-  public boolean followAliases;
+  @JsonProperty public String collection;
 }
diff --git a/solr/api/src/java/org/apache/solr/client/api/model/DeleteCollectionSnapshotResponse.java b/solr/api/src/java/org/apache/solr/client/api/model/CreateCollectionSnapshotRequestBody.java
similarity index 55%
copy from solr/api/src/java/org/apache/solr/client/api/model/DeleteCollectionSnapshotResponse.java
copy to solr/api/src/java/org/apache/solr/client/api/model/CreateCollectionSnapshotRequestBody.java
index 2b134a2877c..b6061e099dc 100644
--- a/solr/api/src/java/org/apache/solr/client/api/model/DeleteCollectionSnapshotResponse.java
+++ b/solr/api/src/java/org/apache/solr/client/api/model/CreateCollectionSnapshotRequestBody.java
@@ -14,29 +14,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package org.apache.solr.client.api.model;
 
-import static org.apache.solr.client.api.model.Constants.COLLECTION;
-
 import com.fasterxml.jackson.annotation.JsonProperty;
-import io.swagger.v3.oas.annotations.media.Schema;
-import org.apache.solr.client.api.endpoint.DeleteCollectionSnapshotApi;
-
-/**
- * The Response for {@link DeleteCollectionSnapshotApi#deleteSnapshot(String, String, boolean,
- * String)}
- */
-public class DeleteCollectionSnapshotResponse extends AsyncJerseyResponse {
-  @Schema(description = "The name of the collection.")
-  @JsonProperty(COLLECTION)
-  public String collection;
-
-  @Schema(description = "The name of the snapshot to be deleted.")
-  @JsonProperty("snapshot")
-  public String snapshotName;
 
-  @Schema(description = "A flag that treats the collName parameter as a collection alias.")
-  @JsonProperty("followAliases")
+public class CreateCollectionSnapshotRequestBody {
+  @JsonProperty(value = "followAliases", defaultValue = "false")
   public boolean followAliases;
+
+  public String async;
 }
diff --git a/solr/api/src/java/org/apache/solr/client/api/model/DeleteCollectionSnapshotResponse.java b/solr/api/src/java/org/apache/solr/client/api/model/CreateCollectionSnapshotResponse.java
similarity index 73%
copy from solr/api/src/java/org/apache/solr/client/api/model/DeleteCollectionSnapshotResponse.java
copy to solr/api/src/java/org/apache/solr/client/api/model/CreateCollectionSnapshotResponse.java
index 2b134a2877c..16944706498 100644
--- a/solr/api/src/java/org/apache/solr/client/api/model/DeleteCollectionSnapshotResponse.java
+++ b/solr/api/src/java/org/apache/solr/client/api/model/CreateCollectionSnapshotResponse.java
@@ -14,29 +14,19 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package org.apache.solr.client.api.model;
 
-import static org.apache.solr.client.api.model.Constants.COLLECTION;
-
 import com.fasterxml.jackson.annotation.JsonProperty;
 import io.swagger.v3.oas.annotations.media.Schema;
-import org.apache.solr.client.api.endpoint.DeleteCollectionSnapshotApi;
 
-/**
- * The Response for {@link DeleteCollectionSnapshotApi#deleteSnapshot(String, String, boolean,
- * String)}
- */
-public class DeleteCollectionSnapshotResponse extends AsyncJerseyResponse {
+public class CreateCollectionSnapshotResponse extends AsyncJerseyResponse {
   @Schema(description = "The name of the collection.")
-  @JsonProperty(COLLECTION)
   public String collection;
 
-  @Schema(description = "The name of the snapshot to be deleted.")
+  @Schema(description = "The name of the snapshot to be created.")
   @JsonProperty("snapshot")
   public String snapshotName;
 
   @Schema(description = "A flag that treats the collName parameter as a collection alias.")
-  @JsonProperty("followAliases")
   public boolean followAliases;
 }
diff --git a/solr/api/src/java/org/apache/solr/client/api/model/DeleteCollectionSnapshotResponse.java b/solr/api/src/java/org/apache/solr/client/api/model/CreateCoreBackupRequestBody.java
similarity index 51%
copy from solr/api/src/java/org/apache/solr/client/api/model/DeleteCollectionSnapshotResponse.java
copy to solr/api/src/java/org/apache/solr/client/api/model/CreateCoreBackupRequestBody.java
index 2b134a2877c..31e92c67d46 100644
--- a/solr/api/src/java/org/apache/solr/client/api/model/DeleteCollectionSnapshotResponse.java
+++ b/solr/api/src/java/org/apache/solr/client/api/model/CreateCoreBackupRequestBody.java
@@ -14,29 +14,37 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package org.apache.solr.client.api.model;
 
-import static org.apache.solr.client.api.model.Constants.COLLECTION;
-
 import com.fasterxml.jackson.annotation.JsonProperty;
 import io.swagger.v3.oas.annotations.media.Schema;
-import org.apache.solr.client.api.endpoint.DeleteCollectionSnapshotApi;
 
-/**
- * The Response for {@link DeleteCollectionSnapshotApi#deleteSnapshot(String, String, boolean,
- * String)}
- */
-public class DeleteCollectionSnapshotResponse extends AsyncJerseyResponse {
-  @Schema(description = "The name of the collection.")
-  @JsonProperty(COLLECTION)
-  public String collection;
-
-  @Schema(description = "The name of the snapshot to be deleted.")
-  @JsonProperty("snapshot")
-  public String snapshotName;
-
-  @Schema(description = "A flag that treats the collName parameter as a collection alias.")
-  @JsonProperty("followAliases")
-  public boolean followAliases;
+public class CreateCoreBackupRequestBody {
+
+  @Schema(description = "The name of the repository to be used for backup.")
+  public String repository;
+
+  @Schema(description = "The path where the backup will be created")
+  public String location;
+
+  public String shardBackupId;
+
+  public String prevShardBackupId;
+
+  @Schema(
+      description = "A descriptive name for the backup.  Only used by non-incremental backups.",
+      name = "backupName")
+  @JsonProperty("name")
+  public String backupName;
+
+  @Schema(
+      description =
+          "The name of the commit which was used while taking a snapshot using the CREATESNAPSHOT command.")
+  public String commitName;
+
+  @Schema(description = "To turn on incremental backup feature")
+  public Boolean incremental;
+
+  @Schema(description = "Request ID to track this action which will be processed asynchronously.")
+  public String async;
 }
diff --git a/solr/api/src/java/org/apache/solr/client/api/model/DeleteCollectionSnapshotResponse.java b/solr/api/src/java/org/apache/solr/client/api/model/DeleteCollectionSnapshotResponse.java
index 2b134a2877c..569d4fbd096 100644
--- a/solr/api/src/java/org/apache/solr/client/api/model/DeleteCollectionSnapshotResponse.java
+++ b/solr/api/src/java/org/apache/solr/client/api/model/DeleteCollectionSnapshotResponse.java
@@ -24,8 +24,8 @@ import io.swagger.v3.oas.annotations.media.Schema;
 import org.apache.solr.client.api.endpoint.DeleteCollectionSnapshotApi;
 
 /**
- * The Response for {@link DeleteCollectionSnapshotApi#deleteSnapshot(String, String, boolean,
- * String)}
+ * The Response for {@link DeleteCollectionSnapshotApi#deleteCollectionSnapshot(String, String,
+ * boolean, String)}
  */
 public class DeleteCollectionSnapshotResponse extends AsyncJerseyResponse {
   @Schema(description = "The name of the collection.")
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/BackupCoreOp.java b/solr/core/src/java/org/apache/solr/handler/admin/BackupCoreOp.java
index 788fb494757..7e63b9aed7a 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/BackupCoreOp.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/BackupCoreOp.java
@@ -17,13 +17,14 @@
 
 package org.apache.solr.handler.admin;
 
+import org.apache.solr.client.api.model.CreateCoreBackupRequestBody;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.params.CoreAdminParams;
 import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.SimpleOrderedMap;
 import org.apache.solr.core.backup.ShardBackupId;
-import org.apache.solr.handler.admin.api.BackupCoreAPI;
+import org.apache.solr.handler.admin.api.CreateCoreBackup;
 import org.apache.solr.handler.api.V2ApiUtils;
 
 class BackupCoreOp implements CoreAdminHandler.CoreAdminOp {
@@ -36,8 +37,7 @@ class BackupCoreOp implements CoreAdminHandler.CoreAdminOp {
   @Override
   public void execute(CoreAdminHandler.CallInfo it) throws Exception {
     final SolrParams params = it.req.getParams();
-    BackupCoreAPI.BackupCoreRequestBody backupCoreRequestBody =
-        new BackupCoreAPI.BackupCoreRequestBody();
+    CreateCoreBackupRequestBody backupCoreRequestBody = new CreateCoreBackupRequestBody();
     backupCoreRequestBody.repository = params.get(CoreAdminParams.BACKUP_REPOSITORY);
     backupCoreRequestBody.location = params.get(CoreAdminParams.BACKUP_LOCATION);
     // An optional parameter to describe the snapshot to be backed-up. If this
@@ -53,8 +53,8 @@ class BackupCoreOp implements CoreAdminHandler.CoreAdminOp {
           params.get(CoreAdminParams.PREV_SHARD_BACKUP_ID, null);
       backupCoreRequestBody.incremental = true;
     }
-    BackupCoreAPI backupCoreAPI =
-        new BackupCoreAPI(
+    CreateCoreBackup backupCoreAPI =
+        new CreateCoreBackup(
             it.handler.coreContainer, it.req, it.rsp, it.handler.coreAdminAsyncTracker);
     try {
       final var response = backupCoreAPI.createBackup(cname, backupCoreRequestBody);
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 f01e8efa02c..50087cdf1a5 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,8 @@ 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.CreateCollectionSnapshotRequestBody;
+import org.apache.solr.client.api.model.CreateCollectionSnapshotResponse;
 import org.apache.solr.client.api.model.InstallShardDataRequestBody;
 import org.apache.solr.client.api.model.ReplaceNodeRequestBody;
 import org.apache.solr.client.api.model.SolrJerseyResponse;
@@ -170,13 +172,13 @@ import org.apache.solr.handler.admin.api.AddReplicaProperty;
 import org.apache.solr.handler.admin.api.AdminAPIBase;
 import org.apache.solr.handler.admin.api.AliasProperty;
 import org.apache.solr.handler.admin.api.BalanceReplicas;
-import org.apache.solr.handler.admin.api.BalanceShardUniqueAPI;
+import org.apache.solr.handler.admin.api.BalanceShardUnique;
 import org.apache.solr.handler.admin.api.CollectionProperty;
 import org.apache.solr.handler.admin.api.CollectionStatusAPI;
 import org.apache.solr.handler.admin.api.CreateAliasAPI;
 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.CreateCollectionBackup;
+import org.apache.solr.handler.admin.api.CreateCollectionSnapshot;
 import org.apache.solr.handler.admin.api.CreateReplica;
 import org.apache.solr.handler.admin.api.CreateShard;
 import org.apache.solr.handler.admin.api.DeleteAlias;
@@ -1022,7 +1024,7 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
     BALANCESHARDUNIQUE_OP(
         BALANCESHARDUNIQUE,
         (req, rsp, h) -> {
-          BalanceShardUniqueAPI.invokeFromV1Params(h.coreContainer, req, rsp);
+          BalanceShardUnique.invokeFromV1Params(h.coreContainer, req, rsp);
           return null;
         }),
     REBALANCELEADERS_OP(
@@ -1062,8 +1064,7 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
     BACKUP_OP(
         BACKUP,
         (req, rsp, h) -> {
-          final var response =
-              CreateCollectionBackupAPI.invokeFromV1Params(req, rsp, h.coreContainer);
+          final var response = CreateCollectionBackup.invokeFromV1Params(req, rsp, h.coreContainer);
           V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, response);
           return null;
         }),
@@ -1114,16 +1115,16 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
           final String commitName = req.getParams().get(CoreAdminParams.COMMIT_NAME);
           final String asyncId = req.getParams().get(ASYNC);
 
-          final CreateCollectionSnapshotAPI createCollectionSnapshotAPI =
-              new CreateCollectionSnapshotAPI(h.coreContainer, req, rsp);
+          final CreateCollectionSnapshot createCollectionSnapshotAPI =
+              new CreateCollectionSnapshot(h.coreContainer, req, rsp);
 
-          final CreateCollectionSnapshotAPI.CreateSnapshotRequestBody requestBody =
-              new CreateCollectionSnapshotAPI.CreateSnapshotRequestBody();
+          final CreateCollectionSnapshotRequestBody requestBody =
+              new CreateCollectionSnapshotRequestBody();
           requestBody.followAliases = followAliases;
-          requestBody.asyncId = asyncId;
+          requestBody.async = asyncId;
 
-          final CreateCollectionSnapshotAPI.CreateSnapshotResponse createSnapshotResponse =
-              createCollectionSnapshotAPI.createSnapshot(
+          final CreateCollectionSnapshotResponse createSnapshotResponse =
+              createCollectionSnapshotAPI.createCollectionSnapshot(
                   extCollectionName, commitName, requestBody);
 
           V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, createSnapshotResponse);
@@ -1144,7 +1145,7 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
               new DeleteCollectionSnapshot(h.coreContainer, req, rsp);
 
           final var deleteSnapshotResponse =
-              deleteCollectionSnapshotAPI.deleteSnapshot(
+              deleteCollectionSnapshotAPI.deleteCollectionSnapshot(
                   extCollectionName, commitName, followAliases, asyncId);
 
           V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, deleteSnapshotResponse);
@@ -1362,10 +1363,10 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
     return List.of(
         CreateReplica.class,
         AddReplicaProperty.class,
-        BalanceShardUniqueAPI.class,
+        BalanceShardUnique.class,
         CreateAliasAPI.class,
         CreateCollection.class,
-        CreateCollectionBackupAPI.class,
+        CreateCollectionBackup.class,
         CreateShard.class,
         DeleteAlias.class,
         DeleteCollectionBackup.class,
@@ -1389,7 +1390,7 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
         ListAliases.class,
         AliasProperty.class,
         ListCollectionSnapshotsAPI.class,
-        CreateCollectionSnapshotAPI.class,
+        CreateCollectionSnapshot.class,
         DeleteCollectionSnapshot.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 6ea7fe8e7aa..e45a250f81b 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
@@ -55,9 +55,9 @@ import org.apache.solr.core.CoreContainer;
 import org.apache.solr.core.CoreDescriptor;
 import org.apache.solr.handler.RequestHandlerBase;
 import org.apache.solr.handler.admin.api.AllCoresStatusAPI;
-import org.apache.solr.handler.admin.api.BackupCoreAPI;
 import org.apache.solr.handler.admin.api.CoreSnapshot;
 import org.apache.solr.handler.admin.api.CreateCoreAPI;
+import org.apache.solr.handler.admin.api.CreateCoreBackup;
 import org.apache.solr.handler.admin.api.InstallCoreData;
 import org.apache.solr.handler.admin.api.MergeIndexesAPI;
 import org.apache.solr.handler.admin.api.OverseerOperationAPI;
@@ -401,7 +401,7 @@ public class CoreAdminHandler extends RequestHandlerBase implements PermissionNa
     return List.of(
         CoreSnapshot.class,
         InstallCoreData.class,
-        BackupCoreAPI.class,
+        CreateCoreBackup.class,
         RestoreCore.class,
         ReloadCore.class,
         UnloadCore.class,
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/BalanceShardUniqueAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/BalanceShardUnique.java
similarity index 77%
rename from solr/core/src/java/org/apache/solr/handler/admin/api/BalanceShardUniqueAPI.java
rename to solr/core/src/java/org/apache/solr/handler/admin/api/BalanceShardUnique.java
index 62324f208c3..149fa1f4105 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/BalanceShardUniqueAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/BalanceShardUnique.java
@@ -16,7 +16,6 @@
  */
 package org.apache.solr.handler.admin.api;
 
-import static org.apache.solr.client.solrj.impl.BinaryResponseParser.BINARY_CONTENT_TYPE_V2;
 import static org.apache.solr.cloud.Overseer.QUEUE_OPERATION;
 import static org.apache.solr.cloud.api.collections.CollectionHandlingUtils.ONLY_ACTIVE_NODES;
 import static org.apache.solr.cloud.api.collections.CollectionHandlingUtils.SHARD_UNIQUE;
@@ -30,47 +29,37 @@ import java.util.HashMap;
 import java.util.Locale;
 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.BalanceShardUniqueApi;
+import org.apache.solr.client.api.model.BalanceShardUniqueRequestBody;
 import org.apache.solr.client.api.model.SubResponseAccumulatingJerseyResponse;
 import org.apache.solr.cloud.overseer.SliceMutator;
 import org.apache.solr.common.SolrException;
-import org.apache.solr.common.annotation.JsonProperty;
 import org.apache.solr.common.cloud.ZkNodeProps;
 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;
 
 /**
- * V2 API for insuring that a particular property is distributed evenly amongst the physical nodes
- * comprising a collection.
- *
- * <p>The new API (POST /v2/collections/collectionName/balance-shard-unique {...} ) is analogous to
- * the v1 /admin/collections?action=BALANCESHARDUNIQUE command.
+ * V2 API implementation for insuring that a particular property is distributed evenly amongst the
+ * physical nodes comprising a collection.
  */
-@Path("/collections/{collectionName}/balance-shard-unique")
-public class BalanceShardUniqueAPI extends AdminAPIBase {
+public class BalanceShardUnique extends AdminAPIBase implements BalanceShardUniqueApi {
   @Inject
-  public BalanceShardUniqueAPI(
+  public BalanceShardUnique(
       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 balanceShardUnique(
-      @PathParam("collectionName") String collectionName, BalanceShardUniqueRequestBody requestBody)
-      throws Exception {
+      String collectionName, BalanceShardUniqueRequestBody requestBody) throws Exception {
     final var response = instantiateJerseyResponse(SubResponseAccumulatingJerseyResponse.class);
     if (requestBody == null) {
       throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Missing required request body");
@@ -86,7 +75,7 @@ public class BalanceShardUniqueAPI extends AdminAPIBase {
         response,
         CollectionParams.CollectionAction.BALANCESHARDUNIQUE,
         remoteMessage,
-        requestBody.asyncId);
+        requestBody.async);
 
     return response;
   }
@@ -100,7 +89,7 @@ public class BalanceShardUniqueAPI extends AdminAPIBase {
     remoteMessage.put(PROPERTY_PROP, requestBody.property);
     insertIfNotNull(remoteMessage, ONLY_ACTIVE_NODES, requestBody.onlyActiveNodes);
     insertIfNotNull(remoteMessage, SHARD_UNIQUE, requestBody.shardUnique);
-    insertIfNotNull(remoteMessage, ASYNC, requestBody.asyncId);
+    insertIfNotNull(remoteMessage, ASYNC, requestBody.async);
 
     return new ZkNodeProps(remoteMessage);
   }
@@ -110,7 +99,7 @@ public class BalanceShardUniqueAPI extends AdminAPIBase {
       SolrQueryRequest solrQueryRequest,
       SolrQueryResponse solrQueryResponse)
       throws Exception {
-    final var api = new BalanceShardUniqueAPI(coreContainer, solrQueryRequest, solrQueryResponse);
+    final var api = new BalanceShardUnique(coreContainer, solrQueryRequest, solrQueryResponse);
     final SolrParams params = solrQueryRequest.getParams();
     params.required().check(COLLECTION_PROP, PROPERTY_PROP);
 
@@ -119,25 +108,12 @@ public class BalanceShardUniqueAPI extends AdminAPIBase {
     requestBody.property = params.get(PROPERTY_PROP);
     requestBody.onlyActiveNodes = params.getBool(ONLY_ACTIVE_NODES);
     requestBody.shardUnique = params.getBool(SHARD_UNIQUE);
-    requestBody.asyncId = params.get(ASYNC);
+    requestBody.async = params.get(ASYNC);
 
     V2ApiUtils.squashIntoSolrResponseWithoutHeader(
         solrQueryResponse, api.balanceShardUnique(collection, requestBody));
   }
 
-  public static class BalanceShardUniqueRequestBody implements JacksonReflectMapWriter {
-    @JsonProperty(required = true)
-    public String property;
-
-    @JsonProperty(ONLY_ACTIVE_NODES)
-    public Boolean onlyActiveNodes;
-
-    @JsonProperty public Boolean shardUnique;
-
-    @JsonProperty(ASYNC)
-    public String asyncId;
-  }
-
   private void validatePropertyToBalance(String prop, boolean shardUnique) {
     prop = prop.toLowerCase(Locale.ROOT);
     if (!prop.startsWith(PROPERTY_PREFIX)) {
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollection.java b/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollection.java
index 8ce1eca623e..2de8917df6a 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollection.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollection.java
@@ -369,9 +369,7 @@ public class CreateCollection extends AdminAPIBase implements CreateCollectionAp
         case ROUTER_KEY:
           final var routerProperties =
               (CreateCollectionRouterProperties) v2MapVals.remove(ROUTER_KEY);
-          final Map<String, Object> routerPropertiesAsMap =
-              ((Utils.DelegateReflectWriter) Utils.getReflectWriter(routerProperties))
-                  .toMap(new HashMap<>());
+          final Map<String, Object> routerPropertiesAsMap = Utils.reflectToMap(routerProperties);
           flattenMapWithPrefix(
               routerPropertiesAsMap, v2MapVals, CollectionAdminParams.ROUTER_PREFIX);
           break;
@@ -406,8 +404,7 @@ public class CreateCollection extends AdminAPIBase implements CreateCollectionAp
 
   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<>());
+    final Map<String, Object> v1Params = Utils.reflectToMap(requestBody);
     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/CreateCollectionBackup.java
similarity index 72%
rename from solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionBackupAPI.java
rename to solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionBackup.java
index d37688f2162..b85e766c7f8 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/CreateCollectionBackup.java
@@ -17,7 +17,6 @@
 
 package org.apache.solr.handler.admin.api;
 
-import static org.apache.solr.client.solrj.impl.BinaryResponseParser.BINARY_CONTENT_TYPE_V2;
 import static org.apache.solr.cloud.Overseer.QUEUE_OPERATION;
 import static org.apache.solr.common.cloud.ZkStateReader.COLLECTION_PROP;
 import static org.apache.solr.common.params.CollectionAdminParams.FOLLOW_ALIASES;
@@ -35,45 +34,35 @@ import static org.apache.solr.handler.admin.CollectionsHandler.DEFAULT_COLLECTIO
 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 com.fasterxml.jackson.databind.ObjectMapper;
 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.model.BackupDeletionData;
+import org.apache.solr.client.api.endpoint.CreateCollectionBackupApi;
+import org.apache.solr.client.api.model.CreateCollectionBackupRequestBody;
+import org.apache.solr.client.api.model.CreateCollectionBackupResponseBody;
 import org.apache.solr.client.api.model.SolrJerseyResponse;
-import org.apache.solr.client.api.model.SubResponseAccumulatingJerseyResponse;
 import org.apache.solr.client.solrj.SolrResponse;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.cloud.ZkNodeProps;
 import org.apache.solr.common.params.CollectionAdminParams;
 import org.apache.solr.common.params.CollectionParams;
 import org.apache.solr.common.params.SolrParams;
+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.jersey.SolrJacksonMapper;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 import org.apache.zookeeper.common.StringUtils;
 
-/**
- * V2 API for creating a new "backup" of a specified collection
- *
- * <p>This API is analogous to the v1 /admin/collections?action=BACKUP command.
- */
-@Path("/collections/{collectionName}/backups/{backupName}/versions")
-public class CreateCollectionBackupAPI extends BackupAPIBase {
+/** V2 API for creating a new "backup" of a specified collection */
+public class CreateCollectionBackup extends BackupAPIBase implements CreateCollectionBackupApi {
   private final ObjectMapper objectMapper;
 
   @Inject
-  public CreateCollectionBackupAPI(
+  public CreateCollectionBackup(
       CoreContainer coreContainer,
       SolrQueryRequest solrQueryRequest,
       SolrQueryResponse solrQueryResponse) {
@@ -82,13 +71,10 @@ public class CreateCollectionBackupAPI extends BackupAPIBase {
     this.objectMapper = SolrJacksonMapper.getObjectMapper();
   }
 
-  @POST
-  @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2})
+  @Override
   @PermissionName(COLL_EDIT_PERM)
   public SolrJerseyResponse createCollectionBackup(
-      @PathParam("collectionName") String collectionName,
-      @PathParam("backupName") String backupName,
-      CreateCollectionBackupRequestBody requestBody)
+      String collectionName, String backupName, CreateCollectionBackupRequestBody requestBody)
       throws Exception {
     if (requestBody == null) {
       throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Missing required request body");
@@ -144,7 +130,7 @@ public class CreateCollectionBackupAPI extends BackupAPIBase {
 
   public static ZkNodeProps createRemoteMessage(
       String collectionName, String backupName, CreateCollectionBackupRequestBody requestBody) {
-    final Map<String, Object> remoteMessage = requestBody.toMap(new HashMap<>());
+    final Map<String, Object> remoteMessage = Utils.reflectToMap(requestBody);
     remoteMessage.put(QUEUE_OPERATION, CollectionParams.CollectionAction.BACKUP.toLower());
     remoteMessage.put(COLLECTION_PROP, collectionName);
     remoteMessage.put(NAME, backupName);
@@ -158,7 +144,7 @@ public class CreateCollectionBackupAPI extends BackupAPIBase {
   }
 
   public static CreateCollectionBackupRequestBody createRequestBodyFromV1Params(SolrParams params) {
-    var requestBody = new CreateCollectionBackupAPI.CreateCollectionBackupRequestBody();
+    var requestBody = new CreateCollectionBackupRequestBody();
 
     requestBody.location = params.get(BACKUP_LOCATION);
     requestBody.repository = params.get(BACKUP_REPOSITORY);
@@ -183,49 +169,7 @@ public class CreateCollectionBackupAPI extends BackupAPIBase {
     final var backupName = req.getParams().get(NAME);
     final var requestBody = createRequestBodyFromV1Params(req.getParams());
 
-    final var createBackupApi = new CreateCollectionBackupAPI(coreContainer, req, rsp);
+    final var createBackupApi = new CreateCollectionBackup(coreContainer, req, rsp);
     return createBackupApi.createCollectionBackup(collectionName, backupName, requestBody);
   }
-
-  public static class CreateCollectionBackupRequestBody implements JacksonReflectMapWriter {
-    @JsonProperty public String location;
-    @JsonProperty public String repository;
-    @JsonProperty public Boolean followAliases;
-    @JsonProperty public String backupStrategy;
-    @JsonProperty public String snapshotName;
-    @JsonProperty public Boolean incremental;
-    @JsonProperty public Boolean backupConfigset;
-    @JsonProperty public Integer maxNumBackupPoints;
-    @JsonProperty public String async;
-    @JsonProperty public Map<String, String> extraProperties;
-  }
-
-  public static class CreateCollectionBackupResponseBody
-      extends SubResponseAccumulatingJerseyResponse {
-    @JsonProperty("response")
-    public CollectionBackupDetails backupDataResponse;
-
-    @JsonProperty("deleted")
-    public List<BackupDeletionData> deleted;
-
-    @JsonProperty public String collection;
-  }
-
-  public static class CollectionBackupDetails implements JacksonReflectMapWriter {
-    @JsonProperty public String collection;
-    @JsonProperty public Integer numShards;
-    @JsonProperty public Integer backupId;
-    @JsonProperty public String indexVersion;
-    @JsonProperty public String startTime;
-    @JsonProperty public String endTime;
-    @JsonProperty public Integer indexFileCount;
-    @JsonProperty public Integer uploadedIndexFileCount;
-    @JsonProperty public Double indexSizeMB;
-    @JsonProperty public Map<String, String> extraProperties;
-
-    @JsonProperty("uploadedIndexFileMB")
-    public Double uploadedIndexSizeMB;
-
-    @JsonProperty public List<String> shardBackupIds;
-  }
 }
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionSnapshotAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionSnapshot.java
similarity index 63%
rename from solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionSnapshotAPI.java
rename to solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionSnapshot.java
index e619ccf442b..52cc1ab0427 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionSnapshotAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionSnapshot.java
@@ -16,7 +16,6 @@
  */
 package org.apache.solr.handler.admin.api;
 
-import static org.apache.solr.client.solrj.impl.BinaryResponseParser.BINARY_CONTENT_TYPE_V2;
 import static org.apache.solr.cloud.Overseer.QUEUE_OPERATION;
 import static org.apache.solr.common.cloud.ZkStateReader.COLLECTION_PROP;
 import static org.apache.solr.common.params.CollectionAdminParams.FOLLOW_ALIASES;
@@ -24,18 +23,12 @@ import static org.apache.solr.common.params.CommonAdminParams.ASYNC;
 import static org.apache.solr.handler.admin.CollectionsHandler.DEFAULT_COLLECTION_OP_TIMEOUT;
 import static org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM;
 
-import com.fasterxml.jackson.annotation.JsonProperty;
-import io.swagger.v3.oas.annotations.Parameter;
-import io.swagger.v3.oas.annotations.media.Schema;
-import io.swagger.v3.oas.annotations.parameters.RequestBody;
 import java.util.HashMap;
 import java.util.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.model.AsyncJerseyResponse;
+import org.apache.solr.client.api.endpoint.CreateCollectionSnapshotApi;
+import org.apache.solr.client.api.model.CreateCollectionSnapshotRequestBody;
+import org.apache.solr.client.api.model.CreateCollectionSnapshotResponse;
 import org.apache.solr.client.solrj.SolrResponse;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.cloud.SolrZkClient;
@@ -45,17 +38,15 @@ import org.apache.solr.common.params.CoreAdminParams;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.core.snapshots.SolrSnapshotManager;
 import org.apache.solr.handler.admin.CollectionsHandler;
-import org.apache.solr.jersey.JacksonReflectMapWriter;
 import org.apache.solr.jersey.PermissionName;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 
-/** V2 API for Creating Collection Snapshots. */
-@Path("/collections/{collName}/snapshots")
-public class CreateCollectionSnapshotAPI extends AdminAPIBase {
+/** V2 API implementation for creating a collection-level snapshot. */
+public class CreateCollectionSnapshot extends AdminAPIBase implements CreateCollectionSnapshotApi {
 
   @Inject
-  public CreateCollectionSnapshotAPI(
+  public CreateCollectionSnapshot(
       CoreContainer coreContainer,
       SolrQueryRequest solrQueryRequest,
       SolrQueryResponse solrQueryResponse) {
@@ -63,22 +54,13 @@ public class CreateCollectionSnapshotAPI extends AdminAPIBase {
   }
 
   /** This API is analogous to V1's (POST /solr/admin/collections?action=CREATESNAPSHOT) */
-  @POST
-  @Path("/{snapshotName}")
-  @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2})
+  @Override
   @PermissionName(COLL_EDIT_PERM)
-  public CreateSnapshotResponse createSnapshot(
-      @Parameter(description = "The name of the collection.", required = true)
-          @PathParam("collName")
-          String collName,
-      @Parameter(description = "The name of the snapshot to be created.", required = true)
-          @PathParam("snapshotName")
-          String snapshotName,
-      @RequestBody(description = "Contains user provided parameters", required = true)
-          CreateSnapshotRequestBody requestBody)
+  public CreateCollectionSnapshotResponse createCollectionSnapshot(
+      String collName, String snapshotName, CreateCollectionSnapshotRequestBody requestBody)
       throws Exception {
-
-    final CreateSnapshotResponse response = instantiateJerseyResponse(CreateSnapshotResponse.class);
+    final CreateCollectionSnapshotResponse response =
+        instantiateJerseyResponse(CreateCollectionSnapshotResponse.class);
     final CoreContainer coreContainer = fetchAndValidateZooKeeperAwareCoreContainer();
     recordCollectionForLogAndTracing(collName, solrQueryRequest);
 
@@ -96,7 +78,7 @@ public class CreateCollectionSnapshotAPI extends AdminAPIBase {
     }
 
     final ZkNodeProps remoteMessage =
-        createRemoteMessage(collName, requestBody.followAliases, snapshotName, requestBody.asyncId);
+        createRemoteMessage(collName, requestBody.followAliases, snapshotName, requestBody.async);
     final SolrResponse remoteResponse =
         CollectionsHandler.submitCollectionApiCommand(
             coreContainer,
@@ -112,41 +94,11 @@ public class CreateCollectionSnapshotAPI extends AdminAPIBase {
     response.collection = collName;
     response.followAliases = requestBody.followAliases;
     response.snapshotName = snapshotName;
-    response.requestId = requestBody.asyncId;
+    response.requestId = requestBody.async;
 
     return response;
   }
 
-  /**
-   * The RequestBody for {@link CreateCollectionSnapshotAPI}'s {@link #createSnapshot(String,
-   * String, CreateSnapshotRequestBody)}
-   */
-  public static class CreateSnapshotRequestBody implements JacksonReflectMapWriter {
-    @JsonProperty(value = "followAliases", defaultValue = "false")
-    public boolean followAliases;
-
-    @JsonProperty("async")
-    public String asyncId;
-  }
-
-  /**
-   * The Response for {@link CreateCollectionSnapshotAPI}'s {@link #createSnapshot(String, String,
-   * CreateSnapshotRequestBody)}
-   */
-  public static class CreateSnapshotResponse extends AsyncJerseyResponse {
-    @Schema(description = "The name of the collection.")
-    @JsonProperty(COLLECTION_PROP)
-    String collection;
-
-    @Schema(description = "The name of the snapshot to be created.")
-    @JsonProperty("snapshot")
-    String snapshotName;
-
-    @Schema(description = "A flag that treats the collName parameter as a collection alias.")
-    @JsonProperty("followAliases")
-    boolean followAliases;
-  }
-
   public static ZkNodeProps createRemoteMessage(
       String collectionName, boolean followAliases, String snapshotName, String asyncId) {
     final Map<String, Object> remoteMessage = new HashMap<>();
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/BackupCoreAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCoreBackup.java
similarity index 73%
rename from solr/core/src/java/org/apache/solr/handler/admin/api/BackupCoreAPI.java
rename to solr/core/src/java/org/apache/solr/handler/admin/api/CreateCoreBackup.java
index 580f2849b2f..08cfb9e3f13 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/BackupCoreAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCoreBackup.java
@@ -16,21 +16,14 @@
  */
 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.COLL_EDIT_PERM;
 
-import com.fasterxml.jackson.annotation.JsonProperty;
-import io.swagger.v3.oas.annotations.Parameter;
-import io.swagger.v3.oas.annotations.media.Schema;
-import io.swagger.v3.oas.annotations.parameters.RequestBody;
 import java.net.URI;
 import java.nio.file.Paths;
 import java.util.Optional;
 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.CreateCoreBackupApi;
+import org.apache.solr.client.api.model.CreateCoreBackupRequestBody;
 import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.core.CoreContainer;
@@ -45,10 +38,9 @@ import org.apache.solr.jersey.PermissionName;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 
-@Path("/cores/{coreName}/backups")
-public class BackupCoreAPI extends CoreAdminAPIBase {
+public class CreateCoreBackup extends CoreAdminAPIBase implements CreateCoreBackupApi {
   @Inject
-  public BackupCoreAPI(
+  public CreateCoreBackup(
       CoreContainer coreContainer,
       SolrQueryRequest solrQueryRequest,
       SolrQueryResponse solrQueryResponse,
@@ -61,14 +53,10 @@ public class BackupCoreAPI extends CoreAdminAPIBase {
     return true;
   }
 
-  @POST
-  @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2})
+  @Override
   @PermissionName(COLL_EDIT_PERM)
   public SolrJerseyResponse createBackup(
-      @Parameter(description = "The name of the core.") @PathParam("coreName") String coreName,
-      @Schema(description = "The POJO for representing additional backup params.") @RequestBody
-          BackupCoreRequestBody backupCoreRequestBody)
-      throws Exception {
+      String coreName, CreateCoreBackupRequestBody backupCoreRequestBody) throws Exception {
     ensureRequiredParameterProvided("coreName", coreName);
     return handlePotentiallyAsynchronousTask(
         null,
@@ -143,40 +131,4 @@ public class BackupCoreAPI extends CoreAdminAPIBase {
           }
         });
   }
-
-  public static class BackupCoreRequestBody extends SolrJerseyResponse {
-
-    @Schema(description = "The name of the repository to be used for backup.")
-    @JsonProperty("repository")
-    public String repository;
-
-    @Schema(description = "The path where the backup will be created")
-    @JsonProperty("location")
-    public String location;
-
-    @JsonProperty("shardBackupId")
-    public String shardBackupId;
-
-    @JsonProperty("prevShardBackupId")
-    public String prevShardBackupId;
-
-    @Schema(
-        description = "A descriptive name for the backup.  Only used by non-incremental backups.")
-    @JsonProperty("name")
-    public String backupName;
-
-    @Schema(
-        description =
-            "The name of the commit which was used while taking a snapshot using the CREATESNAPSHOT command.")
-    @JsonProperty("commitName")
-    public String commitName;
-
-    @Schema(description = "To turn on incremental backup feature")
-    @JsonProperty("incremental")
-    public Boolean incremental;
-
-    @Schema(description = "Request ID to track this action which will be processed asynchronously.")
-    @JsonProperty("async")
-    public String async;
-  }
 }
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionSnapshot.java b/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionSnapshot.java
index e591f85db6f..1ebf7944a08 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionSnapshot.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionSnapshot.java
@@ -51,7 +51,7 @@ public class DeleteCollectionSnapshot extends AdminAPIBase implements DeleteColl
 
   @Override
   @PermissionName(COLL_EDIT_PERM)
-  public DeleteCollectionSnapshotResponse deleteSnapshot(
+  public DeleteCollectionSnapshotResponse deleteCollectionSnapshot(
       String collName, String snapshotName, boolean followAliases, String asyncId)
       throws Exception {
     final var response = instantiateJerseyResponse(DeleteCollectionSnapshotResponse.class);
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/BackupCoreAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/BackupCoreAPITest.java
index 4504d6d77a2..b66fb6d04b9 100644
--- a/solr/core/src/test/org/apache/solr/handler/admin/api/BackupCoreAPITest.java
+++ b/solr/core/src/test/org/apache/solr/handler/admin/api/BackupCoreAPITest.java
@@ -22,6 +22,7 @@ import java.net.URI;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.client.api.model.CreateCoreBackupRequestBody;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.core.backup.BackupFilePaths;
@@ -38,7 +39,7 @@ import org.junit.Test;
 
 public class BackupCoreAPITest extends SolrTestCaseJ4 {
 
-  private BackupCoreAPI backupCoreAPI;
+  private CreateCoreBackup backupCoreAPI;
   private static final String backupName = "my-new-backup";
 
   @BeforeClass
@@ -58,13 +59,13 @@ public class BackupCoreAPITest extends SolrTestCaseJ4 {
     CoreAdminHandler.CoreAdminAsyncTracker coreAdminAsyncTracker =
         new CoreAdminHandler.CoreAdminAsyncTracker();
     backupCoreAPI =
-        new BackupCoreAPI(
+        new CreateCoreBackup(
             coreContainer, solrQueryRequest, solrQueryResponse, coreAdminAsyncTracker);
   }
 
   @Test
   public void testCreateNonIncrementalBackupReturnsValidResponse() throws Exception {
-    BackupCoreAPI.BackupCoreRequestBody backupCoreRequestBody = createBackupCoreRequestBody();
+    CreateCoreBackupRequestBody backupCoreRequestBody = createBackupCoreRequestBody();
     backupCoreRequestBody.incremental = false;
     backupCoreRequestBody.backupName = backupName;
     SnapShooter.CoreSnapshotResponse response =
@@ -79,7 +80,7 @@ public class BackupCoreAPITest extends SolrTestCaseJ4 {
 
   @Test
   public void testMissingLocationParameter() throws Exception {
-    BackupCoreAPI.BackupCoreRequestBody backupCoreRequestBody = createBackupCoreRequestBody();
+    CreateCoreBackupRequestBody backupCoreRequestBody = createBackupCoreRequestBody();
     backupCoreRequestBody.location = null;
     backupCoreRequestBody.incremental = false;
     backupCoreRequestBody.backupName = backupName;
@@ -99,7 +100,7 @@ public class BackupCoreAPITest extends SolrTestCaseJ4 {
 
   @Test
   public void testMissingCoreNameParameter() throws Exception {
-    BackupCoreAPI.BackupCoreRequestBody backupCoreRequestBody = createBackupCoreRequestBody();
+    CreateCoreBackupRequestBody backupCoreRequestBody = createBackupCoreRequestBody();
     backupCoreRequestBody.location = null;
     backupCoreRequestBody.incremental = false;
     backupCoreRequestBody.backupName = backupName;
@@ -118,7 +119,7 @@ public class BackupCoreAPITest extends SolrTestCaseJ4 {
 
   @Test
   public void testNonIncrementalBackupForNonExistentCore() throws Exception {
-    BackupCoreAPI.BackupCoreRequestBody backupCoreRequestBody = createBackupCoreRequestBody();
+    CreateCoreBackupRequestBody backupCoreRequestBody = createBackupCoreRequestBody();
     backupCoreRequestBody.location = null;
     backupCoreRequestBody.incremental = false;
     backupCoreRequestBody.backupName = backupName;
@@ -133,7 +134,7 @@ public class BackupCoreAPITest extends SolrTestCaseJ4 {
 
   @Test
   public void testCreateIncrementalBackupReturnsValidResponse() throws Exception {
-    BackupCoreAPI.BackupCoreRequestBody backupCoreRequestBody = createBackupCoreRequestBody();
+    CreateCoreBackupRequestBody backupCoreRequestBody = createBackupCoreRequestBody();
     backupCoreRequestBody.incremental = true;
     backupCoreRequestBody.shardBackupId = "md_shard1_0";
     IncrementalShardBackup.IncrementalShardSnapshotResponse response =
@@ -165,13 +166,12 @@ public class BackupCoreAPITest extends SolrTestCaseJ4 {
     }
   }
 
-  private BackupCoreAPI.BackupCoreRequestBody createBackupCoreRequestBody() throws Exception {
+  private CreateCoreBackupRequestBody createBackupCoreRequestBody() throws Exception {
     final Path locationPath = createBackupLocation();
     final URI locationUri = bootstrapBackupLocation(locationPath);
     final CoreContainer cores = h.getCoreContainer();
     cores.getAllowPaths().add(Paths.get(locationUri));
-    final BackupCoreAPI.BackupCoreRequestBody backupCoreRequestBody =
-        new BackupCoreAPI.BackupCoreRequestBody();
+    final CreateCoreBackupRequestBody backupCoreRequestBody = new CreateCoreBackupRequestBody();
     backupCoreRequestBody.location = locationPath.toString();
     return backupCoreRequestBody;
   }
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/BalanceShardUniqueAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/BalanceShardUniqueAPITest.java
index fea001edcb2..c986f332da8 100644
--- a/solr/core/src/test/org/apache/solr/handler/admin/api/BalanceShardUniqueAPITest.java
+++ b/solr/core/src/test/org/apache/solr/handler/admin/api/BalanceShardUniqueAPITest.java
@@ -25,10 +25,11 @@ import static org.apache.solr.common.params.CollectionAdminParams.COLLECTION;
 import static org.apache.solr.common.params.CommonAdminParams.ASYNC;
 
 import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.client.api.model.BalanceShardUniqueRequestBody;
 import org.apache.solr.common.SolrException;
 import org.junit.Test;
 
-/** Unit tests for {@link BalanceShardUniqueAPI} */
+/** Unit tests for {@link BalanceShardUnique} */
 public class BalanceShardUniqueAPITest extends SolrTestCaseJ4 {
 
   @Test
@@ -37,7 +38,7 @@ public class BalanceShardUniqueAPITest extends SolrTestCaseJ4 {
         expectThrows(
             SolrException.class,
             () -> {
-              final var api = new BalanceShardUniqueAPI(null, null, null);
+              final var api = new BalanceShardUnique(null, null, null);
               api.balanceShardUnique("someCollectionName", null);
             });
 
@@ -47,13 +48,13 @@ public class BalanceShardUniqueAPITest extends SolrTestCaseJ4 {
 
   @Test
   public void testReportsErrorIfCollectionNameMissing() {
-    final var requestBody = new BalanceShardUniqueAPI.BalanceShardUniqueRequestBody();
+    final var requestBody = new BalanceShardUniqueRequestBody();
     requestBody.property = "preferredLeader";
     final SolrException thrown =
         expectThrows(
             SolrException.class,
             () -> {
-              final var api = new BalanceShardUniqueAPI(null, null, null);
+              final var api = new BalanceShardUnique(null, null, null);
               api.balanceShardUnique(null, requestBody);
             });
 
@@ -64,12 +65,12 @@ public class BalanceShardUniqueAPITest extends SolrTestCaseJ4 {
   @Test
   public void testReportsErrorIfPropertyToBalanceIsMissing() {
     // Note, 'property' param on reqBody not set
-    final var requestBody = new BalanceShardUniqueAPI.BalanceShardUniqueRequestBody();
+    final var requestBody = new BalanceShardUniqueRequestBody();
     final SolrException thrown =
         expectThrows(
             SolrException.class,
             () -> {
-              final var api = new BalanceShardUniqueAPI(null, null, null);
+              final var api = new BalanceShardUnique(null, null, null);
               api.balanceShardUnique("someCollName", requestBody);
             });
 
@@ -79,13 +80,13 @@ public class BalanceShardUniqueAPITest extends SolrTestCaseJ4 {
 
   @Test
   public void testCreateRemoteMessageAllProperties() {
-    final var requestBody = new BalanceShardUniqueAPI.BalanceShardUniqueRequestBody();
+    final var requestBody = new BalanceShardUniqueRequestBody();
     requestBody.property = "someProperty";
     requestBody.shardUnique = Boolean.TRUE;
     requestBody.onlyActiveNodes = Boolean.TRUE;
-    requestBody.asyncId = "someAsyncId";
+    requestBody.async = "someAsyncId";
     final var remoteMessage =
-        BalanceShardUniqueAPI.createRemoteMessage("someCollName", requestBody).getProperties();
+        BalanceShardUnique.createRemoteMessage("someCollName", requestBody).getProperties();
 
     assertEquals(6, remoteMessage.size());
     assertEquals("balanceshardunique", remoteMessage.get(QUEUE_OPERATION));
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/CreateCollectionSnapshotAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/CreateCollectionSnapshotAPITest.java
index c5f31fbdf45..affbbf4266e 100644
--- a/solr/core/src/test/org/apache/solr/handler/admin/api/CreateCollectionSnapshotAPITest.java
+++ b/solr/core/src/test/org/apache/solr/handler/admin/api/CreateCollectionSnapshotAPITest.java
@@ -34,8 +34,7 @@ public class CreateCollectionSnapshotAPITest extends SolrTestCaseJ4 {
   @Test
   public void testConstructsValidOverseerMessage() {
     final ZkNodeProps messageOne =
-        CreateCollectionSnapshotAPI.createRemoteMessage(
-            "myCollName", false, "mySnapshotName", null);
+        CreateCollectionSnapshot.createRemoteMessage("myCollName", false, "mySnapshotName", null);
     final Map<String, Object> rawMessageOne = messageOne.getProperties();
     assertEquals(4, rawMessageOne.size());
     MatcherAssert.assertThat(
@@ -48,7 +47,7 @@ public class CreateCollectionSnapshotAPITest extends SolrTestCaseJ4 {
     assertEquals(false, rawMessageOne.get(FOLLOW_ALIASES));
 
     final ZkNodeProps messageTwo =
-        CreateCollectionSnapshotAPI.createRemoteMessage(
+        CreateCollectionSnapshot.createRemoteMessage(
             "myCollName", true, "mySnapshotName", "myAsyncId");
     final Map<String, Object> rawMessageTwo = messageTwo.getProperties();
     assertEquals(5, rawMessageTwo.size());
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/V2CollectionBackupApiTest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/V2CollectionBackupApiTest.java
index f8c51b1a8d0..514423ba5df 100644
--- a/solr/core/src/test/org/apache/solr/handler/admin/api/V2CollectionBackupApiTest.java
+++ b/solr/core/src/test/org/apache/solr/handler/admin/api/V2CollectionBackupApiTest.java
@@ -20,14 +20,15 @@ import static org.apache.solr.cloud.Overseer.QUEUE_OPERATION;
 import static org.apache.solr.common.params.CollectionAdminParams.COPY_FILES_STRATEGY;
 
 import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.client.api.model.CreateCollectionBackupRequestBody;
 import org.apache.solr.common.params.ModifiableSolrParams;
 import org.junit.Test;
 
-/** Unit tests for {@link CreateCollectionBackupAPI} */
+/** Unit tests for {@link CreateCollectionBackup} */
 public class V2CollectionBackupApiTest extends SolrTestCaseJ4 {
   @Test
   public void testCreateRemoteMessageWithAllProperties() {
-    final var requestBody = new CreateCollectionBackupAPI.CreateCollectionBackupRequestBody();
+    final var requestBody = new CreateCollectionBackupRequestBody();
     requestBody.location = "/some/location";
     requestBody.repository = "someRepoName";
     requestBody.followAliases = true;
@@ -38,7 +39,7 @@ public class V2CollectionBackupApiTest extends SolrTestCaseJ4 {
     requestBody.async = "someId";
 
     var message =
-        CreateCollectionBackupAPI.createRemoteMessage(
+        CreateCollectionBackup.createRemoteMessage(
             "someCollectionName", "someBackupName", requestBody);
     var messageProps = message.getProperties();
 
@@ -58,11 +59,11 @@ public class V2CollectionBackupApiTest extends SolrTestCaseJ4 {
 
   @Test
   public void testCreateRemoteMessageOmitsNullValues() {
-    final var requestBody = new CreateCollectionBackupAPI.CreateCollectionBackupRequestBody();
+    final var requestBody = new CreateCollectionBackupRequestBody();
     requestBody.location = "/some/location";
 
     var message =
-        CreateCollectionBackupAPI.createRemoteMessage(
+        CreateCollectionBackup.createRemoteMessage(
             "someCollectionName", "someBackupName", requestBody);
     var messageProps = message.getProperties();
 
@@ -86,7 +87,7 @@ public class V2CollectionBackupApiTest extends SolrTestCaseJ4 {
     params.set("maxNumBackupPoints", "123");
     params.set("async", "someId");
 
-    final var requestBody = CreateCollectionBackupAPI.createRequestBodyFromV1Params(params);
+    final var requestBody = CreateCollectionBackup.createRequestBodyFromV1Params(params);
 
     assertEquals("/some/location", requestBody.location);
     assertEquals("someRepoName", requestBody.repository);
diff --git a/solr/solrj/src/java/org/apache/solr/common/util/Utils.java b/solr/solrj/src/java/org/apache/solr/common/util/Utils.java
index 9bb772ceac5..52639ce648f 100644
--- a/solr/solrj/src/java/org/apache/solr/common/util/Utils.java
+++ b/solr/solrj/src/java/org/apache/solr/common/util/Utils.java
@@ -1035,6 +1035,16 @@ public class Utils {
     }
   }
 
+  /**
+   * Produce a Map representation of the provided object using reflection to identify annotated
+   * fields.
+   *
+   * <p>The provided object is not required to be a {@link MapWriter}.
+   */
+  public static Map<String, Object> reflectToMap(Object toReflect) {
+    return ((Utils.DelegateReflectWriter) Utils.getReflectWriter(toReflect)).toMap(new HashMap<>());
+  }
+
   @SuppressWarnings({"unchecked", "rawtypes"})
   public static Map<String, Object> convertToMap(MapWriter m, Map<String, Object> map) {
     try {