You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@solr.apache.org by ep...@apache.org on 2023/04/15 19:40:52 UTC

[solr] branch branch_9x updated (245f7938e26 -> 4d994716225)

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

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


    from 245f7938e26 SOLR-16458: Migrate v2 logging APIs to JAX-RS (#1160)
     new b417ad2d1e1 track changes
     new 4d994716225 SOLR-16504 Convert CLI tools to Jetty HTTP 2 client. (#1182)

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 solr/CHANGES.txt                                   |   2 +
 .../org/apache/solr/filestore/PackageStoreAPI.java |  62 +-
 .../packagemanager/DefaultPackageRepository.java   |  23 +-
 .../apache/solr/packagemanager/PackageManager.java | 125 ++--
 .../apache/solr/packagemanager/PackageUtils.java   |  84 +--
 .../solr/packagemanager/RepositoryManager.java     |  40 +-
 .../src/java/org/apache/solr/util/ExportTool.java  |   8 +-
 .../src/java/org/apache/solr/util/PackageTool.java |  24 +-
 .../src/java/org/apache/solr/util/SolrCLI.java     | 643 ++++++---------------
 .../java/org/apache/solr/util/SolrLogPostTool.java |   6 +-
 .../apache/solr/cloud/SolrCloudExampleTest.java    | 176 +++---
 .../solr/handler/RequestHandlerMetricsTest.java    |   2 +-
 .../apache/solr/handler/V2ApiIntegrationTest.java  |  11 +-
 .../solr/handler/admin/HealthCheckHandlerTest.java |   4 +-
 .../admin/PropertiesRequestHandlerTest.java        |   5 +-
 .../admin/ZookeeperStatusHandlerFailureTest.java   |   8 +-
 .../handler/admin/ZookeeperStatusHandlerTest.java  |   8 +-
 .../component/DistributedTermsComponentTest.java   |   6 +-
 .../org/apache/solr/search/TestTaskManagement.java |   2 +-
 .../solr/security/BasicAuthIntegrationTest.java    |   3 +-
 .../src/test/org/apache/solr/util/ApiToolTest.java |  47 ++
 .../apache/solr/util/TestSolrCLIRunExample.java    |   3 +-
 .../solr/util/tracing/TestDistributedTracing.java  |   2 +-
 .../solr/hdfs/snapshots/SolrSnapshotsTool.java     |   4 +-
 .../{test_start_solr.bats => test_create.bats}     |  18 +-
 ...ponseParser.java => JsonMapResponseParser.java} |  60 +-
 .../solrj/request/DelegationTokenRequest.java      |   5 +-
 .../client/solrj/request/GenericSolrRequest.java   |   5 +
 .../solrj/response/DelegationTokenResponse.java    |  49 --
 .../src/java/org/apache/solr/SolrTestCaseHS.java   |   4 +-
 30 files changed, 570 insertions(+), 869 deletions(-)
 create mode 100644 solr/core/src/test/org/apache/solr/util/ApiToolTest.java
 copy solr/packaging/test/{test_start_solr.bats => test_create.bats} (75%)
 copy solr/solrj/src/java/org/apache/solr/client/solrj/impl/{BinaryResponseParser.java => JsonMapResponseParser.java} (61%)


[solr] 01/02: track changes

Posted by ep...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit b417ad2d1e11e60aeab3d50fe0cc5b80e48164e0
Author: Eric Pugh <ep...@opensourceconnections.com>
AuthorDate: Sat Apr 15 15:39:46 2023 -0400

    track changes
---
 solr/CHANGES.txt | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index fd5fd430ec2..fba7bf3a1f9 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -48,6 +48,8 @@ Improvements
   `GET /api/node/logging/messages`.  And the threshold for the log "listener" can be modified with
   `PUT /api/node/logging/messages/threshold`.  (Jason Gerlowski, Calvince Otieno)
 
+* SOLR-16504: Convert CLI tools to use Jetty HTTP 2 client.  (Bence Szabo via Eric Pugh)  
+
 Optimizations
 ---------------------
 


[solr] 02/02: SOLR-16504 Convert CLI tools to Jetty HTTP 2 client. (#1182)

Posted by ep...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 4d99471622598c23a4931fe91737c98e627782cb
Author: bszabo97 <79...@users.noreply.github.com>
AuthorDate: Sat Apr 15 14:00:31 2023 +0200

    SOLR-16504 Convert CLI tools to Jetty HTTP 2 client. (#1182)
    
    Migrate SolrCLI to using Jetty Http2 client from Apache Http client
    
    ---------
    Co-authored-by: Eric Pugh <ep...@opensourceconnections.com>
    Co-authored-by: David Smiley <ds...@salesforce.com>
---
 .../org/apache/solr/filestore/PackageStoreAPI.java |  62 +-
 .../packagemanager/DefaultPackageRepository.java   |  23 +-
 .../apache/solr/packagemanager/PackageManager.java | 125 ++--
 .../apache/solr/packagemanager/PackageUtils.java   |  84 +--
 .../solr/packagemanager/RepositoryManager.java     |  40 +-
 .../src/java/org/apache/solr/util/ExportTool.java  |   8 +-
 .../src/java/org/apache/solr/util/PackageTool.java |  24 +-
 .../src/java/org/apache/solr/util/SolrCLI.java     | 643 ++++++---------------
 .../java/org/apache/solr/util/SolrLogPostTool.java |   6 +-
 .../apache/solr/cloud/SolrCloudExampleTest.java    | 176 +++---
 .../solr/handler/RequestHandlerMetricsTest.java    |   2 +-
 .../apache/solr/handler/V2ApiIntegrationTest.java  |  11 +-
 .../solr/handler/admin/HealthCheckHandlerTest.java |   4 +-
 .../admin/PropertiesRequestHandlerTest.java        |   5 +-
 .../admin/ZookeeperStatusHandlerFailureTest.java   |   8 +-
 .../handler/admin/ZookeeperStatusHandlerTest.java  |   8 +-
 .../component/DistributedTermsComponentTest.java   |   6 +-
 .../org/apache/solr/search/TestTaskManagement.java |   2 +-
 .../solr/security/BasicAuthIntegrationTest.java    |   3 +-
 .../src/test/org/apache/solr/util/ApiToolTest.java |  47 ++
 .../apache/solr/util/TestSolrCLIRunExample.java    |   3 +-
 .../solr/util/tracing/TestDistributedTracing.java  |   2 +-
 .../solr/hdfs/snapshots/SolrSnapshotsTool.java     |   4 +-
 solr/packaging/test/test_create.bats               |  42 ++
 .../client/solrj/impl/JsonMapResponseParser.java   |  69 +++
 .../solrj/request/DelegationTokenRequest.java      |   5 +-
 .../client/solrj/request/GenericSolrRequest.java   |   5 +
 .../solrj/response/DelegationTokenResponse.java    |  49 --
 .../src/java/org/apache/solr/SolrTestCaseHS.java   |   4 +-
 29 files changed, 644 insertions(+), 826 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/filestore/PackageStoreAPI.java b/solr/core/src/java/org/apache/solr/filestore/PackageStoreAPI.java
index 4f4403356fb..eb87c1a8e29 100644
--- a/solr/core/src/java/org/apache/solr/filestore/PackageStoreAPI.java
+++ b/solr/core/src/java/org/apache/solr/filestore/PackageStoreAPI.java
@@ -362,26 +362,50 @@ public class PackageStoreAPI {
 
     private void writeRawFile(SolrQueryRequest req, SolrQueryResponse rsp, String path) {
       ModifiableSolrParams solrParams = new ModifiableSolrParams();
-      solrParams.add(CommonParams.WT, FILE_STREAM);
-      req.setParams(SolrParams.wrapDefaults(solrParams, req.getParams()));
-      rsp.add(
-          FILE_STREAM,
-          (SolrCore.RawWriter)
-              os ->
-                  packageStore.get(
-                      path,
-                      it -> {
-                        try {
-                          InputStream inputStream = it.getInputStream();
-                          if (inputStream != null) {
-                            inputStream.transferTo(os);
+      if ("json".equals(req.getParams().get(CommonParams.WT))) {
+        solrParams.add(CommonParams.WT, "json");
+        req.setParams(SolrParams.wrapDefaults(solrParams, req.getParams()));
+        try {
+          packageStore.get(
+              path,
+              it -> {
+                try {
+                  InputStream inputStream = it.getInputStream();
+                  if (inputStream != null) {
+                    rsp.addResponse(new String(inputStream.readAllBytes(), UTF_8));
+                  }
+                } catch (IOException e) {
+                  throw new SolrException(
+                      SolrException.ErrorCode.SERVER_ERROR, "Error reading file " + path);
+                }
+              },
+              false);
+        } catch (IOException e) {
+          throw new SolrException(
+              SolrException.ErrorCode.SERVER_ERROR, "Error getting file from path " + path);
+        }
+      } else {
+        solrParams.add(CommonParams.WT, FILE_STREAM);
+        req.setParams(SolrParams.wrapDefaults(solrParams, req.getParams()));
+        rsp.add(
+            FILE_STREAM,
+            (SolrCore.RawWriter)
+                os ->
+                    packageStore.get(
+                        path,
+                        it -> {
+                          try {
+                            InputStream inputStream = it.getInputStream();
+                            if (inputStream != null) {
+                              inputStream.transferTo(os);
+                            }
+                          } catch (IOException e) {
+                            throw new SolrException(
+                                SolrException.ErrorCode.SERVER_ERROR, "Error reading file " + path);
                           }
-                        } catch (IOException e) {
-                          throw new SolrException(
-                              SolrException.ErrorCode.SERVER_ERROR, "Error reading file" + path);
-                        }
-                      },
-                      false));
+                        },
+                        false));
+      }
     }
   }
 
diff --git a/solr/core/src/java/org/apache/solr/packagemanager/DefaultPackageRepository.java b/solr/core/src/java/org/apache/solr/packagemanager/DefaultPackageRepository.java
index 0eb9cfc8216..32533625e60 100644
--- a/solr/core/src/java/org/apache/solr/packagemanager/DefaultPackageRepository.java
+++ b/solr/core/src/java/org/apache/solr/packagemanager/DefaultPackageRepository.java
@@ -26,11 +26,15 @@ import java.nio.file.Path;
 import java.util.Map;
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.io.FilenameUtils;
-import org.apache.http.impl.client.CloseableHttpClient;
-import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.solr.client.solrj.SolrRequest;
+import org.apache.solr.client.solrj.SolrServerException;
+import org.apache.solr.client.solrj.impl.Http2SolrClient;
+import org.apache.solr.client.solrj.impl.JsonMapResponseParser;
+import org.apache.solr.client.solrj.request.GenericSolrRequest;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.SolrException.ErrorCode;
 import org.apache.solr.common.util.CollectionUtil;
+import org.apache.solr.common.util.NamedList;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -101,16 +105,23 @@ public class DefaultPackageRepository extends PackageRepository {
   }
 
   private void initPackages() {
-    try (CloseableHttpClient client = HttpClientBuilder.create().build()) {
+    // We need http 1.1 protocol here because we are talking to the repository server and not to
+    // solr actually.
+    // We use an Http2SolrClient so that we do not need a raw jetty http client for this GET.
+    try (Http2SolrClient client =
+        new Http2SolrClient.Builder(repositoryURL).useHttp1_1(true).build()) {
+      GenericSolrRequest request =
+          new GenericSolrRequest(SolrRequest.METHOD.GET, "/repository.json");
+      request.setResponseParser(new JsonMapResponseParser());
+      NamedList<Object> resp = client.request(request);
       SolrPackage[] items =
-          PackageUtils.getJson(client, repositoryURL + "/repository.json", SolrPackage[].class);
-
+          PackageUtils.getMapper().readValue("[" + resp.jsonStr() + "]", SolrPackage[].class);
       packages = CollectionUtil.newHashMap(items.length);
       for (SolrPackage pkg : items) {
         pkg.setRepository(name);
         packages.put(pkg.name, pkg);
       }
-    } catch (IOException ex) {
+    } catch (SolrServerException | IOException ex) {
       throw new SolrException(ErrorCode.INVALID_STATE, ex);
     }
     if (log.isDebugEnabled()) {
diff --git a/solr/core/src/java/org/apache/solr/packagemanager/PackageManager.java b/solr/core/src/java/org/apache/solr/packagemanager/PackageManager.java
index c8c000826c7..c11b2691075 100644
--- a/solr/core/src/java/org/apache/solr/packagemanager/PackageManager.java
+++ b/solr/core/src/java/org/apache/solr/packagemanager/PackageManager.java
@@ -36,19 +36,20 @@ import java.util.Scanner;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
-import org.apache.http.client.methods.HttpDelete;
+import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrRequest;
 import org.apache.solr.client.solrj.SolrServerException;
-import org.apache.solr.client.solrj.impl.HttpSolrClient;
+import org.apache.solr.client.solrj.request.GenericSolrRequest;
 import org.apache.solr.client.solrj.request.V2Request;
 import org.apache.solr.client.solrj.request.beans.PackagePayload;
 import org.apache.solr.client.solrj.request.beans.PluginMeta;
 import org.apache.solr.client.solrj.response.V2Response;
-import org.apache.solr.common.NavigableObject;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.SolrException.ErrorCode;
 import org.apache.solr.common.cloud.SolrZkClient;
 import org.apache.solr.common.cloud.ZkStateReader;
+import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.Pair;
 import org.apache.solr.common.util.StrUtils;
 import org.apache.solr.common.util.Utils;
@@ -68,14 +69,14 @@ import org.slf4j.LoggerFactory;
 public class PackageManager implements Closeable {
 
   final String solrBaseUrl;
-  final HttpSolrClient solrClient;
+  final SolrClient solrClient;
   final SolrZkClient zkClient;
 
   private Map<String, List<SolrPackageInstance>> packages = null;
 
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
-  public PackageManager(HttpSolrClient solrClient, String solrBaseUrl, String zkHost) {
+  public PackageManager(SolrClient solrClient, String solrBaseUrl, String zkHost) {
     this.solrBaseUrl = solrBaseUrl;
     this.solrClient = solrClient;
     this.zkClient =
@@ -93,7 +94,8 @@ public class PackageManager implements Closeable {
     }
   }
 
-  public void uninstall(String packageName, String version) {
+  public void uninstall(String packageName, String version)
+      throws IOException, SolrServerException {
     SolrPackageInstance packageInstance = getPackageInstance(packageName, version);
     if (packageInstance == null) {
       PackageUtils.printRed(
@@ -165,10 +167,9 @@ public class PackageManager implements Closeable {
         String.format(Locale.ROOT, "/package/%s/%s/%s", packageName, version, "manifest.json"));
     for (String filePath : filesToDelete) {
       DistribPackageStore.deleteZKFileEntry(zkClient, filePath);
-      String path = solrClient.getBaseURL() + "/api/cluster/files" + filePath;
+      String path = "/api/cluster/files" + filePath;
       PackageUtils.printGreen("Deleting " + path);
-      HttpDelete httpDel = new HttpDelete(path);
-      Utils.executeHttpMethod(solrClient.getHttpClient(), path, Utils.JSONCONSUMER, httpDel);
+      solrClient.request(new GenericSolrRequest(SolrRequest.METHOD.DELETE, path));
     }
 
     PackageUtils.printGreen("Package uninstalled: " + packageName + ":" + version + ":-)");
@@ -196,7 +197,6 @@ public class PackageManager implements Closeable {
             Manifest manifest =
                 PackageUtils.fetchManifest(
                     solrClient,
-                    solrBaseUrl,
                     pkgVersion.get("manifest").toString(),
                     pkgVersion.get("manifestSHA512").toString());
             List<Plugin> solrPlugins = manifest.plugins;
@@ -231,20 +231,19 @@ public class PackageManager implements Closeable {
   public Map<String, SolrPackageInstance> getPackagesDeployed(String collection) {
     Map<String, String> packages = null;
     try {
-      NavigableObject result =
-          (NavigableObject)
-              Utils.executeGET(
-                  solrClient.getHttpClient(),
-                  solrBaseUrl
-                      + PackageUtils.getCollectionParamsPath(collection)
-                      + "/PKG_VERSIONS?omitHeader=true&wt=javabin",
-                  Utils.JAVABINCONSUMER);
+      NamedList<Object> result =
+          solrClient.request(
+              new GenericSolrRequest(
+                  SolrRequest.METHOD.GET,
+                  PackageUtils.getCollectionParamsPath(collection) + "/PKG_VERSIONS"));
       packages =
           (Map<String, String>)
               result._get("/response/params/PKG_VERSIONS", Collections.emptyMap());
     } catch (PathNotFoundException ex) {
       // Don't worry if PKG_VERSION wasn't found. It just means this collection was never touched by
       // the package manager.
+    } catch (SolrServerException | IOException ex) {
+      throw new SolrException(ErrorCode.SERVER_ERROR, ex);
     }
     if (packages == null) return Collections.emptyMap();
     Map<String, SolrPackageInstance> ret = new HashMap<>();
@@ -271,19 +270,18 @@ public class PackageManager implements Closeable {
     Map<String, Set<PluginMeta>> packagePlugins = new HashMap<>();
     Map<String, Object> result;
     try {
-      result =
-          (Map<String, Object>)
-              Utils.executeGET(
-                  solrClient.getHttpClient(),
-                  solrBaseUrl + PackageUtils.CLUSTERPROPS_PATH,
-                  Utils.JSONCONSUMER);
-    } catch (SolrException ex) {
-      if (ex.code() == ErrorCode.NOT_FOUND.code) {
+      NamedList<Object> response =
+          solrClient.request(
+              new GenericSolrRequest(SolrRequest.METHOD.GET, PackageUtils.CLUSTERPROPS_PATH));
+      Integer statusCode = (Integer) response.findRecursive("responseHeader", "status");
+      if (statusCode == null || statusCode == ErrorCode.NOT_FOUND.code) {
         // Cluster props doesn't exist, that means there are no cluster level plugins installed.
         result = Collections.emptyMap();
       } else {
-        throw ex;
+        result = response.asShallowMap();
       }
+    } catch (SolrServerException | IOException ex) {
+      throw new SolrException(ErrorCode.SERVER_ERROR, ex);
     }
     @SuppressWarnings({"unchecked"})
     Map<String, Object> clusterPlugins =
@@ -419,14 +417,12 @@ public class PackageManager implements Closeable {
       try {
         @SuppressWarnings("unchecked")
         boolean packageParamsExist =
-            ((Map<Object, Object>)
-                    PackageUtils.getJson(
-                            solrClient.getHttpClient(),
-                            solrBaseUrl
-                                + PackageUtils.getCollectionParamsPath(collection)
-                                + "/packages",
-                            Map.class)
-                        .getOrDefault("response", Collections.emptyMap()))
+            solrClient
+                .request(
+                    new GenericSolrRequest(
+                        SolrRequest.METHOD.GET,
+                        PackageUtils.getCollectionParamsPath(collection) + "/packages"))
+                .asShallowMap()
                 .containsKey("params");
         SolrCLI.postJsonToSolr(
             solrClient,
@@ -602,10 +598,7 @@ public class PackageManager implements Closeable {
           Map<String, Object> clusterprops = null;
           try {
             clusterprops =
-                PackageUtils.getJson(
-                    solrClient.getHttpClient(),
-                    solrBaseUrl + PackageUtils.CLUSTERPROPS_PATH,
-                    Map.class);
+                PackageUtils.getJson(solrClient, PackageUtils.CLUSTERPROPS_PATH, Map.class);
           } catch (SolrException ex) {
             if (ex.code() == ErrorCode.NOT_FOUND.code) {
               // Ignore this, as clusterprops may not have been created yet. This means package
@@ -729,22 +722,16 @@ public class PackageManager implements Closeable {
   @SuppressWarnings({"rawtypes", "unchecked"})
   Map<String, String> getPackageParams(String packageName, String collection) {
     try {
+      NamedList<Object> response =
+          solrClient.request(
+              new GenericSolrRequest(
+                  SolrRequest.METHOD.GET,
+                  PackageUtils.getCollectionParamsPath(collection) + "/packages"));
       return (Map<String, String>)
-          ((Map)
-                  ((Map)
-                          ((Map)
-                                  PackageUtils.getJson(
-                                          solrClient.getHttpClient(),
-                                          solrBaseUrl
-                                              + PackageUtils.getCollectionParamsPath(collection)
-                                              + "/packages",
-                                          Map.class)
-                                      .get("response"))
-                              .get("params"))
-                      .get("packages"))
-              .get(packageName);
+          response._get("/response/params/packages/" + packageName, Collections.emptyMap());
     } catch (Exception ex) {
       // This should be because there are no parameters. Be tolerant here.
+      log.warn("There are no parameters to return for package: {}", packageName);
       return Collections.emptyMap();
     }
   }
@@ -776,14 +763,13 @@ public class PackageManager implements Closeable {
                   pkg.version,
                   "plugin-name",
                   plugin.name);
-          String url =
-              solrBaseUrl
-                  + PackageUtils.resolve(
-                      cmd.path, pkg.parameterDefaults, overridesMap, systemParams);
-          PackageUtils.printGreen("Executing " + url + " for cluster level plugin");
+          String path =
+              PackageUtils.resolve(cmd.path, pkg.parameterDefaults, overridesMap, systemParams);
+          PackageUtils.printGreen("Executing " + solrBaseUrl + path + " for cluster level plugin");
 
           if ("GET".equalsIgnoreCase(cmd.method)) {
-            String response = PackageUtils.getJsonStringFromUrl(solrClient.getHttpClient(), url);
+            String response =
+                PackageUtils.getJsonStringFromUrl(solrClient, path, new ModifiableSolrParams());
             PackageUtils.printGreen(response);
             String actualValue = null;
             try {
@@ -826,17 +812,15 @@ public class PackageManager implements Closeable {
                     pkg.version,
                     "plugin-name",
                     plugin.name);
-            String url =
-                solrBaseUrl
-                    + PackageUtils.resolve(
-                        cmd.path,
-                        pkg.parameterDefaults,
-                        collectionParameterOverrides,
-                        systemParams);
-            PackageUtils.printGreen("Executing " + url + " for collection:" + collection);
+            String path =
+                PackageUtils.resolve(
+                    cmd.path, pkg.parameterDefaults, collectionParameterOverrides, systemParams);
+            PackageUtils.printGreen(
+                "Executing " + solrBaseUrl + path + " for collection:" + collection);
 
             if ("GET".equalsIgnoreCase(cmd.method)) {
-              String response = PackageUtils.getJsonStringFromUrl(solrClient.getHttpClient(), url);
+              String response =
+                  PackageUtils.getJsonStringFromUrl(solrClient, path, new ModifiableSolrParams());
               PackageUtils.printGreen(response);
               String actualValue = null;
               try {
@@ -1117,10 +1101,9 @@ public class PackageManager implements Closeable {
       // Check package version installed
       String paramsJson =
           PackageUtils.getJsonStringFromUrl(
-              solrClient.getHttpClient(),
-              solrBaseUrl
-                  + PackageUtils.getCollectionParamsPath(collection)
-                  + "/PKG_VERSIONS?omitHeader=true");
+              solrClient,
+              PackageUtils.getCollectionParamsPath(collection) + "/PKG_VERSIONS",
+              new ModifiableSolrParams().add("omitHeader", "true"));
       String version = null;
       try {
         version =
diff --git a/solr/core/src/java/org/apache/solr/packagemanager/PackageUtils.java b/solr/core/src/java/org/apache/solr/packagemanager/PackageUtils.java
index b87b162ac63..c813f400a37 100644
--- a/solr/core/src/java/org/apache/solr/packagemanager/PackageUtils.java
+++ b/solr/core/src/java/org/apache/solr/packagemanager/PackageUtils.java
@@ -23,28 +23,28 @@ import com.jayway.jsonpath.spi.json.JsonProvider;
 import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider;
 import com.jayway.jsonpath.spi.mapper.MappingProvider;
 import java.io.IOException;
-import java.net.MalformedURLException;
+import java.io.OutputStream;
 import java.nio.ByteBuffer;
+import java.nio.channels.Channels;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Path;
 import java.util.List;
 import java.util.Map;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
-import org.apache.http.HttpResponse;
-import org.apache.http.client.HttpClient;
-import org.apache.http.client.methods.HttpGet;
 import org.apache.lucene.util.SuppressForbidden;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrRequest;
 import org.apache.solr.client.solrj.SolrServerException;
-import org.apache.solr.client.solrj.impl.HttpSolrClient;
-import org.apache.solr.client.solrj.request.V2Request;
-import org.apache.solr.client.solrj.response.V2Response;
+import org.apache.solr.client.solrj.impl.JsonMapResponseParser;
+import org.apache.solr.client.solrj.request.GenericSolrRequest;
+import org.apache.solr.client.solrj.request.RequestWriter;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.SolrException.ErrorCode;
 import org.apache.solr.common.params.CommonParams;
 import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.Utils;
 import org.apache.solr.core.BlobRepository;
 import org.apache.solr.filestore.DistribPackageStore;
@@ -94,29 +94,42 @@ public class PackageUtils {
     if (sig != null) {
       params.add("sig", sig);
     }
-    V2Response rsp =
-        new V2Request.Builder(resource)
-            .withMethod(SolrRequest.METHOD.PUT)
-            .withPayload(buffer)
-            .forceV2(true)
-            .withMimeType("application/octet-stream")
-            .withParams(params)
-            .build()
-            .process(client);
-    if (!name.equals(rsp.getResponse().get(CommonParams.FILE))) {
+    GenericSolrRequest request =
+        new GenericSolrRequest(SolrRequest.METHOD.PUT, resource, params) {
+          @Override
+          public RequestWriter.ContentWriter getContentWriter(String expectedType) {
+            return new RequestWriter.ContentWriter() {
+              public final ByteBuffer payload = buffer;
+
+              @Override
+              public void write(OutputStream os) throws IOException {
+                if (payload == null) return;
+                Channels.newChannel(os).write(payload);
+              }
+
+              @Override
+              public String getContentType() {
+                return "application/octet-stream";
+              }
+            };
+          }
+        };
+    NamedList<Object> rsp = client.request(request);
+    if (!name.equals(rsp.get(CommonParams.FILE))) {
       throw new SolrException(
           ErrorCode.BAD_REQUEST,
           "Mismatch in file uploaded. Uploaded: "
-              + rsp.getResponse().get(CommonParams.FILE)
+              + rsp.get(CommonParams.FILE)
               + ", Original: "
               + name);
     }
   }
 
-  /** Download JSON from the url and deserialize into klass. */
-  public static <T> T getJson(HttpClient client, String url, Class<T> klass) {
+  /** Download JSON from a Solr url and deserialize into klass. */
+  public static <T> T getJson(SolrClient client, String path, Class<T> klass) {
     try {
-      return getMapper().readValue(getJsonStringFromUrl(client, url), klass);
+      return getMapper()
+          .readValue(getJsonStringFromUrl(client, path, new ModifiableSolrParams()), klass);
     } catch (IOException e) {
       throw new RuntimeException(e);
     }
@@ -140,17 +153,14 @@ public class PackageUtils {
     return null;
   }
 
-  /** Returns JSON string from a given URL */
-  public static String getJsonStringFromUrl(HttpClient client, String url) {
+  /** Returns JSON string from a given Solr URL */
+  public static String getJsonStringFromUrl(SolrClient client, String path, SolrParams params) {
     try {
-      HttpResponse resp = client.execute(new HttpGet(url));
-      if (resp.getStatusLine().getStatusCode() != 200) {
-        throw new SolrException(
-            ErrorCode.NOT_FOUND,
-            "Error (code=" + resp.getStatusLine().getStatusCode() + ") fetching from URL: " + url);
-      }
-      return new String(resp.getEntity().getContent().readAllBytes(), StandardCharsets.UTF_8);
-    } catch (UnsupportedOperationException | IOException e) {
+      GenericSolrRequest request = new GenericSolrRequest(SolrRequest.METHOD.GET, path, params);
+      request.setResponseParser(new JsonMapResponseParser());
+      NamedList<Object> response = client.request(request);
+      return response.jsonStr();
+    } catch (IOException | SolrServerException e) {
       throw new RuntimeException(e);
     }
   }
@@ -160,11 +170,13 @@ public class PackageUtils {
    * fetching.
    */
   public static Manifest fetchManifest(
-      HttpSolrClient solrClient, String solrBaseUrl, String manifestFilePath, String expectedSHA512)
-      throws MalformedURLException, IOException {
-    String manifestJson =
-        PackageUtils.getJsonStringFromUrl(
-            solrClient.getHttpClient(), solrBaseUrl + "/api/node/files" + manifestFilePath);
+      SolrClient solrClient, String manifestFilePath, String expectedSHA512)
+      throws IOException, SolrServerException {
+    GenericSolrRequest request =
+        new GenericSolrRequest(SolrRequest.METHOD.GET, "/api/node/files" + manifestFilePath);
+    request.setResponseParser(new JsonMapResponseParser());
+    NamedList<Object> response = solrClient.request(request);
+    String manifestJson = (String) response.get("response");
     String calculatedSHA512 =
         BlobRepository.sha512Digest(ByteBuffer.wrap(manifestJson.getBytes("UTF-8")));
     if (expectedSHA512.equals(calculatedSHA512) == false) {
diff --git a/solr/core/src/java/org/apache/solr/packagemanager/RepositoryManager.java b/solr/core/src/java/org/apache/solr/packagemanager/RepositoryManager.java
index 303c7ccd30a..a480683eba6 100644
--- a/solr/core/src/java/org/apache/solr/packagemanager/RepositoryManager.java
+++ b/solr/core/src/java/org/apache/solr/packagemanager/RepositoryManager.java
@@ -17,6 +17,7 @@
 
 package org.apache.solr.packagemanager;
 
+import static org.apache.solr.common.params.CommonParams.SYSTEM_INFO_PATH;
 import static org.apache.solr.packagemanager.PackageUtils.getMapper;
 
 import java.io.IOException;
@@ -36,22 +37,23 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.stream.Collectors;
+import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrRequest;
 import org.apache.solr.client.solrj.SolrServerException;
-import org.apache.solr.client.solrj.impl.HttpSolrClient;
-import org.apache.solr.client.solrj.request.V2Request;
+import org.apache.solr.client.solrj.request.GenericSolrRequest;
+import org.apache.solr.client.solrj.request.RequestWriter;
 import org.apache.solr.client.solrj.request.beans.PackagePayload;
-import org.apache.solr.client.solrj.response.V2Response;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.SolrException.ErrorCode;
 import org.apache.solr.common.cloud.SolrZkClient;
+import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.common.util.NamedList;
 import org.apache.solr.core.BlobRepository;
 import org.apache.solr.filestore.PackageStoreAPI;
 import org.apache.solr.packagemanager.SolrPackage.Artifact;
 import org.apache.solr.packagemanager.SolrPackage.SolrPackageRelease;
 import org.apache.solr.pkg.PackageAPI;
 import org.apache.solr.pkg.SolrPackageLoader;
-import org.apache.solr.util.SolrCLI;
 import org.apache.zookeeper.CreateMode;
 import org.apache.zookeeper.KeeperException;
 import org.slf4j.Logger;
@@ -64,9 +66,9 @@ public class RepositoryManager {
 
   private final PackageManager packageManager;
 
-  final HttpSolrClient solrClient;
+  final SolrClient solrClient;
 
-  public RepositoryManager(HttpSolrClient solrClient, PackageManager packageManager) {
+  public RepositoryManager(SolrClient solrClient, PackageManager packageManager) {
     this.packageManager = packageManager;
     this.solrClient = solrClient;
   }
@@ -136,17 +138,16 @@ public class RepositoryManager {
 
   public void addKey(byte[] key, String destinationKeyFilename) throws Exception {
     // get solr_home directory from info servlet
-    String systemInfoUrl = solrClient.getBaseURL() + "/solr/admin/info/system";
-    Map<String, Object> systemInfo =
-        SolrCLI.getJson(solrClient.getHttpClient(), systemInfoUrl, 2, true);
+    NamedList<Object> systemInfo =
+        solrClient.request(
+            new GenericSolrRequest(SolrRequest.METHOD.GET, "/solr" + SYSTEM_INFO_PATH));
     String solrHome = (String) systemInfo.get("solr_home");
 
     // put the public key into package store's trusted key store and request a sync.
     String path = PackageStoreAPI.KEYS_DIR + "/" + destinationKeyFilename;
     PackageUtils.uploadKey(key, path, Paths.get(solrHome));
     PackageUtils.getJsonStringFromUrl(
-        solrClient.getHttpClient(),
-        solrClient.getBaseURL() + "/api/node/files" + path + "?sync=true");
+        solrClient, "/api/node/files" + path, new ModifiableSolrParams().add("sync", "true"));
   }
 
   private String getRepositoriesJson(SolrZkClient zkClient)
@@ -230,15 +231,16 @@ public class RepositoryManager {
       add.manifest = "/package/" + packageName + "/" + version + "/manifest.json";
       add.manifestSHA512 = manifestSHA512;
 
-      V2Request req =
-          new V2Request.Builder(PackageUtils.PACKAGE_PATH)
-              .forceV2(true)
-              .withMethod(SolrRequest.METHOD.POST)
-              .withPayload(Collections.singletonMap("add", add))
-              .build();
-
+      GenericSolrRequest request =
+          new GenericSolrRequest(SolrRequest.METHOD.POST, PackageUtils.PACKAGE_PATH) {
+            @Override
+            public RequestWriter.ContentWriter getContentWriter(String expectedType) {
+              return new RequestWriter.StringPayloadContentWriter(
+                  "{add:" + add.jsonStr() + "}", "application/json");
+            }
+          };
       try {
-        V2Response resp = req.process(solrClient);
+        NamedList<Object> resp = solrClient.request(request);
         PackageUtils.printGreen("Response: " + resp.jsonStr());
       } catch (SolrServerException | IOException e) {
         throw new SolrException(ErrorCode.BAD_REQUEST, e);
diff --git a/solr/core/src/java/org/apache/solr/util/ExportTool.java b/solr/core/src/java/org/apache/solr/util/ExportTool.java
index 50f78db8e77..f5e7f53c3c8 100644
--- a/solr/core/src/java/org/apache/solr/util/ExportTool.java
+++ b/solr/core/src/java/org/apache/solr/util/ExportTool.java
@@ -59,10 +59,10 @@ import org.apache.solr.client.solrj.SolrQuery;
 import org.apache.solr.client.solrj.SolrRequest;
 import org.apache.solr.client.solrj.SolrServerException;
 import org.apache.solr.client.solrj.StreamingResponseCallback;
-import org.apache.solr.client.solrj.impl.CloudLegacySolrClient;
+import org.apache.solr.client.solrj.impl.CloudHttp2SolrClient;
 import org.apache.solr.client.solrj.impl.CloudSolrClient;
 import org.apache.solr.client.solrj.impl.ClusterStateProvider;
-import org.apache.solr.client.solrj.impl.HttpSolrClient;
+import org.apache.solr.client.solrj.impl.Http2SolrClient;
 import org.apache.solr.client.solrj.impl.StreamingBinaryResponseParser;
 import org.apache.solr.client.solrj.request.GenericSolrRequest;
 import org.apache.solr.common.SolrDocument;
@@ -146,7 +146,7 @@ public class ExportTool extends SolrCLI.ToolBase {
     abstract void exportDocs() throws Exception;
 
     void fetchUniqueKey() throws SolrServerException, IOException {
-      solrClient = new CloudLegacySolrClient.Builder(Collections.singletonList(baseurl)).build();
+      solrClient = new CloudHttp2SolrClient.Builder(Collections.singletonList(baseurl)).build();
       NamedList<Object> response =
           solrClient.request(
               new GenericSolrRequest(
@@ -492,7 +492,7 @@ public class ExportTool extends SolrCLI.ToolBase {
 
       boolean exportDocsFromCore() throws IOException, SolrServerException {
 
-        try (SolrClient client = new HttpSolrClient.Builder(baseurl).build()) {
+        try (SolrClient client = new Http2SolrClient.Builder(baseurl).build()) {
           expectedDocs = getDocCount(replica.getCoreName(), client);
           GenericSolrRequest request;
           ModifiableSolrParams params = new ModifiableSolrParams();
diff --git a/solr/core/src/java/org/apache/solr/util/PackageTool.java b/solr/core/src/java/org/apache/solr/util/PackageTool.java
index 2c6629fa2db..77d3770dbb7 100644
--- a/solr/core/src/java/org/apache/solr/util/PackageTool.java
+++ b/solr/core/src/java/org/apache/solr/util/PackageTool.java
@@ -18,6 +18,7 @@ package org.apache.solr.util;
 
 import static org.apache.solr.packagemanager.PackageUtils.print;
 import static org.apache.solr.packagemanager.PackageUtils.printGreen;
+import static org.apache.solr.util.SolrCLI.getSolrClient;
 
 import java.lang.invoke.MethodHandles;
 import java.nio.file.Files;
@@ -26,14 +27,17 @@ import java.util.List;
 import java.util.Map;
 import org.apache.commons.cli.CommandLine;
 import org.apache.commons.cli.Option;
-import org.apache.http.impl.client.CloseableHttpClient;
 import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.core.config.Configurator;
 import org.apache.lucene.util.SuppressForbidden;
-import org.apache.solr.client.solrj.impl.HttpClientUtil;
-import org.apache.solr.client.solrj.impl.HttpSolrClient;
+import org.apache.solr.client.solrj.SolrClient;
+import org.apache.solr.client.solrj.SolrRequest;
+import org.apache.solr.client.solrj.impl.Http2SolrClient;
+import org.apache.solr.client.solrj.request.GenericSolrRequest;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.SolrException.ErrorCode;
+import org.apache.solr.common.params.CommonParams;
+import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.Pair;
 import org.apache.solr.packagemanager.PackageManager;
 import org.apache.solr.packagemanager.PackageUtils;
@@ -85,7 +89,7 @@ public class PackageTool extends SolrCLI.ToolBase {
       log.info("ZK: {}", zkHost);
       String cmd = cli.getArgList().size() == 0 ? "help" : cli.getArgs()[0];
 
-      try (HttpSolrClient solrClient = new HttpSolrClient.Builder(solrBaseUrl).build()) {
+      try (SolrClient solrClient = new Http2SolrClient.Builder(solrBaseUrl).build()) {
         if (cmd != null) {
           packageManager = new PackageManager(solrClient, solrBaseUrl, zkHost);
           try {
@@ -365,15 +369,15 @@ public class PackageTool extends SolrCLI.ToolBase {
     String zkHost = cli.getOptionValue("zkHost");
     if (zkHost != null) return zkHost;
 
-    String systemInfoUrl = solrUrl + "/admin/info/system";
-    CloseableHttpClient httpClient = SolrCLI.getHttpClient();
-    try {
+    try (SolrClient solrClient = getSolrClient(solrUrl)) {
       // hit Solr to get system info
-      Map<String, Object> systemInfo = SolrCLI.getJson(httpClient, systemInfoUrl, 2, true);
+      NamedList<Object> systemInfo =
+          solrClient.request(
+              new GenericSolrRequest(SolrRequest.METHOD.GET, CommonParams.SYSTEM_INFO_PATH));
 
       // convert raw JSON into user-friendly output
       StatusTool statusTool = new StatusTool();
-      Map<String, Object> status = statusTool.reportStatus(solrUrl + "/", systemInfo, httpClient);
+      Map<String, Object> status = statusTool.reportStatus(systemInfo, solrClient);
       @SuppressWarnings({"unchecked"})
       Map<String, Object> cloud = (Map<String, Object>) status.get("cloud");
       if (cloud != null) {
@@ -383,8 +387,6 @@ public class PackageTool extends SolrCLI.ToolBase {
         }
         zkHost = zookeeper;
       }
-    } finally {
-      HttpClientUtil.close(httpClient);
     }
 
     return zkHost;
diff --git a/solr/core/src/java/org/apache/solr/util/SolrCLI.java b/solr/core/src/java/org/apache/solr/util/SolrCLI.java
index 17573a73f8e..090a02f50ac 100755
--- a/solr/core/src/java/org/apache/solr/util/SolrCLI.java
+++ b/solr/core/src/java/org/apache/solr/util/SolrCLI.java
@@ -20,7 +20,7 @@ import static org.apache.solr.common.SolrException.ErrorCode.FORBIDDEN;
 import static org.apache.solr.common.SolrException.ErrorCode.UNAUTHORIZED;
 import static org.apache.solr.common.params.CommonParams.DISTRIB;
 import static org.apache.solr.common.params.CommonParams.NAME;
-import static org.apache.solr.common.util.Utils.fromJSONString;
+import static org.apache.solr.common.params.CommonParams.SYSTEM_INFO_PATH;
 
 import com.google.common.annotations.VisibleForTesting;
 import java.io.Console;
@@ -33,6 +33,7 @@ import java.lang.invoke.MethodHandles;
 import java.net.ConnectException;
 import java.net.Socket;
 import java.net.SocketException;
+import java.net.URI;
 import java.net.URL;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
@@ -75,32 +76,21 @@ import org.apache.commons.exec.OS;
 import org.apache.commons.exec.environment.EnvironmentUtils;
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.io.file.PathUtils;
-import org.apache.http.HttpEntity;
-import org.apache.http.HttpResponse;
-import org.apache.http.NoHttpResponseException;
-import org.apache.http.StatusLine;
-import org.apache.http.client.ClientProtocolException;
-import org.apache.http.client.HttpClient;
-import org.apache.http.client.HttpResponseException;
-import org.apache.http.client.ResponseHandler;
-import org.apache.http.client.methods.HttpGet;
-import org.apache.http.client.methods.HttpHead;
-import org.apache.http.client.utils.URIBuilder;
-import org.apache.http.conn.ConnectTimeoutException;
-import org.apache.http.impl.client.CloseableHttpClient;
-import org.apache.http.util.EntityUtils;
 import org.apache.lucene.util.Constants;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrQuery;
 import org.apache.solr.client.solrj.SolrRequest;
 import org.apache.solr.client.solrj.SolrServerException;
-import org.apache.solr.client.solrj.impl.CloudLegacySolrClient;
+import org.apache.solr.client.solrj.impl.CloudHttp2SolrClient;
 import org.apache.solr.client.solrj.impl.CloudSolrClient;
-import org.apache.solr.client.solrj.impl.HttpClientUtil;
-import org.apache.solr.client.solrj.impl.HttpSolrClient;
+import org.apache.solr.client.solrj.impl.Http2SolrClient;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
 import org.apache.solr.client.solrj.request.ContentStreamUpdateRequest;
+import org.apache.solr.client.solrj.request.CoreAdminRequest;
+import org.apache.solr.client.solrj.request.GenericSolrRequest;
+import org.apache.solr.client.solrj.request.HealthCheckRequest;
 import org.apache.solr.client.solrj.response.CollectionAdminResponse;
+import org.apache.solr.client.solrj.response.CoreAdminResponse;
 import org.apache.solr.client.solrj.response.QueryResponse;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.cloud.ClusterState;
@@ -121,7 +111,6 @@ import org.apache.solr.core.ConfigSetService;
 import org.apache.solr.security.Sha256AuthenticationProvider;
 import org.apache.solr.util.configuration.SSLConfigurationsFactory;
 import org.noggit.CharArr;
-import org.noggit.JSONParser;
 import org.noggit.JSONWriter;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -209,7 +198,7 @@ public class SolrCLI implements CLIO {
 
       log.debug("Connecting to Solr cluster: {}", zkHost);
       try (var cloudSolrClient =
-          new CloudLegacySolrClient.Builder(Collections.singletonList(zkHost), Optional.empty())
+          new CloudHttp2SolrClient.Builder(Collections.singletonList(zkHost), Optional.empty())
               .build()) {
 
         cloudSolrClient.connect();
@@ -218,7 +207,7 @@ public class SolrCLI implements CLIO {
     }
 
     /** Runs a SolrCloud tool with CloudSolrClient initialized */
-    protected abstract void runCloudTool(CloudLegacySolrClient cloudSolrClient, CommandLine cli)
+    protected abstract void runCloudTool(CloudSolrClient cloudSolrClient, CommandLine cli)
         throws Exception;
   }
 
@@ -579,35 +568,18 @@ public class SolrCLI implements CLIO {
     Throwable rootCause = SolrException.getRootCause(exc);
     boolean wasCommError =
         (rootCause instanceof ConnectException
-            || rootCause instanceof ConnectTimeoutException
-            || rootCause instanceof NoHttpResponseException
+            || rootCause instanceof SolrServerException
             || rootCause instanceof SocketException);
     return wasCommError;
   }
 
-  /**
-   * Tries a simple HEAD request and throws SolrException in case of Authorization error
-   *
-   * @param url the url to do a HEAD request to
-   * @param httpClient the http client to use (make sure it has authentication options set)
-   * @return the HTTP response code
-   * @throws SolrException if auth/autz problems
-   * @throws IOException if connection failure
-   */
-  private static int attemptHttpHead(String url, HttpClient httpClient)
-      throws SolrException, IOException {
-    HttpResponse response =
-        httpClient.execute(new HttpHead(url), HttpClientUtil.createNewHttpClientRequestContext());
-    int code = response.getStatusLine().getStatusCode();
+  private static void checkCodeForAuthError(int code) {
     if (code == UNAUTHORIZED.code || code == FORBIDDEN.code) {
       throw new SolrException(
           SolrException.ErrorCode.getErrorCode(code),
-          "Solr requires authentication for "
-              + url
-              + ". Please supply valid credentials. HTTP code="
+          "Solr requires authentication for request. Please supply valid credentials. HTTP code="
               + code);
     }
-    return code;
   }
 
   private static boolean exceptionIsAuthRelated(Exception exc) {
@@ -615,23 +587,28 @@ public class SolrCLI implements CLIO {
         && Arrays.asList(UNAUTHORIZED.code, FORBIDDEN.code).contains(((SolrException) exc).code()));
   }
 
-  public static CloseableHttpClient getHttpClient() {
-    ModifiableSolrParams params = new ModifiableSolrParams();
-    params.set(HttpClientUtil.PROP_MAX_CONNECTIONS, 128);
-    params.set(HttpClientUtil.PROP_MAX_CONNECTIONS_PER_HOST, 32);
-    params.set(HttpClientUtil.PROP_FOLLOW_REDIRECTS, false);
-    return HttpClientUtil.createClient(params);
+  public static SolrClient getSolrClient(String solrUrl) {
+    return new Http2SolrClient.Builder(solrUrl).maxConnectionsPerHost(32).build();
   }
 
-  @SuppressWarnings("deprecation")
-  public static void closeHttpClient(CloseableHttpClient httpClient) {
-    if (httpClient != null) {
-      try {
-        HttpClientUtil.close(httpClient);
-      } catch (Exception exc) {
-        // safe to ignore, we're just shutting things down
-      }
+  /**
+   * Get Solr base url with port if present and root from URI
+   *
+   * @param uri Full Solr URI (e.g. http://localhost:8983/solr/admin/info/system)
+   * @return Solr base url with port and root (from above example http://localhost:8983/solr)
+   */
+  public static String getSolrUrlFromUri(URI uri) {
+    return uri.getScheme() + "://" + uri.getAuthority() + "/" + uri.getPath().split("/")[1];
+  }
+
+  public static ModifiableSolrParams getSolrParamsFromUri(URI uri) {
+    ModifiableSolrParams paramsMap = new ModifiableSolrParams();
+    String[] params = uri.getQuery() == null ? new String[] {} : uri.getQuery().split("&");
+    for (String param : params) {
+      String[] paramSplit = param.split("=");
+      paramsMap.add(paramSplit[0], paramSplit[1]);
     }
+    return paramsMap;
   }
 
   public static final String JSON_CONTENT_TYPE = "application/json";
@@ -645,232 +622,6 @@ public class SolrCLI implements CLIO {
     return solrClient.request(req);
   }
 
-  /** Useful when a tool just needs to send one request to Solr. */
-  public static Map<String, Object> getJson(String getUrl) throws Exception {
-    Map<String, Object> json = null;
-    CloseableHttpClient httpClient = getHttpClient();
-    try {
-      json = getJson(httpClient, getUrl, 2, true);
-    } finally {
-      closeHttpClient(httpClient);
-    }
-    return json;
-  }
-
-  /** Utility function for sending HTTP GET request to Solr with built-in retry support. */
-  public static Map<String, Object> getJson(
-      HttpClient httpClient, String getUrl, int attempts, boolean isFirstAttempt) throws Exception {
-    Map<String, Object> json = null;
-    if (attempts >= 1) {
-      try {
-        json = getJson(httpClient, getUrl);
-      } catch (Exception exc) {
-        if (exceptionIsAuthRelated(exc)) {
-          throw exc;
-        }
-        if (--attempts > 0 && checkCommunicationError(exc)) {
-          if (!isFirstAttempt) {
-            // only show the log warning after the second attempt fails
-            log.warn(
-                "Request to {} failed, sleeping for 5 seconds before re-trying the request ...",
-                getUrl,
-                exc);
-          }
-          try {
-            Thread.sleep(5000);
-          } catch (InterruptedException ie) {
-            Thread.interrupted();
-          }
-
-          // retry using recursion with one-less attempt available
-          json = getJson(httpClient, getUrl, attempts, false);
-        } else {
-          // no more attempts or error is not retry-able
-          throw exc;
-        }
-      }
-    }
-
-    return json;
-  }
-
-  @SuppressWarnings("unchecked")
-  private static class SolrResponseHandler implements ResponseHandler<Map<String, Object>> {
-    @Override
-    public Map<String, Object> handleResponse(HttpResponse response)
-        throws ClientProtocolException, IOException {
-      HttpEntity entity = response.getEntity();
-      if (entity != null) {
-
-        String respBody = EntityUtils.toString(entity);
-        Object resp = null;
-        try {
-          resp = fromJSONString(respBody);
-        } catch (JSONParser.ParseException pe) {
-          throw new ClientProtocolException(
-              "Expected JSON response from server but received: "
-                  + respBody
-                  + "\nTypically, this indicates a problem with the Solr server; check the Solr server logs for more information.");
-        }
-        if (resp instanceof Map) {
-          return (Map<String, Object>) resp;
-        } else {
-          throw new ClientProtocolException(
-              "Expected JSON object in response but received " + resp);
-        }
-      } else {
-        StatusLine statusLine = response.getStatusLine();
-        throw new HttpResponseException(statusLine.getStatusCode(), statusLine.getReasonPhrase());
-      }
-    }
-  }
-
-  /**
-   * Utility function for sending HTTP GET request to Solr and then doing some validation of the
-   * response.
-   */
-  public static Map<String, Object> getJson(HttpClient httpClient, String getUrl) throws Exception {
-    try {
-      // ensure we're requesting JSON back from Solr
-      HttpGet httpGet =
-          new HttpGet(
-              new URIBuilder(getUrl).setParameter(CommonParams.WT, CommonParams.JSON).build());
-
-      // make the request and get back a parsed JSON object
-      Map<String, Object> json =
-          httpClient.execute(
-              httpGet,
-              new SolrResponseHandler(),
-              HttpClientUtil.createNewHttpClientRequestContext());
-      // check the response JSON from Solr to see if it is an error
-      Long statusCode = asLong("/responseHeader/status", json);
-      if (statusCode != null && statusCode == -1) {
-        throw new SolrServerException(
-            "Unable to determine outcome of GET request to: " + getUrl + "! Response: " + json);
-      } else if (statusCode != null && statusCode != 0) {
-        String errMsg = asString("/error/msg", json);
-        if (errMsg == null) errMsg = String.valueOf(json);
-        throw new SolrServerException(errMsg);
-      } else {
-        // make sure no "failure" object in there either
-        Object failureObj = json.get("failure");
-        if (failureObj != null) {
-          if (failureObj instanceof Map) {
-            Object err = ((Map) failureObj).get("");
-            if (err != null) throw new SolrServerException(err.toString());
-          }
-          throw new SolrServerException(failureObj.toString());
-        }
-      }
-      return json;
-    } catch (ClientProtocolException cpe) {
-      // Currently detecting authentication by string-matching the HTTP response
-      // Perhaps SolrClient should have thrown an exception itself??
-      if (cpe.getMessage().contains("HTTP ERROR 401")
-          || cpe.getMessage().contentEquals("HTTP ERROR 403")) {
-        int code = cpe.getMessage().contains("HTTP ERROR 401") ? 401 : 403;
-        throw new SolrException(
-            SolrException.ErrorCode.getErrorCode(code),
-            "Solr requires authentication for "
-                + getUrl
-                + ". Please supply valid credentials. HTTP code="
-                + code);
-      } else {
-        throw cpe;
-      }
-    }
-  }
-
-  /** Helper function for reading a String value from a JSON Object tree. */
-  public static String asString(String jsonPath, Map<String, Object> json) {
-    return pathAs(String.class, jsonPath, json);
-  }
-
-  /** Helper function for reading a Long value from a JSON Object tree. */
-  public static Long asLong(String jsonPath, Map<String, Object> json) {
-    return pathAs(Long.class, jsonPath, json);
-  }
-
-  /** Helper function for reading a List of Strings from a JSON Object tree. */
-  @SuppressWarnings("unchecked")
-  public static List<String> asList(String jsonPath, Map<String, Object> json) {
-    return pathAs(List.class, jsonPath, json);
-  }
-
-  /** Helper function for reading a Map from a JSON Object tree. */
-  @SuppressWarnings("unchecked")
-  public static Map<String, Object> asMap(String jsonPath, Map<String, Object> json) {
-    return pathAs(Map.class, jsonPath, json);
-  }
-
-  @SuppressWarnings("unchecked")
-  public static <T> T pathAs(Class<T> clazz, String jsonPath, Map<String, Object> json) {
-    T val = null;
-    Object obj = atPath(jsonPath, json);
-    if (obj != null) {
-      if (clazz.isAssignableFrom(obj.getClass())) {
-        val = (T) obj;
-      } else {
-        // no ok if it's not null and of a different type
-        throw new IllegalStateException(
-            "Expected a "
-                + clazz.getName()
-                + " at path "
-                + jsonPath
-                + " but found "
-                + obj
-                + " instead! "
-                + json);
-      }
-    } // it's ok if it is null
-    return val;
-  }
-
-  /**
-   * Helper function for reading an Object of unknown type from a JSON Object tree.
-   *
-   * <p>To find a path to a child that starts with a slash (e.g. queryHandler named /query) you must
-   * escape the slash. For instance /config/requestHandler/\/query/defaults/echoParams would get the
-   * echoParams value for the "/query" request handler.
-   */
-  @SuppressWarnings("unchecked")
-  public static Object atPath(String jsonPath, Map<String, Object> json) {
-    if ("/".equals(jsonPath)) return json;
-
-    if (!jsonPath.startsWith("/"))
-      throw new IllegalArgumentException(
-          "Invalid JSON path: " + jsonPath + "! Must start with a /");
-
-    Map<String, Object> parent = json;
-    Object result = null;
-    String[] path =
-        jsonPath.split("(?<![\\\\])/"); // Break on all slashes _not_ preceeded by a backslash
-    for (int p = 1; p < path.length; p++) {
-      String part = path[p];
-
-      if (part.startsWith("\\")) {
-        part = part.substring(1);
-      }
-
-      Object child = parent.get(part);
-      if (child == null) break;
-
-      if (p == path.length - 1) {
-        // success - found the node at the desired path
-        result = child;
-      } else {
-        if (child instanceof Map) {
-          // keep walking the path down to the desired node
-          parent = (Map<String, Object>) child;
-        } else {
-          // early termination - hit a leaf before the requested node
-          break;
-        }
-      }
-    }
-    return result;
-  }
-
   /** Get the status of a Solr server. */
   public static class StatusTool extends ToolBase {
 
@@ -966,38 +717,35 @@ public class SolrCLI implements CLIO {
 
       if (!solrUrl.endsWith("/")) solrUrl += "/";
 
-      String systemInfoUrl = solrUrl + "admin/info/system";
-      CloseableHttpClient httpClient = getHttpClient();
-      try {
-        // hit Solr to get system info
-        Map<String, Object> systemInfo = getJson(httpClient, systemInfoUrl, 2, true);
+      try (var solrClient = getSolrClient(solrUrl)) {
+        NamedList<Object> systemInfo =
+            solrClient.request(
+                new GenericSolrRequest(SolrRequest.METHOD.GET, CommonParams.SYSTEM_INFO_PATH));
         // convert raw JSON into user-friendly output
-        status = reportStatus(solrUrl, systemInfo, httpClient);
-      } finally {
-        closeHttpClient(httpClient);
+        status = reportStatus(systemInfo, solrClient);
       }
 
       return status;
     }
 
-    public Map<String, Object> reportStatus(
-        String solrUrl, Map<String, Object> info, HttpClient httpClient) throws Exception {
+    public Map<String, Object> reportStatus(NamedList<Object> info, SolrClient solrClient)
+        throws Exception {
       Map<String, Object> status = new LinkedHashMap<>();
 
       String solrHome = (String) info.get("solr_home");
       status.put("solr_home", solrHome != null ? solrHome : "?");
-      status.put("version", asString("/lucene/solr-impl-version", info));
-      status.put("startTime", asString("/jvm/jmx/startTime", info));
-      status.put("uptime", uptime(asLong("/jvm/jmx/upTimeMS", info)));
+      status.put("version", (String) info.findRecursive("lucene", "solr-impl-version"));
+      status.put("startTime", info.findRecursive("jvm", "jmx", "startTime").toString());
+      status.put("uptime", uptime((Long) info.findRecursive("jvm", "jmx", "upTimeMS")));
 
-      String usedMemory = asString("/jvm/memory/used", info);
-      String totalMemory = asString("/jvm/memory/total", info);
+      String usedMemory = (String) info.findRecursive("jvm", "memory", "used");
+      String totalMemory = (String) info.findRecursive("jvm", "memory", "total");
       status.put("memory", usedMemory + " of " + totalMemory);
 
       // if this is a Solr in solrcloud mode, gather some basic cluster info
       if ("solrcloud".equals(info.get("mode"))) {
         String zkHost = (String) info.get("zkHost");
-        status.put("cloud", getCloudStatus(httpClient, solrUrl, zkHost));
+        status.put("cloud", getCloudStatus(solrClient, zkHost));
       }
 
       return status;
@@ -1007,18 +755,19 @@ public class SolrCLI implements CLIO {
      * Calls the CLUSTERSTATUS endpoint in Solr to get basic status information about the SolrCloud
      * cluster.
      */
-    protected Map<String, String> getCloudStatus(
-        HttpClient httpClient, String solrUrl, String zkHost) throws Exception {
+    @SuppressWarnings("unchecked")
+    protected Map<String, String> getCloudStatus(SolrClient solrClient, String zkHost)
+        throws Exception {
       Map<String, String> cloudStatus = new LinkedHashMap<>();
       cloudStatus.put("ZooKeeper", (zkHost != null) ? zkHost : "?");
 
-      String clusterStatusUrl = solrUrl + "admin/collections?action=CLUSTERSTATUS";
-      Map<String, Object> json = getJson(httpClient, clusterStatusUrl, 2, true);
+      NamedList<Object> json = solrClient.request(new CollectionAdminRequest.ClusterStatus());
 
-      List<String> liveNodes = asList("/cluster/live_nodes", json);
+      List<String> liveNodes = (List<String>) json.findRecursive("cluster", "live_nodes");
       cloudStatus.put("liveNodes", String.valueOf(liveNodes.size()));
 
-      Map<String, Object> collections = asMap("/cluster/collections", json);
+      Map<String, Object> collections =
+          ((NamedList) json.findRecursive("cluster", "collections")).asMap();
       cloudStatus.put("collections", String.valueOf(collections.size()));
 
       return cloudStatus;
@@ -1056,12 +805,27 @@ public class SolrCLI implements CLIO {
     protected void runImpl(CommandLine cli) throws Exception {
       String getUrl = cli.getOptionValue("get");
       if (getUrl != null) {
-        Map<String, Object> json = getJson(getUrl);
-
-        // pretty-print the response to stdout
-        CharArr arr = new CharArr();
-        new JSONWriter(arr, 2).write(json);
-        echo(arr.toString());
+        getUrl = getUrl.replace("+", "%20");
+        URI uri = new URI(getUrl);
+        String solrUrl = getSolrUrlFromUri(uri);
+        String path = uri.getPath();
+        try (var solrClient = getSolrClient(solrUrl)) {
+          NamedList<Object> response =
+              solrClient.request(
+                  // For path parameter we need the path without the root so from the second / char
+                  // (because root can be configured)
+                  // E.g URL is http://localhost:8983/solr/admin/info/system path is
+                  // /solr/admin/info/system and the path without root is /admin/info/system
+                  new GenericSolrRequest(
+                      SolrRequest.METHOD.GET,
+                      path.substring(path.indexOf("/", path.indexOf("/") + 1)),
+                      getSolrParamsFromUri(uri)));
+
+          // pretty-print the response to stdout
+          CharArr arr = new CharArr();
+          new JSONWriter(arr, 2).write(response.asMap());
+          echo(arr.toString());
+        }
       }
     }
   } // end ApiTool class
@@ -1244,8 +1008,7 @@ public class SolrCLI implements CLIO {
     }
 
     @Override
-    protected void runCloudTool(CloudLegacySolrClient cloudSolrClient, CommandLine cli)
-        throws Exception {
+    protected void runCloudTool(CloudSolrClient cloudSolrClient, CommandLine cli) throws Exception {
       raiseLogLevelUnlessVerbose(cli);
       String collection = cli.getOptionValue("collection");
       if (collection == null)
@@ -1263,14 +1026,11 @@ public class SolrCLI implements CLIO {
         throw new IllegalArgumentException("Collection " + collection + " not found!");
 
       Collection<Slice> slices = docCollection.getSlices();
-      // Test http code using a HEAD request first, fail fast if authentication failure
-      String urlForColl =
-          zkStateReader.getLeaderUrl(collection, slices.stream().findFirst().get().getName(), 1000);
-      attemptHttpHead(urlForColl, cloudSolrClient.getHttpClient());
 
       SolrQuery q = new SolrQuery("*:*");
       q.setRows(0);
       QueryResponse qr = cloudSolrClient.query(collection, q);
+      checkCodeForAuthError(qr.getStatus());
       String collErr = null;
       long docCount = -1;
       try {
@@ -1313,19 +1073,17 @@ public class SolrCLI implements CLIO {
             q = new SolrQuery("*:*");
             q.setRows(0);
             q.set(DISTRIB, "false");
-            try (HttpSolrClient solr = new HttpSolrClient.Builder(coreUrl).build()) {
-
-              String solrUrl = solr.getBaseURL();
-
-              qr = solr.query(q);
+            try (var solrClient = getSolrClient(coreUrl)) {
+              qr = solrClient.query(q);
               numDocs = qr.getResults().getNumFound();
 
-              int lastSlash = solrUrl.lastIndexOf('/');
-              String systemInfoUrl = solrUrl.substring(0, lastSlash) + "/admin/info/system";
-              Map<String, Object> info = getJson(solr.getHttpClient(), systemInfoUrl, 2, true);
-              uptime = uptime(asLong("/jvm/jmx/upTimeMS", info));
-              String usedMemory = asString("/jvm/memory/used", info);
-              String totalMemory = asString("/jvm/memory/total", info);
+              NamedList<Object> systemInfo =
+                  solrClient.request(
+                      new GenericSolrRequest(
+                          SolrRequest.METHOD.GET, CommonParams.SYSTEM_INFO_PATH));
+              uptime = uptime((Long) systemInfo.findRecursive("jvm", "jmx", "upTimeMS"));
+              String usedMemory = (String) systemInfo.findRecursive("jvm", "memory", "used");
+              String totalMemory = (String) systemInfo.findRecursive("jvm", "memory", "total");
               memory = usedMemory + " of " + totalMemory;
 
               // if we get here, we can trust the state
@@ -1435,7 +1193,7 @@ public class SolrCLI implements CLIO {
             "Must provide either the '-solrUrl' or '-zkHost' parameters!");
 
       try (CloudSolrClient cloudSolrClient =
-          new CloudLegacySolrClient.Builder(Collections.singletonList(zkHost), Optional.empty())
+          new CloudHttp2SolrClient.Builder(Collections.singletonList(zkHost), Optional.empty())
               .build()) {
         cloudSolrClient.connect();
         Set<String> liveNodes = cloudSolrClient.getClusterState().getLiveNodes();
@@ -1466,15 +1224,15 @@ public class SolrCLI implements CLIO {
 
     if (!solrUrl.endsWith("/")) solrUrl += "/";
 
-    String systemInfoUrl = solrUrl + "admin/info/system";
-    CloseableHttpClient httpClient = getHttpClient();
-    try {
+    try (var solrClient = getSolrClient(solrUrl)) {
       // hit Solr to get system info
-      Map<String, Object> systemInfo = getJson(httpClient, systemInfoUrl, 2, true);
+      NamedList<Object> systemInfo =
+          solrClient.request(
+              new GenericSolrRequest(SolrRequest.METHOD.GET, CommonParams.SYSTEM_INFO_PATH));
 
       // convert raw JSON into user-friendly output
       StatusTool statusTool = new StatusTool();
-      Map<String, Object> status = statusTool.reportStatus(solrUrl, systemInfo, httpClient);
+      Map<String, Object> status = statusTool.reportStatus(systemInfo, solrClient);
       @SuppressWarnings("unchecked")
       Map<String, Object> cloud = (Map<String, Object>) status.get("cloud");
       if (cloud != null) {
@@ -1484,17 +1242,15 @@ public class SolrCLI implements CLIO {
         }
         zkHost = zookeeper;
       }
-    } finally {
-      HttpClientUtil.close(httpClient);
     }
 
     return zkHost;
   }
 
-  public static boolean safeCheckCollectionExists(String url, String collection) {
+  public static boolean safeCheckCollectionExists(String solrUrl, String collection) {
     boolean exists = false;
-    try {
-      Map<String, Object> existsCheckResult = getJson(url);
+    try (var solrClient = getSolrClient(solrUrl); ) {
+      NamedList<Object> existsCheckResult = solrClient.request(new CollectionAdminRequest.List());
       @SuppressWarnings("unchecked")
       List<String> collections = (List<String>) existsCheckResult.get("collections");
       exists = collections != null && collections.contains(collection);
@@ -1504,9 +1260,10 @@ public class SolrCLI implements CLIO {
     return exists;
   }
 
-  public static boolean safeCheckCoreExists(String coreStatusUrl, String coreName) {
+  @SuppressWarnings("unchecked")
+  public static boolean safeCheckCoreExists(String solrUrl, String coreName) {
     boolean exists = false;
-    try {
+    try (var solrClient = getSolrClient(solrUrl)) {
       boolean wait = false;
       final long startWaitAt = System.nanoTime();
       do {
@@ -1514,16 +1271,14 @@ public class SolrCLI implements CLIO {
           final int clamPeriodForStatusPollMs = 1000;
           Thread.sleep(clamPeriodForStatusPollMs);
         }
-        Map<String, Object> existsCheckResult = getJson(coreStatusUrl);
-        @SuppressWarnings("unchecked")
-        Map<String, Object> status = (Map<String, Object>) existsCheckResult.get("status");
-        @SuppressWarnings("unchecked")
-        Map<String, Object> coreStatus = (Map<String, Object>) status.get(coreName);
-        @SuppressWarnings("unchecked")
+        NamedList<Object> existsCheckResult =
+            CoreAdminRequest.getStatus(coreName, solrClient).getResponse();
+        NamedList<Object> status = (NamedList) existsCheckResult.get("status");
+        NamedList<Object> coreStatus = (NamedList) status.get(coreName);
         Map<String, Object> failureStatus =
             (Map<String, Object>) existsCheckResult.get("initFailures");
         String errorMsg = (String) failureStatus.get(coreName);
-        final boolean hasName = coreStatus != null && coreStatus.containsKey(NAME);
+        final boolean hasName = coreStatus != null && coreStatus.asMap().containsKey(NAME);
         exists = hasName || errorMsg != null;
         wait = hasName && errorMsg == null && "true".equals(coreStatus.get("isLoading"));
       } while (wait && System.nanoTime() - startWaitAt < MAX_WAIT_FOR_CORE_LOAD_NANOS);
@@ -1566,8 +1321,8 @@ public class SolrCLI implements CLIO {
                 + "create_collection can only be used when running in SolrCloud mode.\n");
       }
 
-      try (CloudSolrClient cloudSolrClient =
-          new CloudLegacySolrClient.Builder(Collections.singletonList(zkHost), Optional.empty())
+      try (CloudHttp2SolrClient cloudSolrClient =
+          new CloudHttp2SolrClient.Builder(Collections.singletonList(zkHost), Optional.empty())
               .build()) {
         echoIfVerbose("\nConnecting to ZooKeeper at " + zkHost + " ...", cli);
         cloudSolrClient.connect();
@@ -1583,10 +1338,10 @@ public class SolrCLI implements CLIO {
             "No live nodes found! Cannot create a collection until "
                 + "there is at least 1 live node in the cluster.");
 
-      String baseUrl = cli.getOptionValue("solrUrl");
-      if (baseUrl == null) {
+      String solrUrl = cli.getOptionValue("solrUrl");
+      if (solrUrl == null) {
         String firstLiveNode = liveNodes.iterator().next();
-        baseUrl = ZkStateReader.from(cloudSolrClient).getBaseUrlForNodeName(firstLiveNode);
+        solrUrl = ZkStateReader.from(cloudSolrClient).getBaseUrlForNodeName(firstLiveNode);
       }
 
       String collectionName = cli.getOptionValue(NAME);
@@ -1632,40 +1387,23 @@ public class SolrCLI implements CLIO {
       }
 
       // since creating a collection is a heavy-weight operation, check for existence first
-      String collectionListUrl = baseUrl + "/admin/collections?action=list";
-      if (safeCheckCollectionExists(collectionListUrl, collectionName)) {
+      if (safeCheckCollectionExists(solrUrl, collectionName)) {
         throw new IllegalStateException(
             "\nCollection '"
                 + collectionName
-                + "' already exists!\nChecked collection existence using Collections API command:\n"
-                + collectionListUrl);
+                + "' already exists!\nChecked collection existence using CollectionAdminRequest");
       }
 
       // doesn't seem to exist ... try to create
-      String createCollectionUrl =
-          String.format(
-              Locale.ROOT,
-              "%s/admin/collections?action=CREATE&name=%s&numShards=%d&replicationFactor=%d",
-              baseUrl,
-              collectionName,
-              numShards,
-              replicationFactor);
-      if (confname != null && !confname.trim().isEmpty()) {
-        createCollectionUrl =
-            createCollectionUrl + String.format(Locale.ROOT, "&collection.configName=%s", confname);
-      }
-
       echoIfVerbose(
-          "\nCreating new collection '"
-              + collectionName
-              + "' using command:\n"
-              + createCollectionUrl
-              + "\n",
-          cli);
+          "\nCreating new collection '" + collectionName + "' using CollectionAdminRequest", cli);
 
-      Map<String, Object> json = null;
+      NamedList<Object> response = null;
       try {
-        json = getJson(createCollectionUrl);
+        response =
+            cloudSolrClient.request(
+                CollectionAdminRequest.createCollection(
+                    collectionName, confname, numShards, replicationFactor));
       } catch (SolrServerException sse) {
         throw new Exception(
             "Failed to create collection '" + collectionName + "' due to: " + sse.getMessage());
@@ -1673,7 +1411,7 @@ public class SolrCLI implements CLIO {
 
       if (cli.hasOption(OPTION_VERBOSE.getOpt())) {
         CharArr arr = new CharArr();
-        new JSONWriter(arr, 2).write(json);
+        new JSONWriter(arr, 2).write(response.asMap());
         echo(arr.toString());
       } else {
         String endMessage =
@@ -1766,11 +1504,13 @@ public class SolrCLI implements CLIO {
 
       String coreName = cli.getOptionValue(NAME);
 
-      String systemInfoUrl = solrUrl + "admin/info/system";
-      CloseableHttpClient httpClient = getHttpClient();
       String coreRootDirectory = null; // usually same as solr home, but not always
-      try {
-        Map<String, Object> systemInfo = getJson(httpClient, systemInfoUrl, 2, true);
+      try (var solrClient = getSolrClient(solrUrl)) {
+        Map<String, Object> systemInfo =
+            solrClient
+                .request(
+                    new GenericSolrRequest(SolrRequest.METHOD.GET, CommonParams.SYSTEM_INFO_PATH))
+                .asMap();
         if ("solrcloud".equals(systemInfo.get("mode"))) {
           throw new IllegalStateException(
               "Solr at "
@@ -1786,18 +1526,13 @@ public class SolrCLI implements CLIO {
         if (coreRootDirectory == null) coreRootDirectory = (String) systemInfo.get("solr_home");
         if (coreRootDirectory == null)
           coreRootDirectory = configsetsDir.getParentFile().getAbsolutePath();
-
-      } finally {
-        closeHttpClient(httpClient);
       }
 
-      String coreStatusUrl = solrUrl + "admin/cores?action=STATUS&core=" + coreName;
-      if (safeCheckCoreExists(coreStatusUrl, coreName)) {
+      if (safeCheckCoreExists(solrUrl, coreName)) {
         throw new IllegalArgumentException(
             "\nCore '"
                 + coreName
-                + "' already exists!\nChecked core existence using Core API command:\n"
-                + coreStatusUrl);
+                + "' already exists!\nChecked core existence using Core API command");
       }
 
       File coreInstanceDir = new File(coreRootDirectory, coreName);
@@ -1828,23 +1563,12 @@ public class SolrCLI implements CLIO {
             cli);
       }
 
-      String createCoreUrl =
-          String.format(
-              Locale.ROOT,
-              "%sadmin/cores?action=CREATE&name=%s&instanceDir=%s",
-              solrUrl,
-              coreName,
-              coreName);
-
-      echoIfVerbose(
-          "\nCreating new core '" + coreName + "' using command:\n" + createCoreUrl + "\n", cli);
+      echoIfVerbose("\nCreating new core '" + coreName + "' using CoreAdminRequest", cli);
 
-      try {
-        Map<String, Object> json = getJson(createCoreUrl);
+      try (var solrClient = getSolrClient(solrUrl)) {
+        CoreAdminResponse res = CoreAdminRequest.createCore(coreName, coreName, solrClient);
         if (cli.hasOption(OPTION_VERBOSE.getOpt())) {
-          CharArr arr = new CharArr();
-          new JSONWriter(arr, 2).write(json);
-          echo(arr.toString());
+          echo(res.jsonStr());
           echo("\n");
         } else {
           echo(String.format(Locale.ROOT, "\nCreated new core '%s'", coreName));
@@ -1883,20 +1607,16 @@ public class SolrCLI implements CLIO {
       String solrUrl = cli.getOptionValue("solrUrl", DEFAULT_SOLR_URL);
       if (!solrUrl.endsWith("/")) solrUrl += "/";
 
-      String systemInfoUrl = solrUrl + "admin/info/system";
-      CloseableHttpClient httpClient = getHttpClient();
-
       ToolBase tool = null;
-      try {
-        Map<String, Object> systemInfo = getJson(httpClient, systemInfoUrl, 2, true);
+      try (var solrClient = getSolrClient(solrUrl)) {
+        NamedList<Object> systemInfo =
+            solrClient.request(new GenericSolrRequest(SolrRequest.METHOD.GET, SYSTEM_INFO_PATH));
         if ("solrcloud".equals(systemInfo.get("mode"))) {
           tool = new CreateCollectionTool(stdout);
         } else {
           tool = new CreateCoreTool(stdout);
         }
         tool.runImpl(cli);
-      } finally {
-        closeHttpClient(httpClient);
       }
     }
   } // end CreateTool class
@@ -2465,26 +2185,25 @@ public class SolrCLI implements CLIO {
       String solrUrl = cli.getOptionValue("solrUrl", DEFAULT_SOLR_URL);
       if (!solrUrl.endsWith("/")) solrUrl += "/";
 
-      String systemInfoUrl = solrUrl + "admin/info/system";
-      CloseableHttpClient httpClient = getHttpClient();
-      try {
-        Map<String, Object> systemInfo = getJson(httpClient, systemInfoUrl, 2, true);
+      try (var solrClient = getSolrClient(solrUrl)) {
+        Map<String, Object> systemInfo =
+            solrClient
+                .request(new GenericSolrRequest(SolrRequest.METHOD.GET, SYSTEM_INFO_PATH))
+                .asMap();
         if ("solrcloud".equals(systemInfo.get("mode"))) {
           deleteCollection(cli);
         } else {
-          deleteCore(cli, httpClient, solrUrl);
+          deleteCore(cli, solrClient);
         }
-      } finally {
-        closeHttpClient(httpClient);
       }
     }
 
     protected void deleteCollection(CommandLine cli) throws Exception {
       String zkHost = getZkHost(cli);
       try (CloudSolrClient cloudSolrClient =
-          new CloudLegacySolrClient.Builder(Collections.singletonList(zkHost), Optional.empty())
-              .withSocketTimeout(30000, TimeUnit.MILLISECONDS)
-              .withConnectionTimeout(15000, TimeUnit.MILLISECONDS)
+          new CloudHttp2SolrClient.Builder(Collections.singletonList(zkHost), Optional.empty())
+              .withInternalClientBuilder(
+                  new Http2SolrClient.Builder().idleTimeout(30000).connectionTimeout(15000))
               .build()) {
         echoIfVerbose("Connecting to ZooKeeper at " + zkHost, cli);
         cloudSolrClient.connect();
@@ -2502,7 +2221,7 @@ public class SolrCLI implements CLIO {
 
       String firstLiveNode = liveNodes.iterator().next();
       ZkStateReader zkStateReader = ZkStateReader.from(cloudSolrClient);
-      String baseUrl = zkStateReader.getBaseUrlForNodeName(firstLiveNode);
+      String solrUrl = zkStateReader.getBaseUrlForNodeName(firstLiveNode);
       String collectionName = cli.getOptionValue(NAME);
       if (!zkStateReader.getClusterState().hasCollection(collectionName)) {
         throw new IllegalArgumentException("Collection " + collectionName + " not found!");
@@ -2548,21 +2267,12 @@ public class SolrCLI implements CLIO {
         }
       }
 
-      String deleteCollectionUrl =
-          String.format(
-              Locale.ROOT, "%s/admin/collections?action=DELETE&name=%s", baseUrl, collectionName);
-
       echoIfVerbose(
-          "\nDeleting collection '"
-              + collectionName
-              + "' using command:\n"
-              + deleteCollectionUrl
-              + "\n",
-          cli);
+          "\nDeleting collection '" + collectionName + "' using CollectionAdminRequest", cli);
 
-      Map<String, Object> json = null;
+      NamedList<Object> response = null;
       try {
-        json = getJson(deleteCollectionUrl);
+        response = cloudSolrClient.request(CollectionAdminRequest.deleteCollection(collectionName));
       } catch (SolrServerException sse) {
         throw new Exception(
             "Failed to delete collection '" + collectionName + "' due to: " + sse.getMessage());
@@ -2582,38 +2292,36 @@ public class SolrCLI implements CLIO {
         }
       }
 
-      if (json != null) {
+      if (response != null) {
         CharArr arr = new CharArr();
-        new JSONWriter(arr, 2).write(json);
+        new JSONWriter(arr, 2).write(response.asMap());
         echo(arr.toString());
         echo("\n");
       }
 
-      echo("Deleted collection '" + collectionName + "' using command:\n" + deleteCollectionUrl);
+      echo("Deleted collection '" + collectionName + "' using CollectionAdminRequest");
     }
 
-    protected void deleteCore(CommandLine cli, CloseableHttpClient httpClient, String solrUrl)
-        throws Exception {
+    protected void deleteCore(CommandLine cli, SolrClient solrClient) throws Exception {
       String coreName = cli.getOptionValue(NAME);
-      String deleteCoreUrl =
-          String.format(
-              Locale.ROOT,
-              "%sadmin/cores?action=UNLOAD&core=%s&deleteIndex=true&deleteDataDir=true&deleteInstanceDir=true",
-              solrUrl,
-              coreName);
 
-      echo("\nDeleting core '" + coreName + "' using command:\n" + deleteCoreUrl + "\n");
+      echo("\nDeleting core '" + coreName + "' using CoreAdminRequest\n");
 
-      Map<String, Object> json = null;
+      NamedList<Object> response = null;
       try {
-        json = getJson(deleteCoreUrl);
+        CoreAdminRequest.Unload unloadRequest = new CoreAdminRequest.Unload(true);
+        unloadRequest.setDeleteIndex(true);
+        unloadRequest.setDeleteDataDir(true);
+        unloadRequest.setDeleteInstanceDir(true);
+        unloadRequest.setCoreName(coreName);
+        response = solrClient.request(unloadRequest);
       } catch (SolrServerException sse) {
         throw new Exception("Failed to delete core '" + coreName + "' due to: " + sse.getMessage());
       }
 
-      if (json != null) {
+      if (response != null) {
         CharArr arr = new CharArr();
-        new JSONWriter(arr, 2).write(json);
+        new JSONWriter(arr, 2).write(response.asMap());
         echoIfVerbose(arr.toString(), cli);
         echoIfVerbose("\n", cli);
       }
@@ -2718,9 +2426,9 @@ public class SolrCLI implements CLIO {
       echo("\nPOSTing request to Config API: " + solrUrl + updatePath);
       echo(jsonBody);
 
-      try (SolrClient solrClient = new HttpSolrClient.Builder(solrUrl).build()) {
+      try (SolrClient solrClient = new Http2SolrClient.Builder(solrUrl).build()) {
         NamedList<Object> result = postJsonToSolr(solrClient, updatePath, jsonBody);
-        Integer statusCode = (Integer) ((NamedList) result.get("responseHeader")).get("status");
+        Integer statusCode = (Integer) result.findRecursive("responseHeader", "status");
         if (statusCode == 0) {
           if (value != null) {
             echo("Successfully " + action + " " + property + " to " + value);
@@ -2945,27 +2653,21 @@ public class SolrCLI implements CLIO {
       // safe check if core / collection already exists
       boolean alreadyExists = false;
       if (nodeStatus.get("cloud") != null) {
-        String collectionListUrl = solrUrl + "/admin/collections?action=list";
-        if (safeCheckCollectionExists(collectionListUrl, collectionName)) {
+        if (safeCheckCollectionExists(solrUrl, collectionName)) {
           alreadyExists = true;
           echo(
               "\nWARNING: Collection '"
                   + collectionName
-                  + "' already exists!\nChecked collection existence using Collections API command:\n"
-                  + collectionListUrl
-                  + "\n");
+                  + "' already exists!\nChecked collection existence using Collections API");
         }
       } else {
         String coreName = collectionName;
-        String coreStatusUrl = solrUrl + "/admin/cores?action=STATUS&core=" + coreName;
-        if (safeCheckCoreExists(coreStatusUrl, coreName)) {
+        if (safeCheckCoreExists(solrUrl, coreName)) {
           alreadyExists = true;
           echo(
               "\nWARNING: Core '"
                   + coreName
-                  + "' already exists!\nChecked core existence using Core API command:\n"
-                  + coreStatusUrl
-                  + "\n");
+                  + "' already exists!\nChecked core existence using Core API command");
         }
       }
 
@@ -3022,7 +2724,7 @@ public class SolrCLI implements CLIO {
               "exampledocs directory not found, skipping indexing step for the techproducts example");
         }
       } else if ("films".equals(exampleName) && !alreadyExists) {
-        SolrClient solrClient = new HttpSolrClient.Builder(solrUrl).build();
+        SolrClient solrClient = new Http2SolrClient.Builder(solrUrl).build();
 
         echo("Adding dense vector field type to films schema \"_default\"");
         try {
@@ -3475,7 +3177,6 @@ public class SolrCLI implements CLIO {
       String collectionName = "gettingstarted";
 
       File configsetsDir = new File(serverDir, "solr/configsets");
-      String collectionListUrl = solrUrl + "/admin/collections?action=list";
 
       if (prompt) {
         echo(
@@ -3491,7 +3192,7 @@ public class SolrCLI implements CLIO {
                   collectionName);
 
           // Test for existence and then prompt to either create another or skip the create step
-          if (safeCheckCollectionExists(collectionListUrl, collectionName)) {
+          if (safeCheckCollectionExists(solrUrl, collectionName)) {
             echo("\nCollection '" + collectionName + "' already exists!");
             int oneOrTwo =
                 promptForInt(
@@ -3547,7 +3248,7 @@ public class SolrCLI implements CLIO {
         }
       } else {
         // must verify if default collection exists
-        if (safeCheckCollectionExists(collectionListUrl, collectionName)) {
+        if (safeCheckCollectionExists(solrUrl, collectionName)) {
           echo(
               "\nCollection '"
                   + collectionName
@@ -3950,11 +3651,13 @@ public class SolrCLI implements CLIO {
       long timeout =
           System.nanoTime()
               + TimeUnit.NANOSECONDS.convert(timeoutMs.orElse(1000L), TimeUnit.MILLISECONDS);
-      try {
-        attemptHttpHead(url, getHttpClient());
+      try (SolrClient solrClient = getSolrClient(url)) {
+        NamedList<Object> response = solrClient.request(new HealthCheckRequest());
+        Integer statusCode = (Integer) response.findRecursive("responseHeader", "status");
+        checkCodeForAuthError(statusCode);
       } catch (SolrException se) {
-        throw se; // Auth error
-      } catch (IOException e) {
+        throw se;
+      } catch (IOException | SolrServerException e) {
         log.debug("Opening connection to {} failed, Solr does not seem to be running", url, e);
         return 0;
       }
@@ -4078,7 +3781,7 @@ public class SolrCLI implements CLIO {
     }
 
     private static boolean runningSolrIsCloud(String url) throws Exception {
-      try (final SolrClient client = new HttpSolrClient.Builder(url).build()) {
+      try (final SolrClient client = new Http2SolrClient.Builder(url).build()) {
         final SolrRequest<CollectionAdminResponse> request =
             new CollectionAdminRequest.ClusterStatus();
         final CollectionAdminResponse response = request.process(client);
diff --git a/solr/core/src/java/org/apache/solr/util/SolrLogPostTool.java b/solr/core/src/java/org/apache/solr/util/SolrLogPostTool.java
index 371cc5ad97e..d1c361b9164 100644
--- a/solr/core/src/java/org/apache/solr/util/SolrLogPostTool.java
+++ b/solr/core/src/java/org/apache/solr/util/SolrLogPostTool.java
@@ -34,7 +34,7 @@ import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrServerException;
-import org.apache.solr.client.solrj.impl.HttpSolrClient;
+import org.apache.solr.client.solrj.impl.Http2SolrClient;
 import org.apache.solr.client.solrj.request.UpdateRequest;
 import org.apache.solr.common.SolrInputDocument;
 import org.apache.solr.common.SolrInputField;
@@ -65,8 +65,8 @@ public class SolrLogPostTool {
     String baseUrl = args[0];
     String root = args[1];
 
-    HttpSolrClient.Builder builder = new HttpSolrClient.Builder();
-    try (SolrClient client = builder.withBaseSolrUrl(baseUrl).build()) {
+    Http2SolrClient.Builder builder = new Http2SolrClient.Builder(baseUrl);
+    try (SolrClient client = builder.build()) {
       int rec = 0;
       UpdateRequest request = new UpdateRequest();
 
diff --git a/solr/core/src/test/org/apache/solr/cloud/SolrCloudExampleTest.java b/solr/core/src/test/org/apache/solr/cloud/SolrCloudExampleTest.java
index 3cd00c1e22d..c73c43c9413 100644
--- a/solr/core/src/test/org/apache/solr/cloud/SolrCloudExampleTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/SolrCloudExampleTest.java
@@ -16,16 +16,12 @@
  */
 package org.apache.solr.cloud;
 
-import static java.util.Arrays.asList;
-import static org.apache.solr.common.util.Utils.fromJSONString;
-import static org.apache.solr.common.util.Utils.getObjectByPath;
-
 import java.io.File;
 import java.lang.invoke.MethodHandles;
-import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
@@ -37,18 +33,17 @@ import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import org.apache.commons.cli.CommandLine;
-import org.apache.http.HttpEntity;
-import org.apache.http.client.methods.HttpGet;
-import org.apache.http.util.EntityUtils;
+import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrQuery;
-import org.apache.solr.client.solrj.impl.CloudLegacySolrClient;
-import org.apache.solr.client.solrj.impl.CloudSolrClient;
+import org.apache.solr.client.solrj.SolrRequest;
+import org.apache.solr.client.solrj.request.GenericSolrRequest;
 import org.apache.solr.client.solrj.request.StreamingUpdateRequest;
 import org.apache.solr.client.solrj.response.QueryResponse;
 import org.apache.solr.common.cloud.DocCollection;
 import org.apache.solr.common.cloud.Replica;
 import org.apache.solr.common.cloud.Slice;
 import org.apache.solr.common.cloud.ZkStateReader;
+import org.apache.solr.common.util.NamedList;
 import org.apache.solr.util.ExternalPaths;
 import org.apache.solr.util.SolrCLI;
 import org.junit.Test;
@@ -222,109 +217,100 @@ public class SolrCloudExampleTest extends AbstractFullDistribZkTestBase {
    * Uses the SolrCLI config action to activate soft auto-commits for the getting started
    * collection.
    */
+  @SuppressWarnings("unchecked")
   protected void doTestConfigUpdate(String testCollectionName, String solrUrl) throws Exception {
     if (!solrUrl.endsWith("/")) solrUrl += "/";
-    String configUrl = solrUrl + testCollectionName + "/config";
-
-    Map<String, Object> configJson = SolrCLI.getJson(configUrl);
-    Object maxTimeFromConfig =
-        SolrCLI.atPath("/config/updateHandler/autoSoftCommit/maxTime", configJson);
-    assertNotNull(maxTimeFromConfig);
-    assertEquals(-1L, maxTimeFromConfig);
-
-    String prop = "updateHandler.autoSoftCommit.maxTime";
-    Long maxTime = 3000L;
-    String[] args =
-        new String[] {
-          "-collection", testCollectionName,
-          "-property", prop,
-          "-value", maxTime.toString(),
-          "-solrUrl", solrUrl
-        };
-
-    Map<String, Long> startTimes = getSoftAutocommitInterval(testCollectionName);
-
-    SolrCLI.ConfigTool tool = new SolrCLI.ConfigTool();
-    CommandLine cli =
-        SolrCLI.processCommandLineArgs(SolrCLI.joinCommonAndToolOptions(tool.getOptions()), args);
-    log.info("Sending set-property '{}'={} to SolrCLI.ConfigTool.", prop, maxTime);
-    assertEquals("Set config property failed!", 0, tool.runTool(cli));
 
-    configJson = SolrCLI.getJson(configUrl);
-    maxTimeFromConfig = SolrCLI.atPath("/config/updateHandler/autoSoftCommit/maxTime", configJson);
-    assertNotNull(maxTimeFromConfig);
-    assertEquals(maxTime, maxTimeFromConfig);
+    try (SolrClient solrClient = SolrCLI.getSolrClient(solrUrl)) {
+      NamedList<Object> configJson =
+          solrClient.request(
+              new GenericSolrRequest(SolrRequest.METHOD.GET, "/" + testCollectionName + "/config"));
+      Object maxTimeFromConfig =
+          configJson._get("/config/updateHandler/autoSoftCommit/maxTime", Collections.emptyMap());
+      assertNotNull(maxTimeFromConfig);
+      assertEquals(-1, maxTimeFromConfig);
+
+      String prop = "updateHandler.autoSoftCommit.maxTime";
+      Integer maxTime = 3000;
+      String[] args =
+          new String[] {
+            "-collection", testCollectionName,
+            "-property", prop,
+            "-value", maxTime.toString(),
+            "-solrUrl", solrUrl
+          };
+
+      Map<String, Integer> startTimes = getSoftAutocommitInterval(testCollectionName, solrClient);
+
+      SolrCLI.ConfigTool tool = new SolrCLI.ConfigTool();
+      CommandLine cli =
+          SolrCLI.processCommandLineArgs(SolrCLI.joinCommonAndToolOptions(tool.getOptions()), args);
+      log.info("Sending set-property '{}'={} to SolrCLI.ConfigTool.", prop, maxTime);
+      assertEquals("Set config property failed!", 0, tool.runTool(cli));
+
+      configJson =
+          solrClient.request(
+              new GenericSolrRequest(SolrRequest.METHOD.GET, "/" + testCollectionName + "/config"));
+      maxTimeFromConfig =
+          configJson._get("/config/updateHandler/autoSoftCommit/maxTime", Collections.emptyMap());
+      assertNotNull(maxTimeFromConfig);
+      assertEquals(maxTime, maxTimeFromConfig);
 
-    // Just check that we can access paths with slashes in them both through an intermediate method
-    // and explicitly using atPath.
-    assertEquals(
-        "Should have been able to get a value from the /query request handler",
-        "explicit",
-        SolrCLI.asString("/config/requestHandler/\\/query/defaults/echoParams", configJson));
-
-    assertEquals(
-        "Should have been able to get a value from the /query request handler",
-        "explicit",
-        SolrCLI.atPath("/config/requestHandler/\\/query/defaults/echoParams", configJson));
-
-    if (log.isInfoEnabled()) {
-      log.info("live_nodes_count :  {}", cloudClient.getClusterState().getLiveNodes());
-    }
+      if (log.isInfoEnabled()) {
+        log.info("live_nodes_count :  {}", cloudClient.getClusterState().getLiveNodes());
+      }
 
-    // Since it takes some time for this command to complete we need to make sure all the reloads
-    // for all the cores have been done.
-    boolean allGood = false;
-    Map<String, Long> curSoftCommitInterval = null;
-    for (int idx = 0; idx < 600 && allGood == false; ++idx) {
-      curSoftCommitInterval = getSoftAutocommitInterval(testCollectionName);
-      // no point in even trying if they're not the same size!
-      if (curSoftCommitInterval.size() > 0 && curSoftCommitInterval.size() == startTimes.size()) {
-        allGood = true;
-        for (Map.Entry<String, Long> currEntry : curSoftCommitInterval.entrySet()) {
-          if (currEntry.getValue().equals(maxTime) == false) {
-            allGood = false;
+      // Need to use the _get(List, Object) here because of /query in the path
+      assertEquals(
+          "Should have been able to get a value from the /query request handler",
+          "explicit",
+          configJson._get(
+              Arrays.asList("config", "requestHandler", "/query", "defaults", "echoParams"),
+              Collections.emptyMap()));
+
+      // Since it takes some time for this command to complete we need to make sure all the reloads
+      // for all the cores have been done.
+      boolean allGood = false;
+      Map<String, Integer> curSoftCommitInterval = null;
+      for (int idx = 0; idx < 600 && allGood == false; ++idx) {
+        curSoftCommitInterval = getSoftAutocommitInterval(testCollectionName, solrClient);
+        // no point in even trying if they're not the same size!
+        if (curSoftCommitInterval.size() > 0 && curSoftCommitInterval.size() == startTimes.size()) {
+          allGood = true;
+          for (Map.Entry<String, Integer> currEntry : curSoftCommitInterval.entrySet()) {
+            if (currEntry.getValue().equals(maxTime) == false) {
+              allGood = false;
+            }
           }
         }
+        if (allGood == false) {
+          Thread.sleep(100);
+        }
       }
-      if (allGood == false) {
-        Thread.sleep(100);
-      }
+      assertTrue("All cores should have been reloaded within 60 seconds!!!", allGood);
     }
-    assertTrue("All cores should have been reloaded within 60 seconds!!!", allGood);
   }
 
   // Collect all the autoSoftCommit intervals.
-  private Map<String, Long> getSoftAutocommitInterval(String collection) throws Exception {
-    Map<String, Long> ret = new HashMap<>();
+  @SuppressWarnings("unchecked")
+  private Map<String, Integer> getSoftAutocommitInterval(String collection, SolrClient solrClient)
+      throws Exception {
+    Map<String, Integer> ret = new HashMap<>();
     DocCollection coll = cloudClient.getClusterState().getCollection(collection);
     for (Slice slice : coll.getActiveSlices()) {
       for (Replica replica : slice.getReplicas()) {
-        String uri =
-            ""
-                + replica.get(ZkStateReader.BASE_URL_PROP)
-                + "/"
-                + replica.get(ZkStateReader.CORE_NAME_PROP)
-                + "/config";
-        Map<?, ?> respMap = getAsMap(cloudClient, uri);
-        Long maxTime =
-            (Long)
-                (getObjectByPath(
-                    respMap, true, asList("config", "updateHandler", "autoSoftCommit", "maxTime")));
+        NamedList<Object> configJson =
+            solrClient.request(
+                new GenericSolrRequest(
+                    SolrRequest.METHOD.GET,
+                    "/" + replica.get(ZkStateReader.CORE_NAME_PROP) + "/config"));
+        Integer maxTime =
+            (Integer)
+                configJson._get(
+                    "/config/updateHandler/autoSoftCommit/maxTime", Collections.emptyMap());
         ret.put(replica.getCoreName(), maxTime);
       }
     }
     return ret;
   }
-
-  private Map<?, ?> getAsMap(CloudSolrClient cloudClient, String uri) throws Exception {
-    HttpGet get = new HttpGet(uri);
-    HttpEntity entity = null;
-    try {
-      entity = ((CloudLegacySolrClient) cloudClient).getHttpClient().execute(get).getEntity();
-      String response = EntityUtils.toString(entity, StandardCharsets.UTF_8);
-      return (Map<?, ?>) fromJSONString(response);
-    } finally {
-      EntityUtils.consumeQuietly(entity);
-    }
-  }
 }
diff --git a/solr/core/src/test/org/apache/solr/handler/RequestHandlerMetricsTest.java b/solr/core/src/test/org/apache/solr/handler/RequestHandlerMetricsTest.java
index 164a897459e..e3eecfc8cb3 100644
--- a/solr/core/src/test/org/apache/solr/handler/RequestHandlerMetricsTest.java
+++ b/solr/core/src/test/org/apache/solr/handler/RequestHandlerMetricsTest.java
@@ -81,7 +81,7 @@ public class RequestHandlerMetricsTest extends SolrCloudTestCase {
     cloudClient.query(collection2, solrQuery);
 
     NamedList<Object> response =
-        cloudClient.request(new GenericSolrRequest(SolrRequest.METHOD.GET, "/admin/metrics", null));
+        cloudClient.request(new GenericSolrRequest(SolrRequest.METHOD.GET, "/admin/metrics"));
 
     NamedList<Object> metrics = (NamedList<Object>) response.get("metrics");
 
diff --git a/solr/core/src/test/org/apache/solr/handler/V2ApiIntegrationTest.java b/solr/core/src/test/org/apache/solr/handler/V2ApiIntegrationTest.java
index ab89ec0237e..284f268acc2 100644
--- a/solr/core/src/test/org/apache/solr/handler/V2ApiIntegrationTest.java
+++ b/solr/core/src/test/org/apache/solr/handler/V2ApiIntegrationTest.java
@@ -28,11 +28,11 @@ import org.apache.solr.client.solrj.SolrServerException;
 import org.apache.solr.client.solrj.impl.BaseHttpSolrClient;
 import org.apache.solr.client.solrj.impl.BinaryResponseParser;
 import org.apache.solr.client.solrj.impl.CloudSolrClient;
+import org.apache.solr.client.solrj.impl.JsonMapResponseParser;
 import org.apache.solr.client.solrj.impl.NoOpResponseParser;
 import org.apache.solr.client.solrj.impl.XMLResponseParser;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
 import org.apache.solr.client.solrj.request.V2Request;
-import org.apache.solr.client.solrj.response.DelegationTokenResponse;
 import org.apache.solr.client.solrj.response.V2Response;
 import org.apache.solr.cloud.SolrCloudTestCase;
 import org.apache.solr.common.SolrDocumentList;
@@ -88,16 +88,11 @@ public class V2ApiIntegrationTest extends SolrCloudTestCase {
     String notFoundPath = "/c/" + COLL_NAME + "/abccdef";
     String incorrectPayload = "{rebalance-leaders: {maxAtOnce: abc, maxWaitSeconds: xyz}}";
     testException(new XMLResponseParser(), 404, notFoundPath, incorrectPayload);
-    testException(
-        new DelegationTokenResponse.JsonMapResponseParser(), 404, notFoundPath, incorrectPayload);
+    testException(new JsonMapResponseParser(), 404, notFoundPath, incorrectPayload);
     testException(new BinaryResponseParser(), 404, notFoundPath, incorrectPayload);
     testException(new XMLResponseParser(), 400, "/c/" + COLL_NAME, incorrectPayload);
     testException(new BinaryResponseParser(), 400, "/c/" + COLL_NAME, incorrectPayload);
-    testException(
-        new DelegationTokenResponse.JsonMapResponseParser(),
-        400,
-        "/c/" + COLL_NAME,
-        incorrectPayload);
+    testException(new JsonMapResponseParser(), 400, "/c/" + COLL_NAME, incorrectPayload);
   }
 
   @Test
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/HealthCheckHandlerTest.java b/solr/core/src/test/org/apache/solr/handler/admin/HealthCheckHandlerTest.java
index 4a01db8ff0f..6ce83dfd111 100644
--- a/solr/core/src/test/org/apache/solr/handler/admin/HealthCheckHandlerTest.java
+++ b/solr/core/src/test/org/apache/solr/handler/admin/HealthCheckHandlerTest.java
@@ -43,7 +43,6 @@ import org.apache.solr.common.cloud.ClusterState;
 import org.apache.solr.common.cloud.Replica;
 import org.apache.solr.common.cloud.ZkStateReader;
 import org.apache.solr.common.params.CommonParams;
-import org.apache.solr.common.params.ModifiableSolrParams;
 import org.apache.solr.core.CoreDescriptor;
 import org.apache.solr.embedded.JettySolrRunner;
 import org.junit.BeforeClass;
@@ -58,8 +57,7 @@ public class HealthCheckHandlerTest extends SolrCloudTestCase {
   @Test
   public void testHealthCheckHandler() throws Exception {
     GenericSolrRequest req =
-        new GenericSolrRequest(
-            SolrRequest.METHOD.GET, HEALTH_CHECK_HANDLER_PATH, new ModifiableSolrParams());
+        new GenericSolrRequest(SolrRequest.METHOD.GET, HEALTH_CHECK_HANDLER_PATH);
 
     // positive check that our only existing "healthy" node works with cloud client
     // NOTE: this is using GenericSolrRequest, not HealthCheckRequest which is why it passes
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/PropertiesRequestHandlerTest.java b/solr/core/src/test/org/apache/solr/handler/admin/PropertiesRequestHandlerTest.java
index 20fc0bc6b77..7332d8525a5 100644
--- a/solr/core/src/test/org/apache/solr/handler/admin/PropertiesRequestHandlerTest.java
+++ b/solr/core/src/test/org/apache/solr/handler/admin/PropertiesRequestHandlerTest.java
@@ -21,7 +21,6 @@ import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrRequest;
 import org.apache.solr.client.solrj.embedded.EmbeddedSolrServer;
 import org.apache.solr.client.solrj.request.GenericSolrRequest;
-import org.apache.solr.common.params.ModifiableSolrParams;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.util.RedactionUtils;
 import org.junit.BeforeClass;
@@ -64,9 +63,7 @@ public class PropertiesRequestHandlerTest extends SolrTestCaseJ4 {
     SolrClient client = new EmbeddedSolrServer(h.getCore());
 
     NamedList<Object> properties =
-        client.request(
-            new GenericSolrRequest(
-                SolrRequest.METHOD.GET, "/admin/info/properties", new ModifiableSolrParams()));
+        client.request(new GenericSolrRequest(SolrRequest.METHOD.GET, "/admin/info/properties"));
 
     return (NamedList<Object>) properties.get("system.properties");
   }
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/ZookeeperStatusHandlerFailureTest.java b/solr/core/src/test/org/apache/solr/handler/admin/ZookeeperStatusHandlerFailureTest.java
index 2c6b980a57e..8d3b18212f1 100644
--- a/solr/core/src/test/org/apache/solr/handler/admin/ZookeeperStatusHandlerFailureTest.java
+++ b/solr/core/src/test/org/apache/solr/handler/admin/ZookeeperStatusHandlerFailureTest.java
@@ -27,10 +27,9 @@ import java.util.concurrent.TimeoutException;
 import org.apache.solr.client.solrj.SolrRequest;
 import org.apache.solr.client.solrj.SolrServerException;
 import org.apache.solr.client.solrj.impl.HttpSolrClient;
+import org.apache.solr.client.solrj.impl.JsonMapResponseParser;
 import org.apache.solr.client.solrj.request.GenericSolrRequest;
-import org.apache.solr.client.solrj.response.DelegationTokenResponse;
 import org.apache.solr.cloud.SolrCloudTestCase;
-import org.apache.solr.common.params.ModifiableSolrParams;
 import org.apache.solr.common.util.NamedList;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -53,9 +52,8 @@ public class ZookeeperStatusHandlerFailureTest extends SolrCloudTestCase {
     URL baseUrl = cluster.getJettySolrRunner(0).getBaseUrl();
     HttpSolrClient solr = new HttpSolrClient.Builder(baseUrl.toString()).build();
     GenericSolrRequest mntrReq =
-        new GenericSolrRequest(
-            SolrRequest.METHOD.GET, "/admin/zookeeper/status", new ModifiableSolrParams());
-    mntrReq.setResponseParser(new DelegationTokenResponse.JsonMapResponseParser());
+        new GenericSolrRequest(SolrRequest.METHOD.GET, "/admin/zookeeper/status");
+    mntrReq.setResponseParser(new JsonMapResponseParser());
     NamedList<Object> nl = solr.httpUriRequest(mntrReq).future.get(10000, TimeUnit.MILLISECONDS);
 
     assertEquals("zkStatus", nl.getName(1));
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/ZookeeperStatusHandlerTest.java b/solr/core/src/test/org/apache/solr/handler/admin/ZookeeperStatusHandlerTest.java
index 47f77e2431a..038431df7c7 100644
--- a/solr/core/src/test/org/apache/solr/handler/admin/ZookeeperStatusHandlerTest.java
+++ b/solr/core/src/test/org/apache/solr/handler/admin/ZookeeperStatusHandlerTest.java
@@ -34,12 +34,11 @@ import java.util.concurrent.TimeoutException;
 import org.apache.solr.client.solrj.SolrRequest;
 import org.apache.solr.client.solrj.SolrServerException;
 import org.apache.solr.client.solrj.impl.HttpSolrClient;
+import org.apache.solr.client.solrj.impl.JsonMapResponseParser;
 import org.apache.solr.client.solrj.request.GenericSolrRequest;
-import org.apache.solr.client.solrj.response.DelegationTokenResponse;
 import org.apache.solr.cloud.SolrCloudTestCase;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.cloud.ZkDynamicConfig;
-import org.apache.solr.common.params.ModifiableSolrParams;
 import org.apache.solr.common.util.NamedList;
 import org.junit.After;
 import org.junit.Before;
@@ -78,9 +77,8 @@ public class ZookeeperStatusHandlerTest extends SolrCloudTestCase {
     URL baseUrl = cluster.getJettySolrRunner(0).getBaseUrl();
     HttpSolrClient solr = new HttpSolrClient.Builder(baseUrl.toString()).build();
     GenericSolrRequest mntrReq =
-        new GenericSolrRequest(
-            SolrRequest.METHOD.GET, "/admin/zookeeper/status", new ModifiableSolrParams());
-    mntrReq.setResponseParser(new DelegationTokenResponse.JsonMapResponseParser());
+        new GenericSolrRequest(SolrRequest.METHOD.GET, "/admin/zookeeper/status");
+    mntrReq.setResponseParser(new JsonMapResponseParser());
     NamedList<Object> nl = solr.httpUriRequest(mntrReq).future.get(10000, TimeUnit.MILLISECONDS);
 
     assertEquals("zkStatus", nl.getName(1));
diff --git a/solr/core/src/test/org/apache/solr/handler/component/DistributedTermsComponentTest.java b/solr/core/src/test/org/apache/solr/handler/component/DistributedTermsComponentTest.java
index 2cb739107d0..430b9074699 100644
--- a/solr/core/src/test/org/apache/solr/handler/component/DistributedTermsComponentTest.java
+++ b/solr/core/src/test/org/apache/solr/handler/component/DistributedTermsComponentTest.java
@@ -27,9 +27,9 @@ import org.apache.solr.client.solrj.ResponseParser;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrServerException;
 import org.apache.solr.client.solrj.impl.BinaryResponseParser;
+import org.apache.solr.client.solrj.impl.JsonMapResponseParser;
 import org.apache.solr.client.solrj.impl.XMLResponseParser;
 import org.apache.solr.client.solrj.request.QueryRequest;
-import org.apache.solr.client.solrj.response.DelegationTokenResponse;
 import org.apache.solr.client.solrj.response.QueryResponse;
 import org.apache.solr.common.params.ModifiableSolrParams;
 import org.apache.solr.common.params.SolrParams;
@@ -251,9 +251,7 @@ public class DistributedTermsComponentTest extends BaseDistributedSearchTestCase
   private ResponseParser[] getResponseParsers() {
     // can't use junit parameters as this would also require RunWith
     return new ResponseParser[] {
-      new BinaryResponseParser(),
-      new DelegationTokenResponse.JsonMapResponseParser(),
-      new XMLResponseParser()
+      new BinaryResponseParser(), new JsonMapResponseParser(), new XMLResponseParser()
     };
   }
 }
diff --git a/solr/core/src/test/org/apache/solr/search/TestTaskManagement.java b/solr/core/src/test/org/apache/solr/search/TestTaskManagement.java
index 187fe048e12..8efeb9fb583 100644
--- a/solr/core/src/test/org/apache/solr/search/TestTaskManagement.java
+++ b/solr/core/src/test/org/apache/solr/search/TestTaskManagement.java
@@ -185,7 +185,7 @@ public class TestTaskManagement extends SolrCloudTestCase {
     NamedList<Object> response =
         cluster
             .getSolrClient(COLLECTION_NAME)
-            .request(new GenericSolrRequest(SolrRequest.METHOD.GET, "/tasks/list", null));
+            .request(new GenericSolrRequest(SolrRequest.METHOD.GET, "/tasks/list"));
     return (NamedList<String>) response.get("taskList");
   }
 
diff --git a/solr/core/src/test/org/apache/solr/security/BasicAuthIntegrationTest.java b/solr/core/src/test/org/apache/solr/security/BasicAuthIntegrationTest.java
index 0072eefface..bcc7d0d67d8 100644
--- a/solr/core/src/test/org/apache/solr/security/BasicAuthIntegrationTest.java
+++ b/solr/core/src/test/org/apache/solr/security/BasicAuthIntegrationTest.java
@@ -142,8 +142,7 @@ public class BasicAuthIntegrationTest extends SolrCloudAuthTestCase {
                 .build();
       } else {
         GenericSolrRequest genericSolrRequest =
-            new GenericSolrRequest(
-                SolrRequest.METHOD.POST, authcPrefix, new ModifiableSolrParams());
+            new GenericSolrRequest(SolrRequest.METHOD.POST, authcPrefix);
         genericSolrRequest.setContentWriter(
             new StringPayloadContentWriter(command, CommonParams.JSON_MIME));
         genericReq = genericSolrRequest;
diff --git a/solr/core/src/test/org/apache/solr/util/ApiToolTest.java b/solr/core/src/test/org/apache/solr/util/ApiToolTest.java
new file mode 100644
index 00000000000..245f73a144e
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/util/ApiToolTest.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.util;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Arrays;
+import org.apache.solr.SolrTestCase;
+import org.apache.solr.common.params.ModifiableSolrParams;
+import org.junit.Test;
+
+/** Unit test for SolrCLI's ApiTool */
+public class ApiToolTest extends SolrTestCase {
+
+  @Test
+  public void testParsingGetUrl() throws URISyntaxException {
+    SolrCLI.ApiTool apiTool = new SolrCLI.ApiTool();
+
+    String urlWithPercent20SpaceDelimiter =
+        "http://localhost:8983/solr/COLL_NAME/sql?stmt=select%20id%20from%20COLL_NAME%20limit%2010";
+    String urlWithPlusSpaceDelimiter =
+        "http://localhost:8983/solr/COLL_NAME/sql?stmt=select+id+from+COLL_NAME+limit+10";
+
+    for (String url : Arrays.asList(urlWithPercent20SpaceDelimiter, urlWithPlusSpaceDelimiter)) {
+      url = url.replace("+", "%20");
+
+      URI uri = new URI(url);
+      ModifiableSolrParams params = SolrCLI.getSolrParamsFromUri(uri);
+      assertEquals(1, params.size());
+      assertEquals("select id from COLL_NAME limit 10", params.get("stmt"));
+    }
+  }
+}
diff --git a/solr/core/src/test/org/apache/solr/util/TestSolrCLIRunExample.java b/solr/core/src/test/org/apache/solr/util/TestSolrCLIRunExample.java
index 1ce6666a856..e822c3fe24e 100644
--- a/solr/core/src/test/org/apache/solr/util/TestSolrCLIRunExample.java
+++ b/solr/core/src/test/org/apache/solr/util/TestSolrCLIRunExample.java
@@ -506,8 +506,7 @@ public class TestSolrCLIRunExample extends SolrTestCaseJ4 {
 
     // verify Solr is running on the expected port and verify the collection exists
     String solrUrl = "http://localhost:" + bindPort + "/solr";
-    String collectionListUrl = solrUrl + "/admin/collections?action=list";
-    if (!SolrCLI.safeCheckCollectionExists(collectionListUrl, collectionName)) {
+    if (!SolrCLI.safeCheckCollectionExists(solrUrl, collectionName)) {
       fail(
           "After running Solr cloud example, test collection '"
               + collectionName
diff --git a/solr/core/src/test/org/apache/solr/util/tracing/TestDistributedTracing.java b/solr/core/src/test/org/apache/solr/util/tracing/TestDistributedTracing.java
index f547d2a50c9..3c51208c53f 100644
--- a/solr/core/src/test/org/apache/solr/util/tracing/TestDistributedTracing.java
+++ b/solr/core/src/test/org/apache/solr/util/tracing/TestDistributedTracing.java
@@ -120,7 +120,7 @@ public class TestDistributedTracing extends SolrCloudTestCase {
     List<MockSpan> finishedSpans;
 
     // Admin API call
-    cloudClient.request(new GenericSolrRequest(SolrRequest.METHOD.GET, "/admin/metrics", params()));
+    cloudClient.request(new GenericSolrRequest(SolrRequest.METHOD.GET, "/admin/metrics"));
     finishedSpans = getAndClearSpans();
     assertEquals("get:/admin/metrics", finishedSpans.get(0).operationName());
 
diff --git a/solr/modules/hdfs/src/java/org/apache/solr/hdfs/snapshots/SolrSnapshotsTool.java b/solr/modules/hdfs/src/java/org/apache/solr/hdfs/snapshots/SolrSnapshotsTool.java
index fba471e63a8..a73b3145647 100644
--- a/solr/modules/hdfs/src/java/org/apache/solr/hdfs/snapshots/SolrSnapshotsTool.java
+++ b/solr/modules/hdfs/src/java/org/apache/solr/hdfs/snapshots/SolrSnapshotsTool.java
@@ -45,7 +45,6 @@ import org.apache.commons.cli.ParseException;
 import org.apache.commons.cli.PosixParser;
 import org.apache.hadoop.fs.Path;
 import org.apache.solr.client.solrj.SolrServerException;
-import org.apache.solr.client.solrj.impl.CloudLegacySolrClient;
 import org.apache.solr.client.solrj.impl.CloudSolrClient;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
 import org.apache.solr.client.solrj.response.CollectionAdminResponse;
@@ -102,8 +101,7 @@ public class SolrSnapshotsTool implements Closeable, CLIO {
 
   public SolrSnapshotsTool(String solrZkEnsemble) {
     solrClient =
-        new CloudLegacySolrClient.Builder(
-                Collections.singletonList(solrZkEnsemble), Optional.empty())
+        new CloudSolrClient.Builder(Collections.singletonList(solrZkEnsemble), Optional.empty())
             .build();
   }
 
diff --git a/solr/packaging/test/test_create.bats b/solr/packaging/test/test_create.bats
new file mode 100644
index 00000000000..96a57418e91
--- /dev/null
+++ b/solr/packaging/test/test_create.bats
@@ -0,0 +1,42 @@
+#!/usr/bin/env bats
+
+# 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.
+
+load bats_helper
+
+setup() {
+  common_clean_setup
+}
+
+teardown() {
+  # save a snapshot of SOLR_HOME for failed tests
+  save_home_on_failure
+
+  delete_all_collections
+  solr stop -all >/dev/null 2>&1
+}
+
+@test "create for non cloud mode" {
+  run solr start
+  run solr create -c COLL_NAME
+  assert_output --partial "Created new core 'COLL_NAME'"
+}
+
+@test "create for cloud mode" {
+  run solr start -c
+  run solr create -c COLL_NAME
+  assert_output --partial "Created collection 'COLL_NAME'"
+}
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/JsonMapResponseParser.java b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/JsonMapResponseParser.java
new file mode 100644
index 00000000000..6d388c728e3
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/JsonMapResponseParser.java
@@ -0,0 +1,69 @@
+/*
+ * 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.solrj.impl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.Map;
+import org.apache.solr.client.solrj.ResponseParser;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.util.NamedList;
+import org.noggit.JSONParser;
+import org.noggit.ObjectBuilder;
+
+/** ResponseParser for JsonMaps. */
+public class JsonMapResponseParser extends ResponseParser {
+  @Override
+  public String getWriterType() {
+    return "json";
+  }
+
+  @Override
+  @SuppressWarnings({"unchecked"})
+  public NamedList<Object> processResponse(InputStream body, String encoding) {
+    @SuppressWarnings({"rawtypes"})
+    Map map = null;
+    try (InputStreamReader reader =
+        new InputStreamReader(body, encoding == null ? "UTF-8" : encoding)) {
+      ObjectBuilder builder = new ObjectBuilder(new JSONParser(reader));
+      map = (Map) builder.getObject();
+    } catch (IOException | JSONParser.ParseException e) {
+      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "JSON parsing error", e);
+    }
+    NamedList<Object> list = new NamedList<>();
+    list.addAll(map);
+    return list;
+  }
+
+  @Override
+  public NamedList<Object> processResponse(Reader reader) {
+    throw new RuntimeException("Cannot handle character stream");
+  }
+
+  @Override
+  public String getContentType() {
+    return "application/json";
+  }
+
+  @Override
+  public String getVersion() {
+    return "2.2";
+  }
+}
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/request/DelegationTokenRequest.java b/solr/solrj/src/java/org/apache/solr/client/solrj/request/DelegationTokenRequest.java
index 46773c2671f..04271ba1832 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/request/DelegationTokenRequest.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/request/DelegationTokenRequest.java
@@ -21,6 +21,7 @@ import java.util.Arrays;
 import java.util.TreeSet;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrRequest;
+import org.apache.solr.client.solrj.impl.JsonMapResponseParser;
 import org.apache.solr.client.solrj.impl.NoOpResponseParser;
 import org.apache.solr.client.solrj.response.DelegationTokenResponse;
 import org.apache.solr.common.params.ModifiableSolrParams;
@@ -60,7 +61,7 @@ public abstract class DelegationTokenRequest<
     public Get(String renewer) {
       super(METHOD.GET);
       this.renewer = renewer;
-      setResponseParser(new DelegationTokenResponse.JsonMapResponseParser());
+      setResponseParser(new JsonMapResponseParser());
       setQueryParams(new TreeSet<>(Arrays.asList(OP_KEY)));
     }
 
@@ -99,7 +100,7 @@ public abstract class DelegationTokenRequest<
     public Renew(String token) {
       super(METHOD.PUT);
       this.token = token;
-      setResponseParser(new DelegationTokenResponse.JsonMapResponseParser());
+      setResponseParser(new JsonMapResponseParser());
       setQueryParams(new TreeSet<>(Arrays.asList(OP_KEY, TOKEN_KEY)));
     }
 
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/request/GenericSolrRequest.java b/solr/solrj/src/java/org/apache/solr/client/solrj/request/GenericSolrRequest.java
index 27eb410496b..7f08f454a8f 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/request/GenericSolrRequest.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/request/GenericSolrRequest.java
@@ -20,6 +20,7 @@ import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrRequest;
 import org.apache.solr.client.solrj.request.RequestWriter.ContentWriter;
 import org.apache.solr.client.solrj.response.SimpleSolrResponse;
+import org.apache.solr.common.params.ModifiableSolrParams;
 import org.apache.solr.common.params.SolrParams;
 
 public class GenericSolrRequest extends SolrRequest<SimpleSolrResponse> {
@@ -27,6 +28,10 @@ public class GenericSolrRequest extends SolrRequest<SimpleSolrResponse> {
   public SimpleSolrResponse response = new SimpleSolrResponse();
   public ContentWriter contentWriter;
 
+  public GenericSolrRequest(METHOD m, String path) {
+    this(m, path, new ModifiableSolrParams());
+  }
+
   public GenericSolrRequest(METHOD m, String path, SolrParams params) {
     super(m, path);
     this.params = params;
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/response/DelegationTokenResponse.java b/solr/solrj/src/java/org/apache/solr/client/solrj/response/DelegationTokenResponse.java
index 225fbe6b366..3b3bbe96993 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/response/DelegationTokenResponse.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/response/DelegationTokenResponse.java
@@ -17,16 +17,8 @@
 
 package org.apache.solr.client.solrj.response;
 
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.Reader;
 import java.util.Map;
-import org.apache.solr.client.solrj.ResponseParser;
 import org.apache.solr.common.SolrException;
-import org.apache.solr.common.util.NamedList;
-import org.noggit.JSONParser;
-import org.noggit.ObjectBuilder;
 
 /** Delegation Token responses */
 public abstract class DelegationTokenResponse extends SolrResponseBase {
@@ -58,45 +50,4 @@ public abstract class DelegationTokenResponse extends SolrResponseBase {
   }
 
   public static class Cancel extends DelegationTokenResponse {}
-
-  /** ResponseParser for JsonMaps. Used for Get and Renew DelegationToken responses. */
-  public static class JsonMapResponseParser extends ResponseParser {
-    @Override
-    public String getWriterType() {
-      return "json";
-    }
-
-    @Override
-    @SuppressWarnings({"unchecked"})
-    public NamedList<Object> processResponse(InputStream body, String encoding) {
-      @SuppressWarnings({"rawtypes"})
-      Map map = null;
-      try {
-        ObjectBuilder builder =
-            new ObjectBuilder(
-                new JSONParser(new InputStreamReader(body, encoding == null ? "UTF-8" : encoding)));
-        map = (Map) builder.getObject();
-      } catch (IOException | JSONParser.ParseException e) {
-        throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "parsing error", e);
-      }
-      NamedList<Object> list = new NamedList<>();
-      list.addAll(map);
-      return list;
-    }
-
-    @Override
-    public NamedList<Object> processResponse(Reader reader) {
-      throw new RuntimeException("Cannot handle character stream");
-    }
-
-    @Override
-    public String getContentType() {
-      return "application/json";
-    }
-
-    @Override
-    public String getVersion() {
-      return "1";
-    }
-  }
 }
diff --git a/solr/test-framework/src/java/org/apache/solr/SolrTestCaseHS.java b/solr/test-framework/src/java/org/apache/solr/SolrTestCaseHS.java
index e33413efabb..42dc8b3aeb7 100644
--- a/solr/test-framework/src/java/org/apache/solr/SolrTestCaseHS.java
+++ b/solr/test-framework/src/java/org/apache/solr/SolrTestCaseHS.java
@@ -38,10 +38,10 @@ import javax.xml.xpath.XPathExpressionException;
 import org.apache.lucene.util.IOUtils;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrServerException;
+import org.apache.solr.client.solrj.impl.JsonMapResponseParser;
 import org.apache.solr.client.solrj.impl.NoOpResponseParser;
 import org.apache.solr.client.solrj.request.QueryRequest;
 import org.apache.solr.client.solrj.request.UpdateRequest;
-import org.apache.solr.client.solrj.response.DelegationTokenResponse;
 import org.apache.solr.client.solrj.response.UpdateResponse;
 import org.apache.solr.common.SolrInputDocument;
 import org.apache.solr.common.params.ModifiableSolrParams;
@@ -233,7 +233,7 @@ public class SolrTestCaseHS extends SolrTestCaseJ4 {
     }
 
     if ("json".equals(wt)) {
-      query.setResponseParser(new DelegationTokenResponse.JsonMapResponseParser());
+      query.setResponseParser(new JsonMapResponseParser());
       NamedList<Object> rsp = client.request(query);
       return Utils.toJSONString(rsp);
     } else {