You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@solr.apache.org by ds...@apache.org on 2026/06/17 04:44:25 UTC

(solr) branch branch_10x updated (c4ea60dba28 -> e2edd26b12c)

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

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


    from c4ea60dba28 Address logging warnings (#4420)
     new 84ebc0a2777 New SolrJ CollectionScopedSolrClient (#4418)
     new 5f1cf5ff20c SOLR-18188: Remove Apache HttpClient usage from test infra and tests (#4451)
     new e2edd26b12c SOLR-17208: Parallelize backup and restore file transfers (#4023)

The 3 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:
 .../unreleased/CollectionScopedSolrClient.yml      |   7 +
 .../unreleased/SOLR-1092-parallelizebackups.yml    |  10 +
 .../SOLR-18188-testsStopUsingApacheHttpClient.yml  |   9 +
 solr/api/gradle.lockfile                           |   3 -
 solr/benchmark/gradle.lockfile                     |   3 -
 solr/core/build.gradle                             |   1 -
 solr/core/gradle.lockfile                          |   3 -
 .../solr/handler/IncrementalShardBackup.java       | 133 ++-
 .../java/org/apache/solr/handler/RestoreCore.java  | 115 ++-
 .../org/apache/solr/cli/TestSolrCLIRunExample.java |   2 +-
 .../apache/solr/cloud/BasicDistributedZk2Test.java |  21 +-
 .../apache/solr/cloud/BasicDistributedZkTest.java  |  80 +-
 .../solr/cloud/ChaosMonkeyNothingIsSafeTest.java   |  11 +-
 ...aosMonkeyNothingIsSafeWithPullReplicasTest.java |  11 +-
 .../org/apache/solr/cloud/ForceLeaderTest.java     |  23 +-
 .../solr/cloud/FullSolrCloudDistribCmdsTest.java   |   7 +-
 .../org/apache/solr/cloud/HttpPartitionTest.java   |   6 +-
 .../solr/cloud/LeaderElectionContextKeyTest.java   |  54 +-
 .../solr/cloud/LeaderVoteWaitTimeoutTest.java      |  53 +-
 .../apache/solr/cloud/MultiThreadedOCPTest.java    |  11 +-
 .../solr/cloud/RecoveryStrategyStressTest.java     |  20 +-
 .../test/org/apache/solr/cloud/RecoveryZkTest.java |  19 +-
 .../apache/solr/cloud/RecoveryZkTestWithAuth.java  |  27 +-
 .../test/org/apache/solr/cloud/RouteFieldTest.java |  13 +-
 .../org/apache/solr/cloud/SSLMigrationTest.java    |  23 +-
 .../apache/solr/cloud/ShardRoutingCustomTest.java  |   4 +-
 .../test/org/apache/solr/cloud/SyncSliceTest.java  |   4 +-
 .../solr/cloud/TestAuthenticationFramework.java    |  10 +-
 .../apache/solr/cloud/TestCloudConsistency.java    |  15 +-
 .../cloud/TestLeaderElectionWithEmptyReplica.java  |  11 +-
 .../solr/cloud/TestMiniSolrCloudClusterSSL.java    |  24 +-
 .../apache/solr/cloud/TestRandomFlRTGCloud.java    |  27 +-
 .../solr/cloud/TestRandomRequestDistribution.java  |  72 +-
 .../solr/cloud/TestStressInPlaceUpdates.java       |   5 +-
 .../apache/solr/cloud/UnloadDistributedZkTest.java | 130 ++-
 .../CollectionsAPIAsyncDistributedZkTest.java      |  81 +-
 .../solr/cloud/api/collections/ShardSplitTest.java |  39 +-
 .../TestRequestStatusCollectionAPI.java            |  12 +-
 .../solr/handler/TestReplicationHandlerBackup.java |  11 +-
 .../org/apache/solr/handler/TestRestoreCore.java   |  13 +-
 .../solr/handler/admin/CoreAdminHandlerTest.java   | 110 +--
 .../solr/handler/admin/ZookeeperReadAPITest.java   | 172 ++--
 .../admin/ZookeeperStatusHandlerFailureTest.java   |  23 +-
 .../handler/admin/ZookeeperStatusHandlerTest.java  |  32 +-
 .../DistributedCombinedQueryComponentTest.java     |   9 +-
 .../DistributedQueryElevationComponentTest.java    |  36 +-
 .../solr/handler/component/SearchHandlerTest.java  |  42 +-
 .../apache/solr/schema/TestCloudManagedSchema.java |  32 +-
 ...stributedFacetSimpleRefinementLongTailTest.java |   2 +-
 .../solr/security/BasicAuthIntegrationTest.java    |   7 +-
 .../solr/servlet/TestRequestRateLimiter.java       |   2 +
 .../solr/update/DeleteByIdWithRouterFieldTest.java |  14 +-
 .../apache/solr/update/SolrCmdDistributorTest.java |  44 +-
 .../solr/update/TestInPlaceUpdatesDistrib.java     |  81 +-
 .../TestSimplePropagatorDistributedTracing.java    |  11 +-
 solr/cross-dc-manager/gradle.lockfile              |   3 -
 solr/licenses/httpmime-4.5.14.jar.sha1             |   1 -
 solr/licenses/httpmime-LICENSE-ASL.txt             | 182 -----
 solr/licenses/httpmime-NOTICE.txt                  |   8 -
 solr/modules/analysis-extras/gradle.lockfile       |   3 -
 solr/modules/clustering/gradle.lockfile            |   3 -
 solr/modules/cross-dc/gradle.lockfile              |   3 -
 solr/modules/cuvs/gradle.lockfile                  |   3 -
 solr/modules/extraction/gradle.lockfile            |   3 -
 solr/modules/gcs-repository/gradle.lockfile        |   1 -
 .../apache/solr/gcs/GCSIncrementalBackupTest.java  |   3 +
 solr/modules/jwt-auth/gradle.lockfile              |   3 -
 solr/modules/langid/gradle.lockfile                |   3 -
 solr/modules/language-models/gradle.lockfile       |   3 -
 solr/modules/ltr/gradle.lockfile                   |   3 -
 solr/modules/opentelemetry/gradle.lockfile         |   3 -
 solr/modules/s3-repository/gradle.lockfile         |   1 -
 .../apache/solr/s3/S3IncrementalBackupTest.java    |   3 +
 solr/modules/scripting/gradle.lockfile             |   3 -
 solr/modules/sql/gradle.lockfile                   |   3 -
 solr/solr-ref-guide/gradle.lockfile                |   3 -
 .../deployment-guide/pages/backup-restore.adoc     |  32 +
 solr/solrj-jetty/gradle.lockfile                   |   3 -
 .../HttpJettySolrClientCompatibilityTest.java      |   2 +
 .../PreemptiveBasicAuthClientCustomizerTest.java   |  14 +-
 solr/solrj-streaming/build.gradle                  |   1 +
 solr/solrj-streaming/gradle.lockfile               |   3 -
 .../client/solrj/io/graph/GraphExpressionTest.java |  25 +-
 solr/solrj-zookeeper/gradle.lockfile               |   3 -
 solr/solrj/gradle.lockfile                         |   3 -
 .../solrj/impl/CollectionScopedSolrClient.java     |  61 ++
 .../org/apache/solr/common/util/ObjectCache.java   |   7 +-
 .../solr/client/solrj/SolrExampleBinaryTest.java   |   6 +-
 .../solr/client/solrj/SolrExampleCborTest.java     |   6 +-
 .../apache/solr/client/solrj/SolrExampleTests.java |   6 +-
 .../solr/client/solrj/SolrExampleXMLTest.java      |   6 +-
 .../apache/solr/client/solrj/TestBatchUpdate.java  |  15 +-
 .../solr/client/solrj/TestSolrJErrorHandling.java  |  11 +-
 .../embedded/SolrExampleStreamingBinaryTest.java   |  12 +-
 .../solrj/embedded/SolrExampleStreamingTest.java   |  35 +-
 .../impl/CloudHttp2SolrClientBadInputTest.java     |   2 +-
 .../solrj/impl/CloudHttp2SolrClientTest.java       |   3 +-
 .../solrj/impl/CloudSolrClientCacheTest.java       |  17 +-
 .../solrj/impl/LBHttpSolrClientBadInputTest.java   |   7 +-
 .../impl/SendUpdatesToLeadersOverrideTest.java     |  29 -
 .../solr/client/solrj/request/TestV2Request.java   |  29 +-
 .../response/InputStreamResponseParserTest.java    |   5 +-
 .../solrj/response/TestSuggesterResponse.java      |   7 +-
 solr/test-framework/build.gradle                   |   7 -
 solr/test-framework/gradle.lockfile                |   5 +-
 .../apache/solr/BaseDistributedSearchTestCase.java |   7 +-
 .../src/java/org/apache/solr/SolrTestCaseJ4.java   |  73 +-
 .../client/solrj/apache/CloudLegacySolrClient.java | 417 ----------
 .../solrj/apache/ConcurrentUpdateSolrClient.java   | 887 ---------------------
 .../solr/client/solrj/apache/HttpClientUtil.java   | 621 ---------------
 .../solrj/apache/HttpClusterStateProvider.java     |  54 --
 .../solr/client/solrj/apache/HttpSolrClient.java   | 873 --------------------
 .../solr/client/solrj/apache/LBHttpSolrClient.java | 341 --------
 .../solr/client/solrj/apache/PreemptiveAuth.java   |  61 --
 .../client/solrj/apache/SolrClientBuilder.java     | 173 ----
 .../client/solrj/apache/SolrHttpClientBuilder.java |  86 --
 .../solrj/apache/SolrHttpClientContextBuilder.java | 101 ---
 .../solrj/apache/SolrHttpRequestRetryHandler.java  | 185 -----
 .../apache/SolrPortAwareCookieSpecFactory.java     | 113 ---
 .../solr/client/solrj/apache/package-info.java     |  20 -
 .../solr/cloud/AbstractFullDistribZkTestBase.java  |  78 +-
 .../cloud/FullThrottleStoppableIndexingThread.java |  40 +-
 .../apache/solr/cloud/MiniSolrCloudCluster.java    |  21 +-
 .../AbstractCloudBackupRestoreTestCase.java        |  27 +-
 .../org/apache/solr/embedded/JettySolrRunner.java  |  15 +-
 .../java/org/apache/solr/util/SSLTestConfig.java   | 188 ++---
 .../org/apache/solr/util/SolrJettyTestRule.java    |  16 +-
 127 files changed, 1147 insertions(+), 5593 deletions(-)
 create mode 100644 changelog/unreleased/CollectionScopedSolrClient.yml
 create mode 100644 changelog/unreleased/SOLR-1092-parallelizebackups.yml
 create mode 100644 changelog/unreleased/SOLR-18188-testsStopUsingApacheHttpClient.yml
 delete mode 100644 solr/licenses/httpmime-4.5.14.jar.sha1
 delete mode 100644 solr/licenses/httpmime-LICENSE-ASL.txt
 delete mode 100644 solr/licenses/httpmime-NOTICE.txt
 rename solr/{solrj/src/test/org/apache/solr/client/solrj/impl => solrj-jetty/src/test/org/apache/solr/client/solrj/jetty}/PreemptiveBasicAuthClientCustomizerTest.java (88%)
 create mode 100644 solr/solrj/src/java/org/apache/solr/client/solrj/impl/CollectionScopedSolrClient.java
 delete mode 100644 solr/test-framework/src/java/org/apache/solr/client/solrj/apache/CloudLegacySolrClient.java
 delete mode 100644 solr/test-framework/src/java/org/apache/solr/client/solrj/apache/ConcurrentUpdateSolrClient.java
 delete mode 100644 solr/test-framework/src/java/org/apache/solr/client/solrj/apache/HttpClientUtil.java
 delete mode 100644 solr/test-framework/src/java/org/apache/solr/client/solrj/apache/HttpClusterStateProvider.java
 delete mode 100644 solr/test-framework/src/java/org/apache/solr/client/solrj/apache/HttpSolrClient.java
 delete mode 100644 solr/test-framework/src/java/org/apache/solr/client/solrj/apache/LBHttpSolrClient.java
 delete mode 100644 solr/test-framework/src/java/org/apache/solr/client/solrj/apache/PreemptiveAuth.java
 delete mode 100644 solr/test-framework/src/java/org/apache/solr/client/solrj/apache/SolrClientBuilder.java
 delete mode 100644 solr/test-framework/src/java/org/apache/solr/client/solrj/apache/SolrHttpClientBuilder.java
 delete mode 100644 solr/test-framework/src/java/org/apache/solr/client/solrj/apache/SolrHttpClientContextBuilder.java
 delete mode 100644 solr/test-framework/src/java/org/apache/solr/client/solrj/apache/SolrHttpRequestRetryHandler.java
 delete mode 100644 solr/test-framework/src/java/org/apache/solr/client/solrj/apache/SolrPortAwareCookieSpecFactory.java
 delete mode 100644 solr/test-framework/src/java/org/apache/solr/client/solrj/apache/package-info.java


(solr) 02/03: SOLR-18188: Remove Apache HttpClient usage from test infra and tests (#4451)

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

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

commit 5f1cf5ff20c32eb1b878cb394f800f31e8d06d0f
Author: David Smiley <ds...@apache.org>
AuthorDate: Wed Jun 10 00:34:50 2026 -0400

    SOLR-18188: Remove Apache HttpClient usage from test infra and tests (#4451)
    
    Tests updated to NOT use org.apache.solr.client.solrj.apache (legacy SolrClients based on Apache HttpClient) which is now also removed.
    solr-test-framework doesn't use/depend on Apache HttpClient/httpcomponents anymore.
    
    (cherry picked from commit 31405c513d39800c6147e4f31f7d79d57a88957f)
---
 .../SOLR-18188-testsStopUsingApacheHttpClient.yml  |   9 +
 solr/api/gradle.lockfile                           |   3 -
 solr/benchmark/gradle.lockfile                     |   3 -
 solr/core/build.gradle                             |   1 -
 solr/core/gradle.lockfile                          |   3 -
 .../org/apache/solr/cli/TestSolrCLIRunExample.java |   2 +-
 .../apache/solr/cloud/BasicDistributedZk2Test.java |  21 +-
 .../apache/solr/cloud/BasicDistributedZkTest.java  |  80 +-
 .../solr/cloud/ChaosMonkeyNothingIsSafeTest.java   |  11 +-
 ...aosMonkeyNothingIsSafeWithPullReplicasTest.java |  11 +-
 .../org/apache/solr/cloud/ForceLeaderTest.java     |  23 +-
 .../solr/cloud/FullSolrCloudDistribCmdsTest.java   |   7 +-
 .../org/apache/solr/cloud/HttpPartitionTest.java   |   6 +-
 .../solr/cloud/LeaderElectionContextKeyTest.java   |  54 +-
 .../solr/cloud/LeaderVoteWaitTimeoutTest.java      |  53 +-
 .../apache/solr/cloud/MultiThreadedOCPTest.java    |  11 +-
 .../solr/cloud/RecoveryStrategyStressTest.java     |  20 +-
 .../test/org/apache/solr/cloud/RecoveryZkTest.java |  19 +-
 .../apache/solr/cloud/RecoveryZkTestWithAuth.java  |  27 +-
 .../test/org/apache/solr/cloud/RouteFieldTest.java |  13 +-
 .../org/apache/solr/cloud/SSLMigrationTest.java    |  23 +-
 .../apache/solr/cloud/ShardRoutingCustomTest.java  |   4 +-
 .../test/org/apache/solr/cloud/SyncSliceTest.java  |   4 +-
 .../solr/cloud/TestAuthenticationFramework.java    |  10 +-
 .../apache/solr/cloud/TestCloudConsistency.java    |  15 +-
 .../cloud/TestLeaderElectionWithEmptyReplica.java  |  11 +-
 .../solr/cloud/TestMiniSolrCloudClusterSSL.java    |  24 +-
 .../apache/solr/cloud/TestRandomFlRTGCloud.java    |  27 +-
 .../solr/cloud/TestRandomRequestDistribution.java  |  72 +-
 .../solr/cloud/TestStressInPlaceUpdates.java       |   5 +-
 .../apache/solr/cloud/UnloadDistributedZkTest.java | 130 ++-
 .../CollectionsAPIAsyncDistributedZkTest.java      |  81 +-
 .../solr/cloud/api/collections/ShardSplitTest.java |  39 +-
 .../TestRequestStatusCollectionAPI.java            |  12 +-
 .../solr/handler/TestReplicationHandlerBackup.java |  11 +-
 .../org/apache/solr/handler/TestRestoreCore.java   |  13 +-
 .../solr/handler/admin/CoreAdminHandlerTest.java   | 110 +--
 .../solr/handler/admin/ZookeeperReadAPITest.java   | 172 ++--
 .../admin/ZookeeperStatusHandlerFailureTest.java   |  23 +-
 .../handler/admin/ZookeeperStatusHandlerTest.java  |  32 +-
 .../DistributedCombinedQueryComponentTest.java     |   9 +-
 .../DistributedQueryElevationComponentTest.java    |  36 +-
 .../solr/handler/component/SearchHandlerTest.java  |  42 +-
 .../apache/solr/schema/TestCloudManagedSchema.java |  32 +-
 ...stributedFacetSimpleRefinementLongTailTest.java |   2 +-
 .../solr/security/BasicAuthIntegrationTest.java    |   7 +-
 .../solr/servlet/TestRequestRateLimiter.java       |   2 +
 .../solr/update/DeleteByIdWithRouterFieldTest.java |  14 +-
 .../apache/solr/update/SolrCmdDistributorTest.java |  44 +-
 .../solr/update/TestInPlaceUpdatesDistrib.java     |  81 +-
 .../TestSimplePropagatorDistributedTracing.java    |  11 +-
 solr/cross-dc-manager/gradle.lockfile              |   3 -
 solr/licenses/httpmime-4.5.14.jar.sha1             |   1 -
 solr/licenses/httpmime-LICENSE-ASL.txt             | 182 -----
 solr/licenses/httpmime-NOTICE.txt                  |   8 -
 solr/modules/analysis-extras/gradle.lockfile       |   3 -
 solr/modules/clustering/gradle.lockfile            |   3 -
 solr/modules/cross-dc/gradle.lockfile              |   3 -
 solr/modules/cuvs/gradle.lockfile                  |   3 -
 solr/modules/extraction/gradle.lockfile            |   3 -
 solr/modules/gcs-repository/gradle.lockfile        |   1 -
 solr/modules/jwt-auth/gradle.lockfile              |   3 -
 solr/modules/langid/gradle.lockfile                |   3 -
 solr/modules/language-models/gradle.lockfile       |   3 -
 solr/modules/ltr/gradle.lockfile                   |   3 -
 solr/modules/opentelemetry/gradle.lockfile         |   3 -
 solr/modules/s3-repository/gradle.lockfile         |   1 -
 solr/modules/scripting/gradle.lockfile             |   3 -
 solr/modules/sql/gradle.lockfile                   |   3 -
 solr/solr-ref-guide/gradle.lockfile                |   3 -
 solr/solrj-jetty/gradle.lockfile                   |   3 -
 .../HttpJettySolrClientCompatibilityTest.java      |   2 +
 .../PreemptiveBasicAuthClientCustomizerTest.java   |  14 +-
 solr/solrj-streaming/build.gradle                  |   1 +
 solr/solrj-streaming/gradle.lockfile               |   3 -
 .../client/solrj/io/graph/GraphExpressionTest.java |  25 +-
 solr/solrj-zookeeper/gradle.lockfile               |   3 -
 solr/solrj/gradle.lockfile                         |   3 -
 .../solr/client/solrj/SolrExampleBinaryTest.java   |   6 +-
 .../solr/client/solrj/SolrExampleCborTest.java     |   6 +-
 .../apache/solr/client/solrj/SolrExampleTests.java |   6 +-
 .../solr/client/solrj/SolrExampleXMLTest.java      |   6 +-
 .../apache/solr/client/solrj/TestBatchUpdate.java  |  15 +-
 .../solr/client/solrj/TestSolrJErrorHandling.java  |  11 +-
 .../embedded/SolrExampleStreamingBinaryTest.java   |  12 +-
 .../solrj/embedded/SolrExampleStreamingTest.java   |  35 +-
 .../impl/CloudHttp2SolrClientBadInputTest.java     |   2 +-
 .../solrj/impl/CloudHttp2SolrClientTest.java       |   3 +-
 .../solrj/impl/CloudSolrClientCacheTest.java       |  17 +-
 .../solrj/impl/LBHttpSolrClientBadInputTest.java   |   7 +-
 .../impl/SendUpdatesToLeadersOverrideTest.java     |  29 -
 .../solr/client/solrj/request/TestV2Request.java   |  29 +-
 .../response/InputStreamResponseParserTest.java    |   5 +-
 .../solrj/response/TestSuggesterResponse.java      |   7 +-
 solr/test-framework/build.gradle                   |   7 -
 solr/test-framework/gradle.lockfile                |   5 +-
 .../apache/solr/BaseDistributedSearchTestCase.java |   7 +-
 .../src/java/org/apache/solr/SolrTestCaseJ4.java   |  73 +-
 .../client/solrj/apache/CloudLegacySolrClient.java | 417 ----------
 .../solrj/apache/ConcurrentUpdateSolrClient.java   | 887 ---------------------
 .../solr/client/solrj/apache/HttpClientUtil.java   | 621 ---------------
 .../solrj/apache/HttpClusterStateProvider.java     |  54 --
 .../solr/client/solrj/apache/HttpSolrClient.java   | 873 --------------------
 .../solr/client/solrj/apache/LBHttpSolrClient.java | 341 --------
 .../solr/client/solrj/apache/PreemptiveAuth.java   |  61 --
 .../client/solrj/apache/SolrClientBuilder.java     | 173 ----
 .../client/solrj/apache/SolrHttpClientBuilder.java |  86 --
 .../solrj/apache/SolrHttpClientContextBuilder.java | 101 ---
 .../solrj/apache/SolrHttpRequestRetryHandler.java  | 185 -----
 .../apache/SolrPortAwareCookieSpecFactory.java     | 113 ---
 .../solr/client/solrj/apache/package-info.java     |  20 -
 .../solr/cloud/AbstractFullDistribZkTestBase.java  |  78 +-
 .../cloud/FullThrottleStoppableIndexingThread.java |  40 +-
 .../apache/solr/cloud/MiniSolrCloudCluster.java    |  21 +-
 .../AbstractCloudBackupRestoreTestCase.java        |  27 +-
 .../org/apache/solr/embedded/JettySolrRunner.java  |  15 +-
 .../java/org/apache/solr/util/SSLTestConfig.java   | 188 ++---
 .../org/apache/solr/util/SolrJettyTestRule.java    |  16 +-
 118 files changed, 834 insertions(+), 5535 deletions(-)

diff --git a/changelog/unreleased/SOLR-18188-testsStopUsingApacheHttpClient.yml b/changelog/unreleased/SOLR-18188-testsStopUsingApacheHttpClient.yml
new file mode 100644
index 00000000000..8f6c26a349c
--- /dev/null
+++ b/changelog/unreleased/SOLR-18188-testsStopUsingApacheHttpClient.yml
@@ -0,0 +1,9 @@
+title: >
+  Tests updated to NOT use org.apache.solr.client.solrj.apache which is now also removed.
+  solr-test-framework doesn't use/depend on Apache HttpClient/httpcomponents anymore.
+type: other
+authors:
+  - name: David Smiley
+links:
+  - name: SOLR-18188
+    url: https://issues.apache.org/jira/browse/SOLR-18188
diff --git a/solr/api/gradle.lockfile b/solr/api/gradle.lockfile
index aefe95fe349..9872f1a52f4 100644
--- a/solr/api/gradle.lockfile
+++ b/solr/api/gradle.lockfile
@@ -93,9 +93,6 @@ org.apache.commons:commons-math3:3.6.1=jarValidation,testRuntimeClasspath
 org.apache.curator:curator-client:5.9.0=jarValidation,testCompileClasspath,testRuntimeClasspath
 org.apache.curator:curator-framework:5.9.0=jarValidation,testCompileClasspath,testRuntimeClasspath
 org.apache.curator:curator-test:5.9.0=jarValidation,testRuntimeClasspath
-org.apache.httpcomponents:httpclient:4.5.14=jarValidation,testRuntimeClasspath
-org.apache.httpcomponents:httpcore:4.4.16=jarValidation,testRuntimeClasspath
-org.apache.httpcomponents:httpmime:4.5.14=jarValidation,testRuntimeClasspath
 org.apache.logging.log4j:log4j-api:2.25.3=jarValidation,testRuntimeClasspath
 org.apache.logging.log4j:log4j-core:2.25.3=jarValidation,testRuntimeClasspath
 org.apache.logging.log4j:log4j-slf4j2-impl:2.25.3=jarValidation,testRuntimeClasspath
diff --git a/solr/benchmark/gradle.lockfile b/solr/benchmark/gradle.lockfile
index 33c7967aa9a..bad5de3eaca 100644
--- a/solr/benchmark/gradle.lockfile
+++ b/solr/benchmark/gradle.lockfile
@@ -83,9 +83,6 @@ org.apache.commons:commons-math3:3.6.1=annotationProcessor,compileClasspath,jarV
 org.apache.curator:curator-client:5.9.0=compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
 org.apache.curator:curator-framework:5.9.0=compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
 org.apache.curator:curator-test:5.9.0=jarValidation,runtimeClasspath,testRuntimeClasspath
-org.apache.httpcomponents:httpclient:4.5.14=jarValidation,runtimeClasspath,testRuntimeClasspath
-org.apache.httpcomponents:httpcore:4.4.16=jarValidation,runtimeClasspath,testRuntimeClasspath
-org.apache.httpcomponents:httpmime:4.5.14=jarValidation,runtimeClasspath,testRuntimeClasspath
 org.apache.logging.log4j:log4j-api:2.25.3=jarValidation,runtimeClasspath,testRuntimeClasspath
 org.apache.logging.log4j:log4j-core:2.25.3=jarValidation,runtimeClasspath,testRuntimeClasspath
 org.apache.logging.log4j:log4j-slf4j2-impl:2.25.3=jarValidation,runtimeClasspath,testRuntimeClasspath
diff --git a/solr/core/build.gradle b/solr/core/build.gradle
index b7fd3610231..503f5c1c459 100644
--- a/solr/core/build.gradle
+++ b/solr/core/build.gradle
@@ -201,7 +201,6 @@ dependencies {
   testRuntimeOnly(libs.mockito.subclass, {
     exclude group: "net.bytebuddy", module: "byte-buddy-agent"
   })
-  testImplementation libs.apache.httpcomponents.httpclient
 
   testImplementation libs.opentelemetry.sdk.testing
 }
diff --git a/solr/core/gradle.lockfile b/solr/core/gradle.lockfile
index ebb3781d39a..2acb5f859dd 100644
--- a/solr/core/gradle.lockfile
+++ b/solr/core/gradle.lockfile
@@ -90,9 +90,6 @@ org.apache.commons:commons-math3:3.6.1=apiHelper,compileClasspath,jarValidation,
 org.apache.curator:curator-client:5.9.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
 org.apache.curator:curator-framework:5.9.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
 org.apache.curator:curator-test:5.9.0=jarValidation,testCompileClasspath,testRuntimeClasspath
-org.apache.httpcomponents:httpclient:4.5.14=jarValidation,testCompileClasspath,testRuntimeClasspath
-org.apache.httpcomponents:httpcore:4.4.16=jarValidation,testCompileClasspath,testRuntimeClasspath
-org.apache.httpcomponents:httpmime:4.5.14=jarValidation,testRuntimeClasspath
 org.apache.logging.log4j:log4j-api:2.25.3=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
 org.apache.logging.log4j:log4j-core:2.25.3=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
 org.apache.logging.log4j:log4j-slf4j2-impl:2.25.3=jarValidation,runtimeClasspath,runtimeLibs,testRuntimeClasspath
diff --git a/solr/core/src/test/org/apache/solr/cli/TestSolrCLIRunExample.java b/solr/core/src/test/org/apache/solr/cli/TestSolrCLIRunExample.java
index ad9660080ca..7a7f105c084 100644
--- a/solr/core/src/test/org/apache/solr/cli/TestSolrCLIRunExample.java
+++ b/solr/core/src/test/org/apache/solr/cli/TestSolrCLIRunExample.java
@@ -475,7 +475,7 @@ public class TestSolrCLIRunExample extends SolrTestCaseJ4 {
 
     // index some docs - to verify all is good for both shards
     try (CloudSolrClient cloudClient =
-        new RandomizingCloudHttp2SolrClientBuilder(
+        new RandomizingCloudSolrClientBuilder(
                 List.of(executor.solrCloudCluster.getZkServer().getZkAddress()), Optional.empty())
             .withDefaultCollection(collectionName)
             .build()) {
diff --git a/solr/core/src/test/org/apache/solr/cloud/BasicDistributedZk2Test.java b/solr/core/src/test/org/apache/solr/cloud/BasicDistributedZk2Test.java
index 203d035318e..295344d57d9 100644
--- a/solr/core/src/test/org/apache/solr/cloud/BasicDistributedZk2Test.java
+++ b/solr/core/src/test/org/apache/solr/cloud/BasicDistributedZk2Test.java
@@ -33,6 +33,7 @@ import org.apache.solr.client.solrj.request.UpdateRequest;
 import org.apache.solr.client.solrj.response.QueryResponse;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.SolrInputDocument;
+import org.apache.solr.common.cloud.Replica;
 import org.apache.solr.common.cloud.ZkNodeProps;
 import org.apache.solr.common.cloud.ZkStateReader;
 import org.apache.solr.common.params.CommonParams;
@@ -158,11 +159,25 @@ public class BasicDistributedZk2Test extends AbstractFullDistribZkTestBase {
           .getZkController()
           .getZkClient()
           .getCuratorFramework()
-          .blockUntilConnected(50, TimeUnit.MILLISECONDS);
+          .blockUntilConnected(30, TimeUnit.SECONDS);
 
       indexr("id", docId + 1, t1, "slip this doc in");
 
-      waitForRecoveriesToFinish(false);
+      // Wait for the session-expired node to fully re-register its replica as ACTIVE.
+      // waitForRecoveriesToFinish alone is insufficient because it ignores replicas on nodes
+      // not yet back in live_nodes, which can race with teardown.
+      String expiredNodeName = cloudJetty.jetty.getNodeName();
+      ZkStateReader.from(cloudClient)
+          .waitForState(
+              DEFAULT_COLLECTION,
+              60,
+              TimeUnit.SECONDS,
+              (liveNodes, collectionState) ->
+                  liveNodes.contains(expiredNodeName)
+                      && collectionState.getSlices().stream()
+                          .flatMap(s -> s.getReplicas().stream())
+                          .filter(r -> liveNodes.contains(r.getNodeName()))
+                          .allMatch(r -> r.getState() == Replica.State.ACTIVE));
 
       checkShardConsistency(SHARD1);
       checkShardConsistency(SHARD2);
@@ -285,7 +300,7 @@ public class BasicDistributedZk2Test extends AbstractFullDistribZkTestBase {
 
     // ensure shard is dead
     expectThrows(
-        SolrServerException.class,
+        Exception.class,
         "This server should be down and this update should have failed",
         () -> index_specific(deadShard.client.solrClient, id, 999, i1, 107, t1, "specific doc!"));
 
diff --git a/solr/core/src/test/org/apache/solr/cloud/BasicDistributedZkTest.java b/solr/core/src/test/org/apache/solr/cloud/BasicDistributedZkTest.java
index 758588d7465..16031370fc3 100644
--- a/solr/core/src/test/org/apache/solr/cloud/BasicDistributedZkTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/BasicDistributedZkTest.java
@@ -44,7 +44,7 @@ import org.apache.solr.JSONTestUtil;
 import org.apache.solr.SolrTestCaseJ4.SuppressSSL;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrServerException;
-import org.apache.solr.client.solrj.apache.HttpSolrClient;
+import org.apache.solr.client.solrj.impl.HttpSolrClient;
 import org.apache.solr.client.solrj.request.AbstractUpdateRequest;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
 import org.apache.solr.client.solrj.request.CoreAdminRequest.Create;
@@ -695,10 +695,9 @@ public class BasicDistributedZkTest extends AbstractFullDistribZkTestBase {
 
     // try add commitWithin
     long before = cloudClient.query(new SolrQuery("*:*")).getResults().getNumFound();
-    for (SolrClient client : clients) {
+    for (var client : clients) {
       assertEquals(
-          "unexpected pre-commitWithin document count on node: "
-              + ((HttpSolrClient) client).getBaseURL(),
+          "unexpected pre-commitWithin document count on node: " + client.getBaseURL(),
           before,
           client.query(new SolrQuery("*:*")).getResults().getNumFound());
     }
@@ -1040,7 +1039,7 @@ public class BasicDistributedZkTest extends AbstractFullDistribZkTestBase {
 
   private void testStopAndStartCoresInOneInstance() throws Exception {
     JettySolrRunner jetty = jettys.get(0);
-    try (final SolrClient httpSolrClient = (HttpSolrClient) jetty.newClient(15000, 60000)) {
+    try (final var httpSolrClient = jetty.newClient(15000, 60000)) {
       ThreadPoolExecutor executor = null;
       try {
         executor =
@@ -1149,9 +1148,9 @@ public class BasicDistributedZkTest extends AbstractFullDistribZkTestBase {
 
     CollectionAdminResponse res = new CollectionAdminResponse();
     if (client == null) {
-      final String baseUrl = ((HttpSolrClient) clients.get(clientIndex)).getBaseURL();
+      final String baseUrl = clients.get(clientIndex).getBaseURL();
 
-      try (SolrClient aClient = createNewSolrClient("", baseUrl)) {
+      try (SolrClient aClient = createNewSolrClient(baseUrl, null)) {
         res.setResponse(aClient.request(request));
       }
     } else {
@@ -1259,7 +1258,7 @@ public class BasicDistributedZkTest extends AbstractFullDistribZkTestBase {
 
   private void testNumberOfCommitsWithCommitAfterAdd() throws SolrServerException, IOException {
     log.info("### STARTING testNumberOfCommitsWithCommitAfterAdd");
-    long startCommits = getNumCommits((HttpSolrClient) clients.get(0));
+    long startCommits = getNumCommits(clients.getFirst());
 
     NamedList<Object> result =
         clients
@@ -1270,17 +1269,13 @@ public class BasicDistributedZkTest extends AbstractFullDistribZkTestBase {
                     .setCommitWithin(900000)
                     .setAction(AbstractUpdateRequest.ACTION.COMMIT, true, true));
 
-    long endCommits = getNumCommits((HttpSolrClient) clients.get(0));
+    long endCommits = getNumCommits(clients.getFirst());
     assertEquals(startCommits + 1L, endCommits);
   }
 
-  private Long getNumCommits(HttpSolrClient sourceClient) throws SolrServerException, IOException {
-    String collection = sourceClient.getDefaultCollection();
-    try (SolrClient client =
-        new HttpSolrClient.Builder(sourceClient.getBaseURL())
-            .withConnectionTimeout(15000, TimeUnit.MILLISECONDS)
-            .withSocketTimeout(60000, TimeUnit.MILLISECONDS)
-            .build()) {
+  private Long getNumCommits(HttpSolrClient client) throws SolrServerException, IOException {
+    String collection = client.getDefaultCollection();
+    {
       var req = new MetricsRequest(SolrParams.of("wt", "prometheus"));
 
       NamedList<Object> resp = client.request(req);
@@ -1318,7 +1313,7 @@ public class BasicDistributedZkTest extends AbstractFullDistribZkTestBase {
               .setNode(jettys.get(0).getNodeName())
               .process(cloudClient);
       for (String coreName : resp.getCollectionCoresStatus().keySet()) {
-        collectionClients.add(createNewSolrClient(coreName, jettys.get(0).getBaseUrl().toString()));
+        collectionClients.add(createNewSolrClient(jettys.get(0).getBaseUrl().toString(), coreName));
       }
     }
 
@@ -1379,11 +1374,7 @@ public class BasicDistributedZkTest extends AbstractFullDistribZkTestBase {
             .getLeader("shard1");
 
     // now test that unloading a core gets us a new leader
-    try (SolrClient unloadClient =
-        new HttpSolrClient.Builder(jettys.get(0).getBaseUrl().toString())
-            .withConnectionTimeout(15000, TimeUnit.MILLISECONDS)
-            .withSocketTimeout(60000, TimeUnit.MILLISECONDS)
-            .build()) {
+    try (SolrClient unloadClient = jettys.getFirst().newClient(15000, 60000)) {
       Unload unloadCmd = new Unload(true);
       unloadCmd.setCoreName(leader.getCoreName());
 
@@ -1431,12 +1422,11 @@ public class BasicDistributedZkTest extends AbstractFullDistribZkTestBase {
 
   private void testSearchByCollectionName() throws SolrServerException, IOException {
     log.info("### STARTING testSearchByCollectionName");
-    SolrClient client = clients.get(0);
-    final String baseUrl = ((HttpSolrClient) client).getBaseURL();
+    final String baseUrl = clients.getFirst().getBaseURL();
 
     // the cores each have different names, but if we add the collection name to the url
     // we should get mapped to the right core
-    try (SolrClient client1 = createNewSolrClient(oneInstanceCollection, baseUrl)) {
+    try (SolrClient client1 = createNewSolrClient(baseUrl, oneInstanceCollection)) {
       SolrQuery query = new SolrQuery("*:*");
       long oneDocs = client1.query(query).getResults().getNumFound();
       assertEquals(3, oneDocs);
@@ -1445,13 +1435,12 @@ public class BasicDistributedZkTest extends AbstractFullDistribZkTestBase {
 
   private void testUpdateByCollectionName() throws SolrServerException, IOException {
     log.info("### STARTING testUpdateByCollectionName");
-    SolrClient client = clients.get(0);
-    final String baseUrl = ((HttpSolrClient) client).getBaseURL();
+    final String baseUrl = clients.getFirst().getBaseURL();
 
     // the cores each have different names, but if we add the collection name to the url
     // we should get mapped to the right core
     // test hitting an update url
-    try (SolrClient client1 = createNewSolrClient(oneInstanceCollection, baseUrl)) {
+    try (SolrClient client1 = createNewSolrClient(baseUrl, oneInstanceCollection)) {
       client1.commit();
     }
   }
@@ -1465,7 +1454,7 @@ public class BasicDistributedZkTest extends AbstractFullDistribZkTestBase {
     assertEquals(0, response.getStatus());
     List<SolrClient> collectionClients = new ArrayList<>();
     for (String coreName : response.getCollectionCoresStatus().keySet()) {
-      collectionClients.add(createNewSolrClient(coreName, jettys.get(0).getBaseUrl().toString()));
+      collectionClients.add(createNewSolrClient(jettys.get(0).getBaseUrl().toString(), coreName));
     }
 
     SolrClient client1 = collectionClients.get(0);
@@ -1539,7 +1528,7 @@ public class BasicDistributedZkTest extends AbstractFullDistribZkTestBase {
 
     pending.add(completionService.submit(call));
 
-    collectionClients.add(createNewSolrClient(collection + num, baseUrl));
+    collectionClients.add(createNewSolrClient(baseUrl, collection + num));
   }
 
   private void testMultipleCollections() throws Exception {
@@ -1661,7 +1650,7 @@ public class BasicDistributedZkTest extends AbstractFullDistribZkTestBase {
             return null;
           };
 
-      collectionClients.add(createNewSolrClient(collection, runner.getBaseUrl().toString()));
+      collectionClients.add(createNewSolrClient(runner.getBaseUrl().toString(), collection));
       pending.add(completionService.submit(call));
       while (pending != null && pending.size() > 0) {
 
@@ -1672,35 +1661,6 @@ public class BasicDistributedZkTest extends AbstractFullDistribZkTestBase {
     }
   }
 
-  @Override
-  protected SolrClient createNewSolrClient(String collection, String baseUrl) {
-
-    SolrClient client = getHttpSolrClient(baseUrl, collection);
-
-    return client;
-  }
-
-  /**
-   * @param collection the name of a collection or core to set as the "default" on the created
-   *     client.
-   * @param baseUrl the "base" URL of a Solr node. Should <em>not</em> contain a collection or core
-   *     name.
-   * @param connectionTimeoutMillis the HTTP connection timeout in milliseconds
-   * @param socketTimeoutMillis the HTTP socket-read timeout in milliseconds
-   */
-  protected SolrClient createNewSolrClient(
-      String collection, String baseUrl, int connectionTimeoutMillis, int socketTimeoutMillis) {
-
-    SolrClient client =
-        new HttpSolrClient.Builder(baseUrl)
-            .withDefaultCollection(collection)
-            .withConnectionTimeout(connectionTimeoutMillis, TimeUnit.MILLISECONDS)
-            .withSocketTimeout(socketTimeoutMillis, TimeUnit.MILLISECONDS)
-            .build();
-
-    return client;
-  }
-
   @Override
   protected QueryResponse queryRandomShard(ModifiableSolrParams params)
       throws SolrServerException, IOException {
diff --git a/solr/core/src/test/org/apache/solr/cloud/ChaosMonkeyNothingIsSafeTest.java b/solr/core/src/test/org/apache/solr/cloud/ChaosMonkeyNothingIsSafeTest.java
index b8640c9a3d4..03c2bac7a3d 100644
--- a/solr/core/src/test/org/apache/solr/cloud/ChaosMonkeyNothingIsSafeTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/ChaosMonkeyNothingIsSafeTest.java
@@ -23,7 +23,6 @@ import java.util.Set;
 import java.util.concurrent.TimeUnit;
 import org.apache.lucene.tests.util.LuceneTestCase;
 import org.apache.solr.SolrTestCaseJ4.SuppressSSL;
-import org.apache.solr.client.solrj.apache.CloudLegacySolrClient;
 import org.apache.solr.client.solrj.impl.CloudSolrClient;
 import org.apache.solr.client.solrj.request.SolrQuery;
 import org.apache.solr.common.SolrInputDocument;
@@ -133,7 +132,7 @@ public class ChaosMonkeyNothingIsSafeTest extends AbstractFullDistribZkTestBase
 
   protected CloudSolrClient createCloudClient(String defaultCollection, int socketTimeout) {
 
-    return getCloudSolrClient(
+    return createNewCloudSolrClient(
         zkServer.getZkAddress(), defaultCollection, random().nextBoolean(), 30000, socketTimeout);
   }
 
@@ -183,13 +182,7 @@ public class ChaosMonkeyNothingIsSafeTest extends AbstractFullDistribZkTestBase
       if (runFullThrottle) {
         ftIndexThread =
             new FullThrottleStoppableIndexingThread(
-                ((CloudLegacySolrClient) cloudClient).getHttpClient(),
-                controlClient,
-                cloudClient,
-                clients,
-                "ft1",
-                true,
-                this.clientSoTimeout);
+                controlClient, cloudClient, clients, "ft1", true, this.clientSoTimeout);
         ftIndexThread.start();
       }
 
diff --git a/solr/core/src/test/org/apache/solr/cloud/ChaosMonkeyNothingIsSafeWithPullReplicasTest.java b/solr/core/src/test/org/apache/solr/cloud/ChaosMonkeyNothingIsSafeWithPullReplicasTest.java
index b70939ced42..e80b765af89 100644
--- a/solr/core/src/test/org/apache/solr/cloud/ChaosMonkeyNothingIsSafeWithPullReplicasTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/ChaosMonkeyNothingIsSafeWithPullReplicasTest.java
@@ -25,7 +25,6 @@ import java.util.Set;
 import java.util.concurrent.TimeUnit;
 import org.apache.lucene.tests.util.LuceneTestCase;
 import org.apache.solr.SolrTestCaseJ4.SuppressSSL;
-import org.apache.solr.client.solrj.apache.CloudLegacySolrClient;
 import org.apache.solr.client.solrj.impl.CloudSolrClient;
 import org.apache.solr.client.solrj.request.SolrQuery;
 import org.apache.solr.common.SolrInputDocument;
@@ -144,7 +143,7 @@ public class ChaosMonkeyNothingIsSafeWithPullReplicasTest extends AbstractFullDi
   }
 
   protected CloudSolrClient createCloudClient(String defaultCollection, int socketTimeout) {
-    return getCloudSolrClient(
+    return createNewCloudSolrClient(
         zkServer.getZkAddress(), defaultCollection, random().nextBoolean(), 30000, socketTimeout);
   }
 
@@ -217,13 +216,7 @@ public class ChaosMonkeyNothingIsSafeWithPullReplicasTest extends AbstractFullDi
       if (runFullThrottle) {
         ftIndexThread =
             new FullThrottleStoppableIndexingThread(
-                ((CloudLegacySolrClient) cloudClient).getHttpClient(),
-                controlClient,
-                cloudClient,
-                clients,
-                "ft1",
-                true,
-                this.clientSoTimeout);
+                controlClient, cloudClient, clients, "ft1", true, this.clientSoTimeout);
         ftIndexThread.start();
       }
 
diff --git a/solr/core/src/test/org/apache/solr/cloud/ForceLeaderTest.java b/solr/core/src/test/org/apache/solr/cloud/ForceLeaderTest.java
index e5c32a3a257..d9758361901 100644
--- a/solr/core/src/test/org/apache/solr/cloud/ForceLeaderTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/ForceLeaderTest.java
@@ -20,10 +20,8 @@ import com.carrotsearch.randomizedtesting.annotations.Nightly;
 import java.io.IOException;
 import java.lang.invoke.MethodHandles;
 import java.util.List;
-import java.util.Optional;
 import java.util.concurrent.TimeUnit;
 import org.apache.solr.client.solrj.SolrServerException;
-import org.apache.solr.client.solrj.apache.CloudLegacySolrClient;
 import org.apache.solr.client.solrj.impl.CloudSolrClient;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
 import org.apache.solr.common.SolrException;
@@ -193,19 +191,11 @@ public class ForceLeaderTest extends HttpPartitionTest {
     }
   }
 
-  /**
-   * For this test, we need a cloudClient that is not randomized since we need to NEVER send the
-   * updates only to the leader. The way the RandomizingCloudSolrClientBuilder works, you can't
-   * avoid its internal decision-making process to sometimes send updates only to leaders. We
-   * override the definition of this class in AbstractFullDistribZkTestBase to make sure we always
-   * use DEFAULT_COLLECTION defined in ForceLeaderTest.
-   */
   @Override
   protected CloudSolrClient createCloudClient(String defaultCollection) {
-    CloudLegacySolrClient.Builder builder =
-        new CloudLegacySolrClient.Builder(List.of(zkServer.getZkAddress()), Optional.empty());
-    builder.withDefaultCollection(TEST_COLLECTION);
-    return builder.withConnectionTimeout(30000).withSocketTimeout(120000).build();
+    boolean shardLeadersOnly = false; // this test requires we use any replica
+    return createNewCloudSolrClient(
+        zkServer.getZkHost(), TEST_COLLECTION, shardLeadersOnly, 30_000, 120_000);
   }
 
   private void putNonLeadersIntoLowerTerm(
@@ -321,11 +311,8 @@ public class ForceLeaderTest extends HttpPartitionTest {
     CollectionAdminRequest.ForceLeader forceLeader =
         CollectionAdminRequest.forceLeaderElection(collectionName, shard);
 
-    try (CloudSolrClient cloudClient =
-        new CloudLegacySolrClient.Builder(List.of(zkServer.getZkAddress()), Optional.empty())
-            .withConnectionTimeout(3000)
-            .withSocketTimeout(60000)
-            .build()) {
+    try (var cloudClient =
+        createNewCloudSolrClient(zkServer.getZkHost(), null, true, 3_000, 60_000)) {
       cloudClient.request(forceLeader);
     }
   }
diff --git a/solr/core/src/test/org/apache/solr/cloud/FullSolrCloudDistribCmdsTest.java b/solr/core/src/test/org/apache/solr/cloud/FullSolrCloudDistribCmdsTest.java
index d0d10fda029..cb5bee46878 100644
--- a/solr/core/src/test/org/apache/solr/cloud/FullSolrCloudDistribCmdsTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/FullSolrCloudDistribCmdsTest.java
@@ -29,8 +29,8 @@ import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 import org.apache.lucene.tests.util.TestUtil;
 import org.apache.solr.client.solrj.SolrClient;
-import org.apache.solr.client.solrj.apache.ConcurrentUpdateSolrClient;
 import org.apache.solr.client.solrj.impl.CloudSolrClient;
+import org.apache.solr.client.solrj.jetty.ConcurrentUpdateJettySolrClient;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
 import org.apache.solr.client.solrj.request.SolrQuery;
 import org.apache.solr.client.solrj.request.UpdateRequest;
@@ -816,8 +816,9 @@ public class FullSolrCloudDistribCmdsTest extends SolrCloudTestCase {
 
     final int numDocs = atLeast(50);
     final JettySolrRunner nodeToUpdate = cluster.getRandomJetty(random());
-    try (ConcurrentUpdateSolrClient indexClient =
-        new ConcurrentUpdateSolrClient.Builder(nodeToUpdate.getProxyBaseUrl().toString())
+    try (var indexClient =
+        new ConcurrentUpdateJettySolrClient.Builder(
+                nodeToUpdate.getProxyBaseUrl().toString(), nodeToUpdate.getSolrClient(), false)
             .withDefaultCollection(collectionName)
             .withQueueSize(10)
             .withThreadCount(2)
diff --git a/solr/core/src/test/org/apache/solr/cloud/HttpPartitionTest.java b/solr/core/src/test/org/apache/solr/cloud/HttpPartitionTest.java
index c799ab5981e..48396c63d31 100644
--- a/solr/core/src/test/org/apache/solr/cloud/HttpPartitionTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/HttpPartitionTest.java
@@ -36,7 +36,6 @@ import org.apache.solr.JSONTestUtil;
 import org.apache.solr.SolrTestCaseJ4.SuppressSSL;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrServerException;
-import org.apache.solr.client.solrj.apache.HttpSolrClient;
 import org.apache.solr.client.solrj.request.QueryRequest;
 import org.apache.solr.client.solrj.request.UpdateRequest;
 import org.apache.solr.common.SolrException;
@@ -523,10 +522,7 @@ public class HttpPartitionTest extends AbstractFullDistribZkTestBase {
   // Send doc directly to a server (without going through proxy)
   protected int sendDoc(String collectionName, int docId, JettySolrRunner leaderJetty)
       throws IOException, SolrServerException {
-    try (SolrClient solrClient =
-        new HttpSolrClient.Builder(leaderJetty.getBaseUrl().toString()).build()) {
-      return sendDoc(docId, solrClient, collectionName);
-    }
+    return sendDoc(docId, leaderJetty.getSolrClient(), collectionName);
   }
 
   protected int sendDoc(String collectionName, int docId) throws Exception {
diff --git a/solr/core/src/test/org/apache/solr/cloud/LeaderElectionContextKeyTest.java b/solr/core/src/test/org/apache/solr/cloud/LeaderElectionContextKeyTest.java
index 7591dde5362..9b1faef7fe7 100644
--- a/solr/core/src/test/org/apache/solr/cloud/LeaderElectionContextKeyTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/LeaderElectionContextKeyTest.java
@@ -23,7 +23,6 @@ import java.util.Locale;
 import java.util.concurrent.TimeUnit;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrServerException;
-import org.apache.solr.client.solrj.apache.HttpSolrClient;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
 import org.apache.solr.client.solrj.request.CoreAdminRequest;
 import org.apache.solr.common.cloud.ClusterState;
@@ -92,37 +91,32 @@ public class LeaderElectionContextKeyTest extends SolrCloudTestCase {
     Replica replica = clusterState.getCollection(TEST_COLLECTION_1).getLeader(shard);
     assertNotNull(replica);
 
-    try (SolrClient shardLeaderClient =
-        new HttpSolrClient.Builder(replica.get("base_url").toString()).build()) {
-      assertEquals(
-          1L, getElectionNodes(TEST_COLLECTION_1, shard, stateReader.getZkClient()).size());
-      List<String> collection2Shard1Nodes =
-          getElectionNodes(TEST_COLLECTION_2, "shard1", stateReader.getZkClient());
-      List<String> collection2Shard2Nodes =
-          getElectionNodes(TEST_COLLECTION_2, "shard2", stateReader.getZkClient());
-      CoreAdminRequest.unloadCore(replica.getCoreName(), shardLeaderClient);
-      // Waiting for leader election being kicked off
-      long timeout = System.nanoTime() + TimeUnit.NANOSECONDS.convert(60, TimeUnit.SECONDS);
-      boolean found = false;
-      while (System.nanoTime() < timeout) {
-        try {
-          found = getElectionNodes(TEST_COLLECTION_1, shard, stateReader.getZkClient()).size() == 0;
-          break;
-        } catch (KeeperException.NoNodeException nne) {
-          // ignore
-        }
+    SolrClient shardLeaderClient = cluster.getReplicaJetty(replica).getSolrClient();
+    assertEquals(1L, getElectionNodes(TEST_COLLECTION_1, shard, stateReader.getZkClient()).size());
+    List<String> collection2Shard1Nodes =
+        getElectionNodes(TEST_COLLECTION_2, "shard1", stateReader.getZkClient());
+    List<String> collection2Shard2Nodes =
+        getElectionNodes(TEST_COLLECTION_2, "shard2", stateReader.getZkClient());
+    CoreAdminRequest.unloadCore(replica.getCoreName(), shardLeaderClient);
+    // Waiting for leader election being kicked off
+    long timeout = System.nanoTime() + TimeUnit.NANOSECONDS.convert(60, TimeUnit.SECONDS);
+    boolean found = false;
+    while (System.nanoTime() < timeout) {
+      try {
+        found = getElectionNodes(TEST_COLLECTION_1, shard, stateReader.getZkClient()).size() == 0;
+        break;
+      } catch (KeeperException.NoNodeException nne) {
+        // ignore
       }
-      assertTrue(found);
-      // There are no leader election was kicked off on testCollection2
-      assertThat(
-          collection2Shard1Nodes,
-          CoreMatchers.is(
-              getElectionNodes(TEST_COLLECTION_2, "shard1", stateReader.getZkClient())));
-      assertThat(
-          collection2Shard2Nodes,
-          CoreMatchers.is(
-              getElectionNodes(TEST_COLLECTION_2, "shard2", stateReader.getZkClient())));
     }
+    assertTrue(found);
+    // There are no leader election was kicked off on testCollection2
+    assertThat(
+        collection2Shard1Nodes,
+        CoreMatchers.is(getElectionNodes(TEST_COLLECTION_2, "shard1", stateReader.getZkClient())));
+    assertThat(
+        collection2Shard2Nodes,
+        CoreMatchers.is(getElectionNodes(TEST_COLLECTION_2, "shard2", stateReader.getZkClient())));
   }
 
   private List<String> getElectionNodes(String collection, String shard, SolrZkClient client)
diff --git a/solr/core/src/test/org/apache/solr/cloud/LeaderVoteWaitTimeoutTest.java b/solr/core/src/test/org/apache/solr/cloud/LeaderVoteWaitTimeoutTest.java
index 4deca8cebd5..d00190cfe53 100644
--- a/solr/core/src/test/org/apache/solr/cloud/LeaderVoteWaitTimeoutTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/LeaderVoteWaitTimeoutTest.java
@@ -31,7 +31,6 @@ import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrRequest;
 import org.apache.solr.client.solrj.SolrRequest.SolrRequestType;
 import org.apache.solr.client.solrj.SolrServerException;
-import org.apache.solr.client.solrj.apache.HttpSolrClient;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
 import org.apache.solr.client.solrj.request.GenericSolrRequest;
 import org.apache.solr.common.SolrInputDocument;
@@ -291,48 +290,47 @@ public class LeaderVoteWaitTimeoutTest extends SolrCloudTestCase {
 
   private void addDoc(String collection, int docId, JettySolrRunner solrRunner)
       throws IOException, SolrServerException {
-    try (SolrClient solrClient =
-        new HttpSolrClient.Builder(solrRunner.getBaseUrl().toString()).build()) {
-      solrClient.add(collection, new SolrInputDocument("id", String.valueOf(docId)));
-      solrClient.commit(collection);
-    }
+    var solrClient = solrRunner.getSolrClient();
+    solrClient.add(collection, new SolrInputDocument("id", String.valueOf(docId)));
+    solrClient.commit(collection);
   }
 
   private void assertDocsExistInAllReplicas(
       List<Replica> notLeaders, String testCollectionName, int firstDocId, int lastDocId)
       throws Exception {
     Replica leader = cluster.getZkStateReader().getLeaderRetry(testCollectionName, "shard1", 10000);
-    SolrClient leaderSolr = getSolrClient(leader, testCollectionName);
+    SolrClient leaderSolr = getJettyForReplica(leader).getSolrClient();
     List<SolrClient> replicas = new ArrayList<>(notLeaders.size());
 
     for (Replica r : notLeaders) {
-      replicas.add(getSolrClient(r, testCollectionName));
+      replicas.add(getJettyForReplica(r).getSolrClient());
     }
-    try {
-      for (int d = firstDocId; d <= lastDocId; d++) {
-        String docId = String.valueOf(d);
-        assertDocExists(leaderSolr, docId);
-        for (SolrClient replicaSolr : replicas) {
-          assertDocExists(replicaSolr, docId);
-        }
-      }
-    } finally {
-      if (leaderSolr != null) {
-        leaderSolr.close();
-      }
+    for (int d = firstDocId; d <= lastDocId; d++) {
+      String docId = String.valueOf(d);
+      assertDocExists(leaderSolr, testCollectionName, docId);
       for (SolrClient replicaSolr : replicas) {
-        replicaSolr.close();
+        assertDocExists(replicaSolr, testCollectionName, docId);
       }
     }
   }
 
-  private void assertDocExists(SolrClient solr, String docId) throws Exception {
-    NamedList<?> rsp = realTimeGetDocId(solr, docId);
+  private JettySolrRunner getJettyForReplica(Replica replica) {
+    return cluster.getJettySolrRunners().stream()
+        .filter(j -> j.getNodeName().equals(replica.getNodeName()))
+        .findFirst()
+        .orElseThrow(
+            () ->
+                new IllegalArgumentException(
+                    "Cannot find Jetty for replica node: " + replica.getNodeName()));
+  }
+
+  private void assertDocExists(SolrClient solr, String collection, String docId) throws Exception {
+    NamedList<?> rsp = realTimeGetDocId(solr, collection, docId);
     String match = JSONTestUtil.matchObj("/id", rsp.get("doc"), docId);
     assertNull("Doc with id=" + docId + " not found due to: " + match + "; rsp=" + rsp, match);
   }
 
-  private NamedList<Object> realTimeGetDocId(SolrClient solr, String docId)
+  private NamedList<Object> realTimeGetDocId(SolrClient solr, String collection, String docId)
       throws SolrServerException, IOException {
     return solr.request(
         new GenericSolrRequest(
@@ -340,10 +338,7 @@ public class LeaderVoteWaitTimeoutTest extends SolrCloudTestCase {
                 "/get",
                 SolrRequestType.QUERY,
                 params("id", docId, "distrib", "false"))
-            .setRequiresCollection(true));
-  }
-
-  protected SolrClient getSolrClient(Replica replica, String coll) {
-    return getHttpSolrClient(replica.getBaseUrl(), coll);
+            .setRequiresCollection(true),
+        collection);
   }
 }
diff --git a/solr/core/src/test/org/apache/solr/cloud/MultiThreadedOCPTest.java b/solr/core/src/test/org/apache/solr/cloud/MultiThreadedOCPTest.java
index 6a8e56cbb51..37a26488c2d 100644
--- a/solr/core/src/test/org/apache/solr/cloud/MultiThreadedOCPTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/MultiThreadedOCPTest.java
@@ -51,7 +51,7 @@ public class MultiThreadedOCPTest extends AbstractFullDistribZkTestBase {
 
   @Test
   public void testFillWorkQueue() throws Exception {
-    try (SolrClient client = createNewSolrClient("", getBaseUrl(jettys.get(0)))) {
+    try (SolrClient client = createNewSolrClient(getBaseUrl(jettys.get(0)), null)) {
       // This test does not make sense when the Collection API execution is distributed. There is no
       // queue to fill
       if (!new CollectionAdminRequest.RequestApiDistributedProcessing()
@@ -147,7 +147,7 @@ public class MultiThreadedOCPTest extends AbstractFullDistribZkTestBase {
   @Test
   public void testParallelCollectionAPICalls() throws IOException, SolrServerException {
     final int ASYNC_SHIFT = 10000;
-    try (SolrClient client = createNewSolrClient("", getBaseUrl(jettys.get(0)))) {
+    try (SolrClient client = createNewSolrClient(getBaseUrl(jettys.get(0)), null)) {
       for (int i = 1 + ASYNC_SHIFT; i <= NUM_COLLECTIONS + ASYNC_SHIFT; i++) {
         CollectionAdminRequest.createCollection("ocptest" + i, "conf1", 3, 1)
             .processAsync(String.valueOf(i), client);
@@ -188,7 +188,7 @@ public class MultiThreadedOCPTest extends AbstractFullDistribZkTestBase {
 
   @Test
   public void testTaskExclusivity() throws Exception, SolrServerException {
-    try (SolrClient client = createNewSolrClient("", getBaseUrl(jettys.get(0)))) {
+    try (SolrClient client = createNewSolrClient(getBaseUrl(jettys.get(0)), null)) {
 
       Create createCollectionRequest =
           CollectionAdminRequest.createCollection("ocptest_shardsplit", "conf1", 4, 1);
@@ -246,7 +246,7 @@ public class MultiThreadedOCPTest extends AbstractFullDistribZkTestBase {
 
   @Test
   public void testDeduplicationOfSubmittedTasks() throws IOException, SolrServerException {
-    try (SolrClient client = createNewSolrClient("", getBaseUrl(jettys.get(0)))) {
+    try (SolrClient client = createNewSolrClient(getBaseUrl(jettys.get(0)), null)) {
       CollectionAdminRequest.createCollection("ocptest_shardsplit2", "conf1", 3, 1).process(client);
 
       SplitShard splitShardRequest =
@@ -300,7 +300,7 @@ public class MultiThreadedOCPTest extends AbstractFullDistribZkTestBase {
           }
         };
     indexThread.start();
-    try (SolrClient client = createNewSolrClient("", getBaseUrl(jettys.get(0)))) {
+    try (SolrClient client = createNewSolrClient(getBaseUrl(jettys.get(0)), null)) {
 
       SplitShard splitShardRequest =
           CollectionAdminRequest.splitShard("collection1").setShardName(SHARD1);
@@ -337,6 +337,7 @@ public class MultiThreadedOCPTest extends AbstractFullDistribZkTestBase {
           RequestStatusState.RUNNING,
           state);
 
+      getRequestStateAfterCompletion("2000", REQUEST_STATUS_TIMEOUT, client);
     } finally {
       try {
         indexThread.join();
diff --git a/solr/core/src/test/org/apache/solr/cloud/RecoveryStrategyStressTest.java b/solr/core/src/test/org/apache/solr/cloud/RecoveryStrategyStressTest.java
index 2cd0ef45a34..67c143b240f 100644
--- a/solr/core/src/test/org/apache/solr/cloud/RecoveryStrategyStressTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/RecoveryStrategyStressTest.java
@@ -24,10 +24,7 @@ import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrServerException;
-import org.apache.solr.client.solrj.apache.CloudLegacySolrClient;
-import org.apache.solr.client.solrj.apache.HttpSolrClient;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
-import org.apache.solr.client.solrj.request.SolrQuery;
 import org.apache.solr.client.solrj.request.UpdateRequest;
 import org.apache.solr.common.cloud.DocCollection;
 import org.apache.solr.common.cloud.Replica;
@@ -114,15 +111,14 @@ public class RecoveryStrategyStressTest extends SolrCloudTestCase {
     long[] numCounts = new long[replicas.size()];
     int i = 0;
     for (Replica replica : replicas) {
-      try (var client =
-          new HttpSolrClient.Builder(replica.getBaseUrl())
-              .withDefaultCollection(replica.getCoreName())
-              .withHttpClient(((CloudLegacySolrClient) cluster.getSolrClient()).getHttpClient())
-              .build()) {
-        numCounts[i] =
-            client.query(new SolrQuery("*:*").add("distrib", "false")).getResults().getNumFound();
-        i++;
-      }
+      numCounts[i] =
+          cluster
+              .getReplicaJetty(replica)
+              .getSolrClient()
+              .query(replica.getCoreName(), params("q", "*:*", "distrib", "false"))
+              .getResults()
+              .getNumFound();
+      i++;
     }
     for (int j = 1; j < replicas.size(); j++) {
       if (numCounts[j] != numCounts[j - 1])
diff --git a/solr/core/src/test/org/apache/solr/cloud/RecoveryZkTest.java b/solr/core/src/test/org/apache/solr/cloud/RecoveryZkTest.java
index 48d4855c1d2..24383d8d317 100644
--- a/solr/core/src/test/org/apache/solr/cloud/RecoveryZkTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/RecoveryZkTest.java
@@ -21,10 +21,7 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 import org.apache.solr.client.solrj.SolrClient;
-import org.apache.solr.client.solrj.apache.CloudLegacySolrClient;
-import org.apache.solr.client.solrj.apache.HttpSolrClient;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
-import org.apache.solr.client.solrj.request.SolrQuery;
 import org.apache.solr.client.solrj.request.UpdateRequest;
 import org.apache.solr.common.cloud.DocCollection;
 import org.apache.solr.common.cloud.Replica;
@@ -137,15 +134,13 @@ public class RecoveryZkTest extends SolrCloudTestCase {
     long[] numCounts = new long[replicas.size()];
     int i = 0;
     for (Replica replica : replicas) {
-      try (var client =
-          new HttpSolrClient.Builder(replica.getBaseUrl())
-              .withDefaultCollection(replica.getCoreName())
-              .withHttpClient(((CloudLegacySolrClient) cluster.getSolrClient()).getHttpClient())
-              .build()) {
-        numCounts[i] =
-            client.query(new SolrQuery("*:*").add("distrib", "false")).getResults().getNumFound();
-        i++;
-      }
+      numCounts[i++] =
+          cluster
+              .getReplicaJetty(replica)
+              .getSolrClient()
+              .query(replica.getCoreName(), params("q", "*:*", "distrib", "false"))
+              .getResults()
+              .getNumFound();
     }
     for (int j = 1; j < replicas.size(); j++) {
       if (numCounts[j] != numCounts[j - 1])
diff --git a/solr/core/src/test/org/apache/solr/cloud/RecoveryZkTestWithAuth.java b/solr/core/src/test/org/apache/solr/cloud/RecoveryZkTestWithAuth.java
index 479a5c44ba3..111bcaea8b7 100644
--- a/solr/core/src/test/org/apache/solr/cloud/RecoveryZkTestWithAuth.java
+++ b/solr/core/src/test/org/apache/solr/cloud/RecoveryZkTestWithAuth.java
@@ -18,22 +18,17 @@ package org.apache.solr.cloud;
 
 import static org.apache.solr.client.solrj.response.RequestStatusState.COMPLETED;
 
-import java.io.IOException;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrRequest;
 import org.apache.solr.client.solrj.SolrResponse;
-import org.apache.solr.client.solrj.SolrServerException;
-import org.apache.solr.client.solrj.apache.CloudLegacySolrClient;
-import org.apache.solr.client.solrj.apache.HttpSolrClient;
 import org.apache.solr.client.solrj.jetty.HttpJettySolrClient;
 import org.apache.solr.client.solrj.jetty.PreemptiveBasicAuthClientCustomizer;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
 import org.apache.solr.client.solrj.request.QueryRequest;
 import org.apache.solr.client.solrj.request.SolrQuery;
 import org.apache.solr.client.solrj.request.UpdateRequest;
-import org.apache.solr.client.solrj.response.QueryResponse;
 import org.apache.solr.client.solrj.response.RequestStatusState;
 import org.apache.solr.common.cloud.DocCollection;
 import org.apache.solr.common.cloud.Replica;
@@ -66,11 +61,6 @@ public class RecoveryZkTestWithAuth extends SolrCloudTestCase {
     return req;
   }
 
-  private QueryResponse queryWithBasicAuth(SolrClient client, SolrQuery q)
-      throws IOException, SolrServerException {
-    return withBasicAuth(new QueryRequest(q)).process(client);
-  }
-
   @Test
   public void testRecoveryWithAuthEnabled() throws Exception {
     final String collection = "recoverytestwithauth";
@@ -110,16 +100,13 @@ public class RecoveryZkTestWithAuth extends SolrCloudTestCase {
     long[] numCounts = new long[replicas.size()];
     int i = 0;
     for (Replica replica : replicas) {
-      try (var client =
-          new HttpSolrClient.Builder(replica.getBaseUrl())
-              .withDefaultCollection(replica.getCoreName())
-              .withHttpClient(((CloudLegacySolrClient) cluster.getSolrClient()).getHttpClient())
-              .build()) {
-        var q = new SolrQuery("*:*");
-        q.add("distrib", "false");
-        numCounts[i] = queryWithBasicAuth(client, q).getResults().getNumFound();
-        i++;
-      }
+      var client = cluster.getReplicaJetty(replica).getSolrClient();
+      var q = new SolrQuery("q", "*:*", "distrib", "false");
+      numCounts[i++] =
+          withBasicAuth(new QueryRequest(q))
+              .process(client, replica.getCoreName())
+              .getResults()
+              .getNumFound();
     }
     for (int j = 1; j < replicas.size(); j++) {
       if (numCounts[j] != numCounts[j - 1])
diff --git a/solr/core/src/test/org/apache/solr/cloud/RouteFieldTest.java b/solr/core/src/test/org/apache/solr/cloud/RouteFieldTest.java
index 0b6bd761ee5..fbc789ed2f5 100644
--- a/solr/core/src/test/org/apache/solr/cloud/RouteFieldTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/RouteFieldTest.java
@@ -26,7 +26,6 @@ import java.util.List;
 import java.util.Map;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrServerException;
-import org.apache.solr.client.solrj.apache.HttpSolrClient;
 import org.apache.solr.client.solrj.impl.CloudSolrClient;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
 import org.apache.solr.client.solrj.request.QueryRequest;
@@ -174,11 +173,13 @@ public class RouteFieldTest extends SolrCloudTestCase {
 
   private SolrDocumentList getDocsMatching(String coreUrl, QueryRequest request)
       throws IOException, SolrServerException {
-    final var baseUrl = URLUtil.extractBaseUrl(coreUrl);
     final var coreName = URLUtil.extractCoreFromCoreUrl(coreUrl);
-    try (SolrClient solrClient =
-        new HttpSolrClient.Builder(baseUrl).withDefaultCollection(coreName).build()) {
-      return (SolrDocumentList) solrClient.request(request).get("response");
-    }
+    SolrClient solrClient =
+        cluster.getJettySolrRunners().stream()
+            .filter(j -> coreUrl.startsWith(j.getBaseUrl().toString()))
+            .findFirst()
+            .orElseThrow()
+            .getSolrClient();
+    return request.process(solrClient, coreName).getResults();
   }
 }
diff --git a/solr/core/src/test/org/apache/solr/cloud/SSLMigrationTest.java b/solr/core/src/test/org/apache/solr/cloud/SSLMigrationTest.java
index bbdfecf3475..b0e329f0836 100644
--- a/solr/core/src/test/org/apache/solr/cloud/SSLMigrationTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/SSLMigrationTest.java
@@ -23,9 +23,8 @@ import java.util.Map;
 import java.util.Properties;
 import org.apache.lucene.tests.util.LuceneTestCase.AwaitsFix;
 import org.apache.solr.SolrTestCaseJ4.SuppressSSL;
-import org.apache.solr.client.solrj.SolrClient;
-import org.apache.solr.client.solrj.apache.HttpClientUtil;
-import org.apache.solr.client.solrj.apache.LBHttpSolrClient;
+import org.apache.solr.client.solrj.impl.LBSolrClient;
+import org.apache.solr.client.solrj.jetty.LBJettySolrClient;
 import org.apache.solr.client.solrj.request.QueryRequest;
 import org.apache.solr.common.cloud.DocCollection;
 import org.apache.solr.common.cloud.Replica;
@@ -37,7 +36,6 @@ import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.embedded.JettyConfig;
 import org.apache.solr.embedded.JettySolrRunner;
 import org.apache.solr.util.SSLTestConfig;
-import org.junit.After;
 import org.junit.Test;
 
 /**
@@ -48,11 +46,6 @@ import org.junit.Test;
 @AwaitsFix(bugUrl = "https://issues.apache.org/jira/browse/SOLR-12028") // 17-Mar-2018
 public class SSLMigrationTest extends AbstractFullDistribZkTestBase {
 
-  @After
-  public void afterTest() {
-    HttpClientUtil.resetHttpClientBuilder(); // also resets SocketFactoryRegistryProvider
-  }
-
   @Test
   public void test() throws Exception {
     // Migrate from HTTP -> HTTPS -> HTTP
@@ -69,8 +62,6 @@ public class SSLMigrationTest extends AbstractFullDistribZkTestBase {
       runner.stop();
     }
 
-    HttpClientUtil.setSocketFactoryRegistryProvider(
-        sslConfig.buildClientSocketFactoryRegistryProvider()); // we reset in After
     for (int i = 0; i < this.jettys.size(); i++) {
       JettySolrRunner runner = jettys.get(i);
       JettyConfig config =
@@ -128,12 +119,14 @@ public class SSLMigrationTest extends AbstractFullDistribZkTestBase {
     QueryRequest request = new QueryRequest(params);
     request.setPath("/admin/collections");
 
-    String[] urls =
+    LBSolrClient.Endpoint[] urls =
         getReplicas().stream()
             .map(r -> r.getStr(ZkStateReader.BASE_URL_PROP))
-            .toArray(String[]::new);
-    // Create new SolrServer to configure new HttpClient w/ SSL config
-    try (SolrClient client = new LBHttpSolrClient.Builder().withBaseEndpoints(urls).build()) {
+            .map(LBSolrClient.Endpoint::new)
+            .toArray(LBSolrClient.Endpoint[]::new);
+    // Create new HttpClient w/ SSL config
+    try (var client =
+        new LBJettySolrClient.Builder(jettys.getFirst().getSolrClient(), urls).build()) {
       client.request(request);
     }
   }
diff --git a/solr/core/src/test/org/apache/solr/cloud/ShardRoutingCustomTest.java b/solr/core/src/test/org/apache/solr/cloud/ShardRoutingCustomTest.java
index 004d7cc06e8..c101e533139 100644
--- a/solr/core/src/test/org/apache/solr/cloud/ShardRoutingCustomTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/ShardRoutingCustomTest.java
@@ -18,7 +18,6 @@ package org.apache.solr.cloud;
 
 import java.nio.file.Files;
 import java.nio.file.Path;
-import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
 import org.apache.solr.common.cloud.Replica;
 import org.apache.solr.embedded.JettySolrRunner;
@@ -76,8 +75,7 @@ public class ShardRoutingCustomTest extends AbstractFullDistribZkTestBase {
             .process(cloudClient)
             .isSuccess());
     jettys.add(j);
-    SolrClient client = createNewSolrClient(j.getLocalPort());
-    clients.add(client);
+    clients.add(createNewSolrClient(j.getLocalPort()));
 
     waitForActiveReplicaCount(cloudClient, DEFAULT_COLLECTION, 1);
 
diff --git a/solr/core/src/test/org/apache/solr/cloud/SyncSliceTest.java b/solr/core/src/test/org/apache/solr/cloud/SyncSliceTest.java
index 8f49afba970..65f2959dd96 100644
--- a/solr/core/src/test/org/apache/solr/cloud/SyncSliceTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/SyncSliceTest.java
@@ -29,7 +29,7 @@ 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.apache.HttpSolrClient;
+import org.apache.solr.client.solrj.jetty.HttpJettySolrClient;
 import org.apache.solr.client.solrj.request.GenericSolrRequest;
 import org.apache.solr.client.solrj.request.SolrQuery;
 import org.apache.solr.client.solrj.request.UpdateRequest;
@@ -107,7 +107,7 @@ public class SyncSliceTest extends AbstractFullDistribZkTestBase {
 
     // we only set the connect timeout, not so timeout
     try (SolrClient baseClient =
-        new HttpSolrClient.Builder(baseUrl)
+        new HttpJettySolrClient.Builder(baseUrl)
             .withConnectionTimeout(30000, TimeUnit.MILLISECONDS)
             .build()) {
       baseClient.request(request);
diff --git a/solr/core/src/test/org/apache/solr/cloud/TestAuthenticationFramework.java b/solr/core/src/test/org/apache/solr/cloud/TestAuthenticationFramework.java
index 6a90af12d04..130e288ee96 100644
--- a/solr/core/src/test/org/apache/solr/cloud/TestAuthenticationFramework.java
+++ b/solr/core/src/test/org/apache/solr/cloud/TestAuthenticationFramework.java
@@ -21,7 +21,7 @@ import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
 import java.lang.invoke.MethodHandles;
 import java.util.Map;
-import org.apache.solr.client.solrj.RemoteSolrException;
+import org.apache.solr.client.solrj.SolrServerException;
 import org.apache.solr.client.solrj.impl.CloudSolrClient;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
 import org.apache.solr.client.solrj.request.SolrQuery;
@@ -70,9 +70,11 @@ public class TestAuthenticationFramework extends SolrCloudTestCase {
 
     // Should fail with 401
     try {
-      RemoteSolrException e =
-          expectThrows(RemoteSolrException.class, this::collectionCreateSearchDeleteTwice);
-      assertTrue("Should've returned a 401 error", e.getMessage().contains("Error 401"));
+      SolrServerException e =
+          expectThrows(SolrServerException.class, this::collectionCreateSearchDeleteTwice);
+      assertTrue(
+          "Should've returned a 401 error",
+          e.getMessage().contains("401") || e.getMessage().contains("Authentication"));
     } finally {
       MockAuthenticationPlugin.expectedUsername = null;
       MockAuthenticationPlugin.expectedPassword = null;
diff --git a/solr/core/src/test/org/apache/solr/cloud/TestCloudConsistency.java b/solr/core/src/test/org/apache/solr/cloud/TestCloudConsistency.java
index edae061cbb8..9041d42555d 100644
--- a/solr/core/src/test/org/apache/solr/cloud/TestCloudConsistency.java
+++ b/solr/core/src/test/org/apache/solr/cloud/TestCloudConsistency.java
@@ -32,7 +32,6 @@ import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrRequest;
 import org.apache.solr.client.solrj.SolrRequest.SolrRequestType;
 import org.apache.solr.client.solrj.SolrServerException;
-import org.apache.solr.client.solrj.apache.HttpSolrClient;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
 import org.apache.solr.client.solrj.request.GenericSolrRequest;
 import org.apache.solr.common.SolrInputDocument;
@@ -297,13 +296,13 @@ public class TestCloudConsistency extends SolrCloudTestCase {
 
   private void addDoc(String collection, int docId, JettySolrRunner solrRunner)
       throws IOException, SolrServerException {
-    try (SolrClient solrClient =
-        new HttpSolrClient.Builder(solrRunner.getBaseUrl().toString()).build()) {
-      solrClient.add(
-          collection,
-          new SolrInputDocument("id", String.valueOf(docId), "fieldName_s", String.valueOf(docId)));
-      solrClient.commit(collection);
-    }
+    solrRunner
+        .getSolrClient()
+        .add(
+            collection,
+            new SolrInputDocument(
+                "id", String.valueOf(docId), "fieldName_s", String.valueOf(docId)));
+    solrRunner.getSolrClient().commit(collection);
   }
 
   private void assertDocsExistInAllReplicas(
diff --git a/solr/core/src/test/org/apache/solr/cloud/TestLeaderElectionWithEmptyReplica.java b/solr/core/src/test/org/apache/solr/cloud/TestLeaderElectionWithEmptyReplica.java
index dccb8aadc57..e9cb371f25a 100644
--- a/solr/core/src/test/org/apache/solr/cloud/TestLeaderElectionWithEmptyReplica.java
+++ b/solr/core/src/test/org/apache/solr/cloud/TestLeaderElectionWithEmptyReplica.java
@@ -22,8 +22,6 @@ import java.util.List;
 import java.util.concurrent.TimeUnit;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrServerException;
-import org.apache.solr.client.solrj.apache.CloudLegacySolrClient;
-import org.apache.solr.client.solrj.apache.HttpSolrClient.Builder;
 import org.apache.solr.client.solrj.impl.CloudSolrClient;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
 import org.apache.solr.client.solrj.request.SolrQuery;
@@ -116,12 +114,9 @@ public class TestLeaderElectionWithEmptyReplica extends SolrCloudTestCase {
     long numFound = Long.MIN_VALUE;
     int count = 0;
     for (Replica replica : shard.getReplicas()) {
-      SolrClient client =
-          new Builder(replica.getBaseUrl())
-              .withDefaultCollection(replica.getCoreName())
-              .withHttpClient(((CloudLegacySolrClient) cloudClient).getHttpClient())
-              .build();
-      QueryResponse response = client.query(new SolrQuery("q", "*:*", "distrib", "false"));
+      SolrClient client = cluster.getReplicaJetty(replica).getSolrClient();
+      QueryResponse response =
+          client.query(replica.getCoreName(), params("q", "*:*", "distrib", "false"));
       //      log.info("Found numFound={} on replica: {}", response.getResults().getNumFound(),
       // replica.getCoreUrl());
       if (numFound == Long.MIN_VALUE) {
diff --git a/solr/core/src/test/org/apache/solr/cloud/TestMiniSolrCloudClusterSSL.java b/solr/core/src/test/org/apache/solr/cloud/TestMiniSolrCloudClusterSSL.java
index 85072d26d32..8fbbe214fe0 100644
--- a/solr/core/src/test/org/apache/solr/cloud/TestMiniSolrCloudClusterSSL.java
+++ b/solr/core/src/test/org/apache/solr/cloud/TestMiniSolrCloudClusterSSL.java
@@ -25,9 +25,8 @@ import org.apache.lucene.util.Constants;
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrServerException;
-import org.apache.solr.client.solrj.apache.HttpClientUtil;
-import org.apache.solr.client.solrj.apache.HttpSolrClient;
 import org.apache.solr.client.solrj.impl.CloudSolrClient;
+import org.apache.solr.client.solrj.impl.HttpSolrClient;
 import org.apache.solr.client.solrj.impl.SolrHttpConstants;
 import org.apache.solr.client.solrj.jetty.HttpJettySolrClient;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
@@ -83,14 +82,12 @@ public class TestMiniSolrCloudClusterSSL extends SolrTestCaseJ4 {
     // undo the randomization of our super class
     log.info(
         "NOTE: This Test ignores the randomized SSL & clientAuth settings selected by base class");
-    HttpClientUtil.resetHttpClientBuilder(); // also resets SocketFactoryRegistryProvider
     HttpJettySolrClient.resetSslContextFactory();
     System.clearProperty(SOLR_SSL_ENABLED);
   }
 
   @After
   public void after() {
-    HttpClientUtil.resetHttpClientBuilder(); // also resets SocketFactoryRegistryProvider
     HttpJettySolrClient.resetSslContextFactory();
     System.clearProperty(SOLR_SSL_ENABLED);
     SSLContext.setDefault(DEFAULT_SSL_CONTEXT);
@@ -98,8 +95,6 @@ public class TestMiniSolrCloudClusterSSL extends SolrTestCaseJ4 {
 
   public void testNoSsl() throws Exception {
     final SSLTestConfig sslConfig = new SSLTestConfig(false, false);
-    HttpClientUtil.setSocketFactoryRegistryProvider(
-        sslConfig.buildClientSocketFactoryRegistryProvider()); // must be reset
     HttpJettySolrClient.setDefaultSSLConfig(sslConfig.buildClientSSLConfig()); // must be reset
     System.setProperty(SOLR_SSL_ENABLED, "false");
     checkClusterWithNodeReplacement(sslConfig);
@@ -110,8 +105,6 @@ public class TestMiniSolrCloudClusterSSL extends SolrTestCaseJ4 {
     // ignored, but we test it anyway for completeness of checking the behavior of code that
     // looks at those options.
     final SSLTestConfig sslConfig = new SSLTestConfig(false, true);
-    HttpClientUtil.setSocketFactoryRegistryProvider(
-        sslConfig.buildClientSocketFactoryRegistryProvider());
     HttpJettySolrClient.setDefaultSSLConfig(sslConfig.buildClientSSLConfig());
     System.setProperty(SOLR_SSL_ENABLED, "false");
     checkClusterWithNodeReplacement(sslConfig);
@@ -119,8 +112,6 @@ public class TestMiniSolrCloudClusterSSL extends SolrTestCaseJ4 {
 
   public void testSslAndNoClientAuth() throws Exception {
     final SSLTestConfig sslConfig = new SSLTestConfig(true, false);
-    HttpClientUtil.setSocketFactoryRegistryProvider(
-        sslConfig.buildClientSocketFactoryRegistryProvider());
     HttpJettySolrClient.setDefaultSSLConfig(sslConfig.buildClientSSLConfig());
     System.setProperty(SOLR_SSL_ENABLED, "true");
     checkClusterWithNodeReplacement(sslConfig);
@@ -131,8 +122,6 @@ public class TestMiniSolrCloudClusterSSL extends SolrTestCaseJ4 {
 
     final SSLTestConfig sslConfig = new SSLTestConfig(true, true);
 
-    HttpClientUtil.setSocketFactoryRegistryProvider(
-        sslConfig.buildClientSocketFactoryRegistryProvider());
     HttpJettySolrClient.setDefaultSSLConfig(sslConfig.buildClientSSLConfig());
     System.setProperty(SOLR_SSL_ENABLED, "true");
     checkClusterWithNodeReplacement(sslConfig);
@@ -140,8 +129,6 @@ public class TestMiniSolrCloudClusterSSL extends SolrTestCaseJ4 {
 
   public void testSslWithCheckPeerName() throws Exception {
     final SSLTestConfig sslConfig = new SSLTestConfig(true, false, true);
-    HttpClientUtil.setSocketFactoryRegistryProvider(
-        sslConfig.buildClientSocketFactoryRegistryProvider());
     HttpJettySolrClient.setDefaultSSLConfig(sslConfig.buildClientSSLConfig());
     System.setProperty(SOLR_SSL_ENABLED, "true");
     checkClusterWithNodeReplacement(sslConfig);
@@ -154,7 +141,6 @@ public class TestMiniSolrCloudClusterSSL extends SolrTestCaseJ4 {
    * HttpClientBuilder, new HttpJettySolrClient instances will still be able to talk to our servers.
    *
    * @see SSLContext#setDefault
-   * @see HttpClientUtil#resetHttpClientBuilder
    * @see #checkClusterWithCollectionCreations
    */
   private void checkClusterWithNodeReplacement(SSLTestConfig sslConfig) throws Exception {
@@ -174,8 +160,8 @@ public class TestMiniSolrCloudClusterSSL extends SolrTestCaseJ4 {
       System.setProperty(
           SolrHttpConstants.SYS_PROP_CHECK_PEER_NAME,
           Boolean.toString(sslConfig.getCheckPeerName()));
-      HttpClientUtil.resetHttpClientBuilder();
       HttpJettySolrClient.resetSslContextFactory();
+      HttpJettySolrClient.setDefaultSSLConfig(sslConfig.buildClientSSLConfig());
 
       // recheck that we can communicate with all the jetty instances in our cluster
       checkClusterJettys(cluster, sslConfig);
@@ -189,8 +175,6 @@ public class TestMiniSolrCloudClusterSSL extends SolrTestCaseJ4 {
     // NOTE: first initialize the cluster w/o peer name checks, which means our server will use
     // certs with a bogus hostname/ip and clients shouldn't care...
     final SSLTestConfig sslConfig = new SSLTestConfig(true, false, false);
-    HttpClientUtil.setSocketFactoryRegistryProvider(
-        sslConfig.buildClientSocketFactoryRegistryProvider());
     HttpJettySolrClient.setDefaultSSLConfig(sslConfig.buildClientSSLConfig());
     System.setProperty(SOLR_SSL_ENABLED, "true");
     final JettyConfig config =
@@ -203,7 +187,6 @@ public class TestMiniSolrCloudClusterSSL extends SolrTestCaseJ4 {
       // now initialize a client that still uses the existing SSLContext/Provider, so it will accept
       // our existing certificate, but *does* care about validating the peer name
       System.setProperty(SolrHttpConstants.SYS_PROP_CHECK_PEER_NAME, "true");
-      HttpClientUtil.resetHttpClientBuilder();
       HttpJettySolrClient.resetSslContextFactory();
 
       // and validate we get failures when trying to talk to our cluster...
@@ -425,9 +408,8 @@ public class TestMiniSolrCloudClusterSSL extends SolrTestCaseJ4 {
     // ... so we are hopefully future proofing against possible changes to
     // SolrTestCaseJ4.getHttpSolrClient that "optimize" the test client construction in a way that
     // would prevent us from finding bugs with regular HttpSolrClient instantiation.
-    // This test fails if you return a HttpJettySolrClient
     if (random().nextBoolean()) {
-      return (new HttpSolrClient.Builder(url)).build();
+      return HttpSolrClient.builder(url).build();
     } // else...
     return getHttpSolrClient(url);
   }
diff --git a/solr/core/src/test/org/apache/solr/cloud/TestRandomFlRTGCloud.java b/solr/core/src/test/org/apache/solr/cloud/TestRandomFlRTGCloud.java
index 0423498ba6a..53c9fe0b3ac 100644
--- a/solr/core/src/test/org/apache/solr/cloud/TestRandomFlRTGCloud.java
+++ b/solr/core/src/test/org/apache/solr/cloud/TestRandomFlRTGCloud.java
@@ -30,7 +30,6 @@ import java.util.Map;
 import java.util.Random;
 import java.util.Set;
 import java.util.TreeSet;
-import java.util.concurrent.ConcurrentHashMap;
 import javax.xml.stream.XMLStreamConstants;
 import javax.xml.stream.XMLStreamException;
 import javax.xml.stream.XMLStreamReader;
@@ -38,8 +37,8 @@ import org.apache.commons.io.FilenameUtils;
 import org.apache.lucene.tests.util.TestUtil;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrServerException;
-import org.apache.solr.client.solrj.apache.HttpSolrClient;
 import org.apache.solr.client.solrj.impl.CloudSolrClient;
+import org.apache.solr.client.solrj.impl.CollectionScopedSolrClient;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
 import org.apache.solr.client.solrj.request.QueryRequest;
 import org.apache.solr.client.solrj.response.QueryResponse;
@@ -52,7 +51,6 @@ 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.common.params.SolrParams;
-import org.apache.solr.common.util.IOUtils;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.embedded.JettySolrRunner;
 import org.apache.solr.response.transform.DocTransformer;
@@ -77,9 +75,6 @@ public class TestRandomFlRTGCloud extends SolrCloudTestCase {
   /** A collection specific client for operations at the cloud level */
   private static CloudSolrClient COLLECTION_CLIENT;
 
-  /** We have a map of clients, keyed by base URL */
-  private static final Map<String, SolrClient> CLIENTS = new ConcurrentHashMap<>();
-
   /** Always included in fl, so we can check what doc we're looking at */
   private static final FlValidator ID_VALIDATOR = new SimpleFieldValueValidator("id");
 
@@ -167,7 +162,7 @@ public class TestRandomFlRTGCloud extends SolrCloudTestCase {
 
     configureCluster(numNodes).addConfig(configName, configDir).configure();
 
-    COLLECTION_CLIENT = cluster.getSolrClient(COLLECTION_NAME);
+    COLLECTION_CLIENT = cluster.getSolrClient(COLLECTION_NAME); // FYI is closed automatically
 
     CollectionAdminRequest.createCollection(COLLECTION_NAME, configName, numShards, repFactor)
         .withProperty("config", "solrconfig-tlog.xml")
@@ -179,14 +174,7 @@ public class TestRandomFlRTGCloud extends SolrCloudTestCase {
 
   @AfterClass
   public static void afterClass() throws Exception {
-    if (null != COLLECTION_CLIENT) {
-      COLLECTION_CLIENT.close();
-      COLLECTION_CLIENT = null;
-    }
-    for (SolrClient client : CLIENTS.values()) {
-      IOUtils.closeQuietly(client);
-    }
-    CLIENTS.clear();
+    COLLECTION_CLIENT = null;
   }
 
   /**
@@ -420,10 +408,6 @@ public class TestRandomFlRTGCloud extends SolrCloudTestCase {
     }
   }
 
-  private static SolrClient getSolrClient(final String jettyBaseUrl) {
-    return new HttpSolrClient.Builder(jettyBaseUrl).withDefaultCollection(COLLECTION_NAME).build();
-  }
-
   /**
    * Does one or more RTG request for the specified docIds with a randomized fl &amp; fq params,
    * asserting that the returned document (if any) makes sense given the expected SolrInputDocuments
@@ -617,9 +601,8 @@ public class TestRandomFlRTGCloud extends SolrCloudTestCase {
       // Return the CloudSolrClient, it only uses javabin writerType.
       return COLLECTION_CLIENT;
     } else {
-      JettySolrRunner jetty = jettySolrRunners.get(idx);
-      String jettyBaseUrl = jetty.getBaseUrl().toString();
-      return CLIENTS.computeIfAbsent(jettyBaseUrl, TestRandomFlRTGCloud::getSolrClient);
+      var solrClient = jettySolrRunners.get(idx).getSolrClient();
+      return new CollectionScopedSolrClient(solrClient, COLLECTION_NAME);
     }
   }
 
diff --git a/solr/core/src/test/org/apache/solr/cloud/TestRandomRequestDistribution.java b/solr/core/src/test/org/apache/solr/cloud/TestRandomRequestDistribution.java
index 218bff726e1..08330e310c2 100644
--- a/solr/core/src/test/org/apache/solr/cloud/TestRandomRequestDistribution.java
+++ b/solr/core/src/test/org/apache/solr/cloud/TestRandomRequestDistribution.java
@@ -29,7 +29,7 @@ import org.apache.lucene.tests.util.TestUtil;
 import org.apache.solr.BaseDistributedSearchTestCase;
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.client.solrj.SolrClient;
-import org.apache.solr.client.solrj.apache.HttpSolrClient;
+import org.apache.solr.client.solrj.jetty.HttpJettySolrClient;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
 import org.apache.solr.client.solrj.request.SolrQuery;
 import org.apache.solr.cloud.overseer.OverseerAction;
@@ -107,44 +107,42 @@ public class TestRandomRequestDistribution extends AbstractFullDistribZkTestBase
     Collection<Replica> replicas = b1x1.getSlice("shard1").getReplicas();
     assertEquals(1, replicas.size());
     String baseUrl = replicas.iterator().next().getBaseUrl();
-    if (!baseUrl.endsWith("/")) baseUrl += "/";
-    try (SolrClient client =
-        new HttpSolrClient.Builder(baseUrl)
-            .withDefaultCollection("a1x2")
-            .withConnectionTimeout(2000, TimeUnit.MILLISECONDS)
-            .withSocketTimeout(5000, TimeUnit.MILLISECONDS)
-            .build()) {
-
-      long expectedTotalRequests = 0;
-      Set<String> uniqueCoreNames = new LinkedHashSet<>();
-
-      log.info("Making requests to {} a1x2", baseUrl);
-      while (uniqueCoreNames.size() < cores.size() && expectedTotalRequests < 1000L) {
-        expectedTotalRequests++;
-        client.query(new SolrQuery("*:*"));
-
-        double actualTotalRequests = 0;
-        for (Map.Entry<String, SolrCore> entry : cores.entrySet()) {
-          final double coreCount = getSelectRequestCount(entry.getValue());
-          actualTotalRequests += coreCount;
-          if (0 < coreCount) {
-            uniqueCoreNames.add(entry.getKey());
-          }
+    SolrClient client =
+        jettys.stream()
+            .filter(j -> baseUrl.equals(j.getBaseUrl().toString()))
+            .findFirst()
+            .orElseThrow()
+            .getSolrClient();
+
+    long expectedTotalRequests = 0;
+    Set<String> uniqueCoreNames = new LinkedHashSet<>();
+
+    log.info("Making requests to {} a1x2", baseUrl);
+    while (uniqueCoreNames.size() < cores.size() && expectedTotalRequests < 1000L) {
+      expectedTotalRequests++;
+      client.query("a1x2", new SolrQuery("*:*"));
+
+      double actualTotalRequests = 0;
+      for (Map.Entry<String, SolrCore> entry : cores.entrySet()) {
+        final double coreCount = getSelectRequestCount(entry.getValue());
+        actualTotalRequests += coreCount;
+        if (0 < coreCount) {
+          uniqueCoreNames.add(entry.getKey());
         }
-        assertEquals(
-            "Sanity Check: Num Queries So Far Doesn't Match Total????",
-            expectedTotalRequests,
-            (long) actualTotalRequests);
       }
-      log.info("Total requests: {}", expectedTotalRequests);
       assertEquals(
-          "either request randomization code is broken of this test seed is really unlucky, "
-              + "Gave up waiting for requests to hit every core at least once after "
-              + expectedTotalRequests
-              + " requests",
-          uniqueCoreNames.size(),
-          cores.size());
+          "Sanity Check: Num Queries So Far Doesn't Match Total????",
+          expectedTotalRequests,
+          (long) actualTotalRequests);
     }
+    log.info("Total requests: {}", expectedTotalRequests);
+    assertEquals(
+        "either request randomization code is broken of this test seed is really unlucky, "
+            + "Gave up waiting for requests to hit every core at least once after "
+            + expectedTotalRequests
+            + " requests",
+        uniqueCoreNames.size(),
+        cores.size());
   }
 
   /** Asserts that requests against a collection are only served by a 'active' local replica */
@@ -221,10 +219,10 @@ public class TestRandomRequestDistribution extends AbstractFullDistribZkTestBase
     String baseUrl = notLeader.getBaseUrl();
     log.info("Firing queries against path={} and collection=football", baseUrl);
     try (SolrClient client =
-        new HttpSolrClient.Builder(baseUrl)
+        new HttpJettySolrClient.Builder(baseUrl)
             .withDefaultCollection("football")
             .withConnectionTimeout(2000, TimeUnit.MILLISECONDS)
-            .withSocketTimeout(5000, TimeUnit.MILLISECONDS)
+            .withIdleTimeout(5000, TimeUnit.MILLISECONDS)
             .build()) {
 
       SolrCore leaderCore = null;
diff --git a/solr/core/src/test/org/apache/solr/cloud/TestStressInPlaceUpdates.java b/solr/core/src/test/org/apache/solr/cloud/TestStressInPlaceUpdates.java
index 777188e10d8..9239f4720b5 100644
--- a/solr/core/src/test/org/apache/solr/cloud/TestStressInPlaceUpdates.java
+++ b/solr/core/src/test/org/apache/solr/cloud/TestStressInPlaceUpdates.java
@@ -30,7 +30,6 @@ import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicLong;
 import org.apache.commons.math3.primes.Primes;
 import org.apache.solr.client.solrj.SolrClient;
-import org.apache.solr.client.solrj.apache.HttpSolrClient;
 import org.apache.solr.client.solrj.request.UpdateRequest;
 import org.apache.solr.client.solrj.response.QueryResponse;
 import org.apache.solr.client.solrj.response.UpdateResponse;
@@ -679,9 +678,9 @@ public class TestStressInPlaceUpdates extends AbstractFullDistribZkTestBase {
     Slice shard1 = clusterState.getCollection(DEFAULT_COLLECTION).getSlice(SHARD1);
     leader = shard1.getLeader();
 
-    for (SolrClient client : clients) {
+    for (var client : clients) {
       String leaderBaseUrl = zkStateReader.getBaseUrlForNodeName(leader.getNodeName());
-      if (((HttpSolrClient) client).getBaseURL().startsWith(leaderBaseUrl)) {
+      if (client.getBaseURL().startsWith(leaderBaseUrl)) {
         return client;
       }
     }
diff --git a/solr/core/src/test/org/apache/solr/cloud/UnloadDistributedZkTest.java b/solr/core/src/test/org/apache/solr/cloud/UnloadDistributedZkTest.java
index ce003c19fb7..093470a2bb9 100644
--- a/solr/core/src/test/org/apache/solr/cloud/UnloadDistributedZkTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/UnloadDistributedZkTest.java
@@ -30,15 +30,13 @@ import java.util.concurrent.TimeoutException;
 import org.apache.solr.SolrTestCaseJ4.SuppressSSL;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrServerException;
-import org.apache.solr.client.solrj.apache.HttpSolrClient;
+import org.apache.solr.client.solrj.impl.CollectionScopedSolrClient;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
 import org.apache.solr.client.solrj.request.CoreAdminRequest.Unload;
-import org.apache.solr.client.solrj.request.SolrQuery;
 import org.apache.solr.common.SolrInputDocument;
 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.params.ModifiableSolrParams;
 import org.apache.solr.common.util.ExecutorUtil;
 import org.apache.solr.common.util.SolrNamedThreadFactory;
 import org.apache.solr.core.SolrCore;
@@ -83,16 +81,6 @@ public class UnloadDistributedZkTest extends AbstractFullDistribZkTestBase {
     testUnloadShardAndCollection();
   }
 
-  /**
-   * @param url a Solr node base URL. Should <em>not</em> contain a core or collection name.
-   */
-  private SolrClient newSolrClient(String url) {
-    return new HttpSolrClient.Builder(url)
-        .withConnectionTimeout(15000, TimeUnit.MILLISECONDS)
-        .withSocketTimeout(30000, TimeUnit.MILLISECONDS)
-        .build();
-  }
-
   private void checkCoreNamePresenceAndSliceCount(
       String collectionName, String coreName, boolean shouldBePresent, int expectedSliceCount)
       throws Exception {
@@ -247,21 +235,21 @@ public class UnloadDistributedZkTest extends AbstractFullDistribZkTestBase {
 
     Random random = random();
     if (random.nextBoolean()) {
-      try (SolrClient collectionClient =
-          getHttpSolrClient(leader.getBaseUrl(), leader.getCoreName())) {
-        // lets try and use the solrj client to index and retrieve a couple
-        // documents
-        SolrInputDocument doc1 =
-            getDoc(id, 6, i1, -600, tlong, 600, t1, "humpty dumpy sat on a wall");
-        SolrInputDocument doc2 =
-            getDoc(id, 7, i1, -600, tlong, 600, t1, "humpty dumpy3 sat on a walls");
-        SolrInputDocument doc3 =
-            getDoc(id, 8, i1, -600, tlong, 600, t1, "humpty dumpy2 sat on a walled");
-        collectionClient.add(doc1);
-        collectionClient.add(doc2);
-        collectionClient.add(doc3);
-        collectionClient.commit();
-      }
+      SolrClient collectionClient =
+          new CollectionScopedSolrClient(getReplicaNodeClient(leader), leader.getCoreName());
+
+      // lets try and use the solrj client to index and retrieve a couple
+      // documents
+      SolrInputDocument doc1 =
+          getDoc(id, 6, i1, -600, tlong, 600, t1, "humpty dumpy sat on a wall");
+      SolrInputDocument doc2 =
+          getDoc(id, 7, i1, -600, tlong, 600, t1, "humpty dumpy3 sat on a walls");
+      SolrInputDocument doc3 =
+          getDoc(id, 8, i1, -600, tlong, 600, t1, "humpty dumpy2 sat on a walled");
+      collectionClient.add(doc1);
+      collectionClient.add(doc2);
+      collectionClient.add(doc3);
+      collectionClient.commit();
     }
 
     assertTrue(
@@ -276,30 +264,22 @@ public class UnloadDistributedZkTest extends AbstractFullDistribZkTestBase {
     // so that we start with some versions when we reload...
     TestInjection.skipIndexWriterCommitOnClose = true;
 
-    try (SolrClient addClient =
-        new HttpSolrClient.Builder(jettys.get(2).getBaseUrl().toString())
-            .withDefaultCollection("unloadcollection_shard1_replica3")
-            .withConnectionTimeout(30000, TimeUnit.MILLISECONDS)
-            .build()) {
-
+    {
       // add a few docs
       for (int x = 20; x < 100; x++) {
         SolrInputDocument doc1 =
             getDoc(id, x, i1, -600, tlong, 600, t1, "humpty dumpy sat on a wall");
-        addClient.add(doc1);
+        jettys.get(2).getSolrClient().add("unloadcollection_shard1_replica3", doc1);
       }
     }
     // don't commit so they remain in the tran log
     // collectionClient.commit();
 
     // unload the leader
-    try (SolrClient collectionClient = newSolrClient(leader.getBaseUrl())) {
-
+    {
       Unload unloadCmd = new Unload(false);
       unloadCmd.setCoreName(leader.getCoreName());
-      ModifiableSolrParams p = (ModifiableSolrParams) unloadCmd.getParams();
-
-      collectionClient.request(unloadCmd);
+      getReplicaNodeClient(leader).request(unloadCmd);
     }
     //    Thread.currentThread().sleep(500);
     //    printLayout();
@@ -317,19 +297,11 @@ public class UnloadDistributedZkTest extends AbstractFullDistribZkTestBase {
     // ensure there is a leader
     zkStateReader.getLeaderRetry("unloadcollection", "shard1", 15000);
 
-    try (SolrClient addClient =
-        new HttpSolrClient.Builder(jettys.get(1).getBaseUrl().toString())
-            .withDefaultCollection("unloadcollection_shard1_replica2")
-            .withConnectionTimeout(30000, TimeUnit.MILLISECONDS)
-            .withSocketTimeout(90000, TimeUnit.MILLISECONDS)
-            .build()) {
-
-      // add a few docs while the leader is down
-      for (int x = 101; x < 200; x++) {
-        SolrInputDocument doc1 =
-            getDoc(id, x, i1, -600, tlong, 600, t1, "humpty dumpy sat on a wall");
-        addClient.add(doc1);
-      }
+    // add a few docs while the leader is down
+    for (int x = 101; x < 200; x++) {
+      SolrInputDocument doc1 =
+          getDoc(id, x, i1, -600, tlong, 600, t1, "humpty dumpy sat on a wall");
+      jettys.get(1).getSolrClient().add("unloadcollection_shard1_replica2", doc1);
     }
 
     assertTrue(
@@ -343,11 +315,10 @@ public class UnloadDistributedZkTest extends AbstractFullDistribZkTestBase {
 
     // unload the leader again
     leader = getLeaderFromZk("unloadcollection", "shard1");
-    try (SolrClient collectionClient = newSolrClient(leader.getBaseUrl())) {
-
+    {
       Unload unloadCmd = new Unload(false);
       unloadCmd.setCoreName(leader.getCoreName());
-      collectionClient.request(unloadCmd);
+      getReplicaNodeClient(leader).request(unloadCmd);
     }
     tries = 50;
     while (leader
@@ -374,32 +345,27 @@ public class UnloadDistributedZkTest extends AbstractFullDistribZkTestBase {
 
     long found1, found3;
 
-    try (SolrClient adminClient =
-        newSolrClient((jettys.get(1).getBaseUrl() + "/unloadcollection_shard1_replica2"))) {
-      adminClient.commit();
-      SolrQuery q = new SolrQuery("*:*");
-      q.set("distrib", false);
-      found1 = adminClient.query(q).getResults().getNumFound();
+    var params = params("q", "*:*", "distrib", "false");
+
+    {
+      SolrClient adminClient = jettys.get(1).getSolrClient();
+      adminClient.commit("unloadcollection_shard1_replica2");
+      found1 =
+          adminClient.query("unloadcollection_shard1_replica2", params).getResults().getNumFound();
     }
 
-    try (SolrClient adminClient =
-        new HttpSolrClient.Builder(jettys.get(2).getBaseUrl().toString())
-            .withDefaultCollection("unloadcollection_shard1_replica3")
-            .withConnectionTimeout(15000, TimeUnit.MILLISECONDS)
-            .withSocketTimeout(30000, TimeUnit.MILLISECONDS)
-            .build()) {
-      adminClient.commit();
-      SolrQuery q = new SolrQuery("*:*");
-      q.set("distrib", false);
-      found3 = adminClient.query(q).getResults().getNumFound();
+    {
+      SolrClient adminClient = jettys.get(2).getSolrClient();
+      adminClient.commit("unloadcollection_shard1_replica3");
+      found3 =
+          adminClient.query("unloadcollection_shard1_replica3", params).getResults().getNumFound();
     }
 
-    try (SolrClient adminClient =
-        newSolrClient((jettys.get(3).getBaseUrl() + "/unloadcollection_shard1_replica4"))) {
-      adminClient.commit();
-      SolrQuery q = new SolrQuery("*:*");
-      q.set("distrib", false);
-      long found4 = adminClient.query(q).getResults().getNumFound();
+    {
+      SolrClient adminClient = jettys.get(3).getSolrClient();
+      adminClient.commit("unloadcollection_shard1_replica4");
+      long found4 =
+          adminClient.query("unloadcollection_shard1_replica4", params).getResults().getNumFound();
 
       // all 3 shards should now have the same number of docs
       assertEquals(found1, found3);
@@ -407,6 +373,14 @@ public class UnloadDistributedZkTest extends AbstractFullDistribZkTestBase {
     }
   }
 
+  private SolrClient getReplicaNodeClient(Replica replica) {
+    return jettys.stream()
+        .filter(j -> j.getBaseUrl().toString().equals(replica.getBaseUrl()))
+        .findAny()
+        .orElseThrow()
+        .getSolrClient();
+  }
+
   private void testUnloadLotsOfCores() throws Exception {
     JettySolrRunner jetty = jettys.get(0);
     int shards = TEST_NIGHTLY ? 2 : 1;
diff --git a/solr/core/src/test/org/apache/solr/cloud/api/collections/CollectionsAPIAsyncDistributedZkTest.java b/solr/core/src/test/org/apache/solr/cloud/api/collections/CollectionsAPIAsyncDistributedZkTest.java
index 6eae89d164f..b874181aded 100644
--- a/solr/core/src/test/org/apache/solr/cloud/api/collections/CollectionsAPIAsyncDistributedZkTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/api/collections/CollectionsAPIAsyncDistributedZkTest.java
@@ -30,7 +30,6 @@ import org.apache.lucene.tests.util.TestUtil;
 import org.apache.solr.client.solrj.RemoteSolrException;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrServerException;
-import org.apache.solr.client.solrj.apache.HttpSolrClient;
 import org.apache.solr.client.solrj.impl.CloudSolrClient;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
 import org.apache.solr.client.solrj.request.SolrQuery;
@@ -234,7 +233,7 @@ public class CollectionsAPIAsyncDistributedZkTest extends SolrCloudTestCase {
     SolrClient[] clients = new SolrClient[cluster.getJettySolrRunners().size()];
     int j = 0;
     for (JettySolrRunner r : cluster.getJettySolrRunners()) {
-      clients[j++] = new HttpSolrClient.Builder(r.getBaseUrl().toString()).build();
+      clients[j++] = r.getSolrClient(); // no need to close
     }
     RequestStatusState state =
         CollectionAdminRequest.createCollection("testAsyncIdRaceCondition", "conf1", 1, 1)
@@ -251,51 +250,41 @@ public class CollectionsAPIAsyncDistributedZkTest extends SolrCloudTestCase {
     ExecutorService es =
         ExecutorUtil.newMDCAwareFixedThreadPool(
             numThreads, new SolrNamedThreadFactory("testAsyncIdRaceCondition"));
-    try {
-      for (int i = 0; i < numThreads; i++) {
-        es.execute(
-            new Runnable() {
-
-              @Override
-              public void run() {
-                CollectionAdminRequest.Reload reloadCollectionRequest =
-                    CollectionAdminRequest.reloadCollection("testAsyncIdRaceCondition");
-                latch.countDown();
-                try {
-                  latch.await();
-                } catch (InterruptedException e) {
-                  throw new RuntimeException();
-                }
-
-                try {
-                  if (log.isInfoEnabled()) {
-                    log.info("{} - Reloading Collection.", Thread.currentThread().getName());
-                  }
-                  reloadCollectionRequest.processAsync(
-                      "repeatedId", clients[random().nextInt(clients.length)]);
-                  numSuccess.incrementAndGet();
-                } catch (SolrServerException | RemoteSolrException e) {
-                  if (log.isInfoEnabled()) {
-                    log.info("Exception during collection reloading, we were waiting for one: ", e);
-                  }
-                  assertThat(
-                      e.getMessage(),
-                      containsString("Task with the same requestid already exists. (repeatedId)"));
-                  numFailure.incrementAndGet();
-                } catch (IOException e) {
-                  throw new RuntimeException();
-                }
+    for (int i = 0; i < numThreads; i++) {
+      es.execute(
+          () -> {
+            CollectionAdminRequest.Reload reloadCollectionRequest =
+                CollectionAdminRequest.reloadCollection("testAsyncIdRaceCondition");
+            latch.countDown();
+            try {
+              latch.await();
+            } catch (InterruptedException e) {
+              throw new RuntimeException();
+            }
+
+            try {
+              if (log.isInfoEnabled()) {
+                log.info("{} - Reloading Collection.", Thread.currentThread().getName());
               }
-            });
-      }
-      es.shutdown();
-      assertTrue(es.awaitTermination(10, TimeUnit.SECONDS));
-      assertEquals(1, numSuccess.get());
-      assertEquals(numThreads - 1, numFailure.get());
-    } finally {
-      for (SolrClient client : clients) {
-        client.close();
-      }
+              reloadCollectionRequest.processAsync(
+                  "repeatedId", clients[random().nextInt(clients.length)]);
+              numSuccess.incrementAndGet();
+            } catch (SolrServerException | RemoteSolrException e) {
+              if (log.isInfoEnabled()) {
+                log.info("Exception during collection reloading, we were waiting for one: ", e);
+              }
+              assertThat(
+                  e.getMessage(),
+                  containsString("Task with the same requestid already exists. (repeatedId)"));
+              numFailure.incrementAndGet();
+            } catch (IOException e) {
+              throw new RuntimeException();
+            }
+          });
     }
+    es.shutdown();
+    assertTrue(es.awaitTermination(10, TimeUnit.SECONDS));
+    assertEquals(1, numSuccess.get());
+    assertEquals(numThreads - 1, numFailure.get());
   }
 }
diff --git a/solr/core/src/test/org/apache/solr/cloud/api/collections/ShardSplitTest.java b/solr/core/src/test/org/apache/solr/cloud/api/collections/ShardSplitTest.java
index a96e0573dd6..b52177d006f 100644
--- a/solr/core/src/test/org/apache/solr/cloud/api/collections/ShardSplitTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/api/collections/ShardSplitTest.java
@@ -40,9 +40,8 @@ import java.util.concurrent.atomic.AtomicReference;
 import org.apache.solr.client.solrj.RemoteSolrException;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrServerException;
-import org.apache.solr.client.solrj.apache.CloudLegacySolrClient;
-import org.apache.solr.client.solrj.apache.HttpSolrClient;
 import org.apache.solr.client.solrj.impl.CloudSolrClient;
+import org.apache.solr.client.solrj.jetty.CloudJettySolrClient;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
 import org.apache.solr.client.solrj.request.QueryRequest;
 import org.apache.solr.client.solrj.request.SolrQuery;
@@ -158,7 +157,7 @@ public class ShardSplitTest extends BasicDistributedZkTest {
         builder
             .withDefaultCollection(collectionName)
             .sendUpdatesOnlyToShardLeaders()
-            .withHttpClient(((CloudLegacySolrClient) cloudClient).getHttpClient())
+            .withHttpClient(((CloudJettySolrClient) cloudClient).getHttpClient())
             .build()) {
       StoppableIndexingThread thread =
           new StoppableIndexingThread(controlClient, client, "i1", true);
@@ -233,19 +232,7 @@ public class ShardSplitTest extends BasicDistributedZkTest {
               CollectionAdminRequest.addReplicaToShard(collectionName, SHARD1_0);
           // use control client because there are fewer chances of it being the node being restarted
           // this is to avoid flakiness of test because of NoHttpResponseExceptions
-          String control_collection =
-              client
-                  .getClusterState()
-                  .getCollection("control_collection")
-                  .getReplicas()
-                  .get(0)
-                  .getBaseUrl();
-          try (var control =
-              new HttpSolrClient.Builder(control_collection)
-                  .withHttpClient(((CloudLegacySolrClient) client).getHttpClient())
-                  .build()) {
-            state = addReplica.processAndWait(control, 30);
-          }
+          state = addReplica.processAndWait(controlClient, 30);
 
           ZkStateReader.from(cloudClient)
               .waitForState(
@@ -306,11 +293,12 @@ public class ShardSplitTest extends BasicDistributedZkTest {
     int count = 0;
     for (Replica replica : shard.getReplicas()) {
       var client =
-          new HttpSolrClient.Builder(replica.getBaseUrl())
-              .withDefaultCollection(replica.getCoreName())
-              .withHttpClient(((CloudLegacySolrClient) cloudClient).getHttpClient())
-              .build();
-      QueryResponse response = client.query(new SolrQuery("q", "*:*", "distrib", "false"));
+          clients.stream()
+              .filter(c -> c.getBaseURL().equals(replica.getBaseUrl()))
+              .findAny()
+              .orElseThrow();
+      QueryResponse response =
+          client.query(replica.getCoreName(), new SolrQuery("q", "*:*", "distrib", "false"));
       if (log.isInfoEnabled()) {
         log.info(
             "Found numFound={} on replica: {}",
@@ -1285,13 +1273,8 @@ public class ShardSplitTest extends BasicDistributedZkTest {
     QueryRequest request = new QueryRequest(params);
     request.setPath("/admin/collections");
 
-    String baseUrl = shardToJetty.get(SHARD1).get(0).jetty.getBaseUrl().toString();
-
-    try (SolrClient baseServer =
-        new HttpSolrClient.Builder(baseUrl)
-            .withConnectionTimeout(30, TimeUnit.SECONDS)
-            .withSocketTimeout(5, TimeUnit.MINUTES)
-            .build()) {
+    JettySolrRunner jetty = shardToJetty.get(SHARD1).getFirst().jetty;
+    try (SolrClient baseServer = jetty.newClient(30_000, 300_000)) {
       NamedList<Object> rsp = baseServer.request(request);
       if (log.isInfoEnabled()) {
         log.info("Shard split response: {}", Utils.toJSONString(rsp));
diff --git a/solr/core/src/test/org/apache/solr/cloud/api/collections/TestRequestStatusCollectionAPI.java b/solr/core/src/test/org/apache/solr/cloud/api/collections/TestRequestStatusCollectionAPI.java
index b48ae59b668..5f93ef71768 100644
--- a/solr/core/src/test/org/apache/solr/cloud/api/collections/TestRequestStatusCollectionAPI.java
+++ b/solr/core/src/test/org/apache/solr/cloud/api/collections/TestRequestStatusCollectionAPI.java
@@ -23,11 +23,8 @@ import java.lang.invoke.MethodHandles;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
-import java.util.concurrent.TimeUnit;
 import org.apache.solr.client.solrj.RemoteSolrException;
-import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrServerException;
-import org.apache.solr.client.solrj.apache.HttpSolrClient;
 import org.apache.solr.client.solrj.request.QueryRequest;
 import org.apache.solr.client.solrj.response.RequestStatusState;
 import org.apache.solr.cloud.BasicDistributedZkTest;
@@ -231,13 +228,6 @@ public class TestRequestStatusCollectionAPI extends BasicDistributedZkTest {
     QueryRequest request = new QueryRequest(params);
     request.setPath("/admin/collections");
 
-    String baseUrl = shardToJetty.get(SHARD1).get(0).jetty.getBaseUrl().toString();
-
-    try (SolrClient baseServer =
-        new HttpSolrClient.Builder(baseUrl)
-            .withConnectionTimeout(15000, TimeUnit.MILLISECONDS)
-            .build()) {
-      return baseServer.request(request);
-    }
+    return shardToJetty.get(SHARD1).getFirst().jetty.getSolrClient().request(request);
   }
 }
diff --git a/solr/core/src/test/org/apache/solr/handler/TestReplicationHandlerBackup.java b/solr/core/src/test/org/apache/solr/handler/TestReplicationHandlerBackup.java
index c325a0ffebf..361d7e07d46 100644
--- a/solr/core/src/test/org/apache/solr/handler/TestReplicationHandlerBackup.java
+++ b/solr/core/src/test/org/apache/solr/handler/TestReplicationHandlerBackup.java
@@ -28,7 +28,6 @@ import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Properties;
-import java.util.concurrent.TimeUnit;
 import org.apache.lucene.index.DirectoryReader;
 import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.search.IndexSearcher;
@@ -40,7 +39,6 @@ import org.apache.lucene.tests.util.LuceneTestCase;
 import org.apache.lucene.tests.util.TestUtil;
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.client.solrj.SolrClient;
-import org.apache.solr.client.solrj.apache.HttpSolrClient;
 import org.apache.solr.embedded.JettyConfig;
 import org.apache.solr.embedded.JettySolrRunner;
 import org.junit.After;
@@ -74,13 +72,6 @@ public class TestReplicationHandlerBackup extends SolrTestCaseJ4 {
     return jetty;
   }
 
-  private static SolrClient createNewSolrClient(JettySolrRunner jetty) {
-    return new HttpSolrClient.Builder(jetty.getBaseUrl().toString())
-        .withConnectionTimeout(15000, TimeUnit.MILLISECONDS)
-        .withSocketTimeout(60000, TimeUnit.MILLISECONDS)
-        .build();
-  }
-
   @Override
   @Before
   public void setUp() throws Exception {
@@ -97,7 +88,7 @@ public class TestReplicationHandlerBackup extends SolrTestCaseJ4 {
     leader.copyConfigFile(CONF_DIR.resolve(configFile).toString(), "solrconfig.xml");
 
     leaderJetty = createAndStartJetty(leader);
-    leaderClient = createNewSolrClient(leaderJetty);
+    leaderClient = leaderJetty.getSolrClient();
     docsSeed = random().nextLong();
   }
 
diff --git a/solr/core/src/test/org/apache/solr/handler/TestRestoreCore.java b/solr/core/src/test/org/apache/solr/handler/TestRestoreCore.java
index deaaddfac89..a2f79f7f3b1 100644
--- a/solr/core/src/test/org/apache/solr/handler/TestRestoreCore.java
+++ b/solr/core/src/test/org/apache/solr/handler/TestRestoreCore.java
@@ -23,13 +23,11 @@ import java.nio.file.DirectoryStream;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.Properties;
-import java.util.concurrent.TimeUnit;
 import org.apache.lucene.index.IndexFileNames;
 import org.apache.lucene.tests.util.LuceneTestCase;
 import org.apache.lucene.tests.util.TestUtil;
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.client.solrj.SolrClient;
-import org.apache.solr.client.solrj.apache.HttpSolrClient;
 import org.apache.solr.common.SolrInputDocument;
 import org.apache.solr.embedded.JettyConfig;
 import org.apache.solr.embedded.JettySolrRunner;
@@ -59,15 +57,6 @@ public class TestRestoreCore extends SolrTestCaseJ4 {
     return jetty;
   }
 
-  private static SolrClient createNewSolrClient(int port) {
-
-    final String baseUrl = buildUrl(port);
-    return new HttpSolrClient.Builder(baseUrl)
-        .withConnectionTimeout(15000, TimeUnit.MILLISECONDS)
-        .withSocketTimeout(60000, TimeUnit.MILLISECONDS)
-        .build();
-  }
-
   @Override
   @Before
   public void setUp() throws Exception {
@@ -79,7 +68,7 @@ public class TestRestoreCore extends SolrTestCaseJ4 {
     leader.copyConfigFile(CONF_DIR.resolve(configFile).toString(), "solrconfig.xml");
 
     leaderJetty = createAndStartJetty(leader);
-    leaderClient = createNewSolrClient(leaderJetty.getLocalPort());
+    leaderClient = leaderJetty.getSolrClient();
     docsSeed = random().nextLong();
   }
 
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/CoreAdminHandlerTest.java b/solr/core/src/test/org/apache/solr/handler/admin/CoreAdminHandlerTest.java
index db7883a0a1e..66627c22583 100644
--- a/solr/core/src/test/org/apache/solr/handler/admin/CoreAdminHandlerTest.java
+++ b/solr/core/src/test/org/apache/solr/handler/admin/CoreAdminHandlerTest.java
@@ -27,7 +27,6 @@ import java.nio.file.StandardCopyOption;
 import java.util.Map;
 import java.util.Properties;
 import java.util.Set;
-import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicLong;
 import org.apache.commons.io.file.PathUtils;
 import org.apache.lucene.util.Constants;
@@ -35,7 +34,6 @@ import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.client.api.model.CoreStatusResponse;
 import org.apache.solr.client.solrj.RemoteSolrException;
 import org.apache.solr.client.solrj.SolrClient;
-import org.apache.solr.client.solrj.apache.HttpSolrClient;
 import org.apache.solr.client.solrj.request.CoreAdminRequest;
 import org.apache.solr.client.solrj.request.SolrQuery;
 import org.apache.solr.client.solrj.request.json.JacksonContentWriter;
@@ -315,23 +313,11 @@ public class CoreAdminHandlerTest extends SolrTestCaseJ4 {
             solrHomeDirectory.toAbsolutePath().toString(), JettyConfig.builder().build());
     runner.start();
 
-    try (SolrClient client =
-        new HttpSolrClient.Builder(runner.getBaseUrl().toString())
-            .withDefaultCollection("corex")
-            .withConnectionTimeout(DEFAULT_CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)
-            .withSocketTimeout(DEFAULT_CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)
-            .build()) {
-      SolrInputDocument doc = new SolrInputDocument();
-      doc.addField("id", "123");
-      client.add(doc);
-      client.commit();
-    }
+    var client = runner.getSolrClient();
+    {
+      client.add("corex", sdoc("id", "123"));
+      client.commit("corex");
 
-    try (SolrClient client =
-        new HttpSolrClient.Builder(runner.getBaseUrl().toString())
-            .withConnectionTimeout(DEFAULT_CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)
-            .withSocketTimeout(DEFAULT_CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)
-            .build()) {
       CoreAdminRequest.Unload req = new CoreAdminRequest.Unload(false);
       req.setDeleteInstanceDir(true);
       req.setCoreName("corex");
@@ -342,11 +328,7 @@ public class CoreAdminHandlerTest extends SolrTestCaseJ4 {
     // 1> has the property persisted (SOLR-11783)
     // 2> is deleted after rename properly.
 
-    try (SolrClient client =
-        new HttpSolrClient.Builder(runner.getBaseUrl().toString())
-            .withConnectionTimeout(DEFAULT_CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)
-            .withSocketTimeout(DEFAULT_CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)
-            .build()) {
+    {
       CoreAdminRequest.renameCore("corerename", "brand_new_core_name", client);
       Properties props = new Properties();
       try (Reader is = Files.newBufferedReader(renamePropFile, StandardCharsets.UTF_8)) {
@@ -356,11 +338,7 @@ public class CoreAdminHandlerTest extends SolrTestCaseJ4 {
           "Name should have been persisted!", "brand_new_core_name", props.getProperty("name"));
     }
 
-    try (SolrClient client =
-        new HttpSolrClient.Builder(runner.getBaseUrl().toString())
-            .withConnectionTimeout(DEFAULT_CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)
-            .withSocketTimeout(DEFAULT_CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)
-            .build()) {
+    {
       CoreAdminRequest.Unload req = new CoreAdminRequest.Unload(false);
       req.setDeleteInstanceDir(true);
       req.setCoreName("brand_new_core_name");
@@ -389,33 +367,18 @@ public class CoreAdminHandlerTest extends SolrTestCaseJ4 {
             solrHomeDirectory.toAbsolutePath().toString(), JettyConfig.builder().build());
     runner.start();
 
-    try (SolrClient client =
-        new HttpSolrClient.Builder(runner.getBaseUrl().toString())
-            .withDefaultCollection("corex")
-            .withConnectionTimeout(DEFAULT_CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)
-            .withSocketTimeout(DEFAULT_CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)
-            .build()) {
-      SolrInputDocument doc = new SolrInputDocument();
-      doc.addField("id", "123");
-      client.add(doc);
-      client.commit();
+    var client = runner.getSolrClient();
+    {
+      client.add("corex", sdoc("id", "123"));
+      client.commit("corex");
     }
 
-    try (SolrClient client =
-        new HttpSolrClient.Builder(runner.getBaseUrl().toString())
-            .withDefaultCollection("corex")
-            .withConnectionTimeout(DEFAULT_CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)
-            .withSocketTimeout(DEFAULT_CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)
-            .build()) {
-      QueryResponse result = client.query(new SolrQuery("id:*"));
+    {
+      QueryResponse result = client.query("corex", new SolrQuery("id:*"));
       assertEquals(1, result.getResults().getNumFound());
     }
 
-    try (SolrClient client =
-        new HttpSolrClient.Builder(runner.getBaseUrl().toString())
-            .withConnectionTimeout(DEFAULT_CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)
-            .withSocketTimeout(DEFAULT_CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)
-            .build()) {
+    {
       CoreAdminRequest.Unload req = new CoreAdminRequest.Unload(false);
       req.setDeleteInstanceDir(false); // random().nextBoolean());
       req.setCoreName("corex");
@@ -426,13 +389,8 @@ public class CoreAdminHandlerTest extends SolrTestCaseJ4 {
         expectThrows(
             RemoteSolrException.class,
             () -> {
-              try (SolrClient client =
-                  new HttpSolrClient.Builder(runner.getBaseUrl().toString())
-                      .withDefaultCollection("corex")
-                      .withConnectionTimeout(DEFAULT_CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)
-                      .withSocketTimeout(DEFAULT_CONNECTION_TIMEOUT * 1000, TimeUnit.MILLISECONDS)
-                      .build()) {
-                client.query(new SolrQuery("id:*"));
+              try {
+                runner.getSolrClient().query("corex", new SolrQuery("id:*"));
               } finally {
                 runner.stop();
               }
@@ -454,17 +412,10 @@ public class CoreAdminHandlerTest extends SolrTestCaseJ4 {
             solrHomeDirectory.toAbsolutePath().toString(), JettyConfig.builder().build());
     runner.start();
 
-    try (SolrClient client =
-        new HttpSolrClient.Builder(runner.getBaseUrl().toString())
-            .withDefaultCollection("corex")
-            .withConnectionTimeout(DEFAULT_CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)
-            .withSocketTimeout(DEFAULT_CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)
-            .build()) {
-      SolrInputDocument doc = new SolrInputDocument();
-      doc.addField("id", "123");
-      client.add(doc);
-      client.commit();
-    }
+    SolrInputDocument doc = new SolrInputDocument();
+    doc.addField("id", "123");
+    runner.getSolrClient().add("corex", doc);
+    runner.getSolrClient().commit("corex");
 
     Path dataDir = null;
     try (SolrClient client = getHttpSolrClient(runner.getBaseUrl().toString())) {
@@ -481,21 +432,16 @@ public class CoreAdminHandlerTest extends SolrTestCaseJ4 {
         subHome.resolve("solrconfig.xml"),
         StandardCopyOption.REPLACE_EXISTING);
 
-    try (SolrClient client =
-        new HttpSolrClient.Builder(runner.getBaseUrl().toString())
-            .withConnectionTimeout(DEFAULT_CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)
-            .withSocketTimeout(DEFAULT_CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)
-            .build()) {
-      // this is expected because we put a bad solrconfig -- ignore
-      expectThrows(Exception.class, () -> CoreAdminRequest.reloadCore("corex", client));
+    // this is expected because we put a bad solrconfig -- ignore
+    expectThrows(
+        Exception.class, () -> CoreAdminRequest.reloadCore("corex", runner.getSolrClient()));
 
-      CoreAdminRequest.Unload req = new CoreAdminRequest.Unload(false);
-      req.setDeleteDataDir(true);
-      // important because the data directory is inside the instance directory
-      req.setDeleteInstanceDir(false);
-      req.setCoreName("corex");
-      req.process(client);
-    }
+    CoreAdminRequest.Unload unloadReq = new CoreAdminRequest.Unload(false);
+    unloadReq.setDeleteDataDir(true);
+    // important because the data directory is inside the instance directory
+    unloadReq.setDeleteInstanceDir(false);
+    unloadReq.setCoreName("corex");
+    unloadReq.process(runner.getSolrClient());
 
     runner.stop();
 
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/ZookeeperReadAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/ZookeeperReadAPITest.java
index ed5bd6353dd..f4c558de467 100644
--- a/solr/core/src/test/org/apache/solr/handler/admin/ZookeeperReadAPITest.java
+++ b/solr/core/src/test/org/apache/solr/handler/admin/ZookeeperReadAPITest.java
@@ -19,19 +19,16 @@ package org.apache.solr.handler.admin;
 
 import static org.hamcrest.Matchers.containsInAnyOrder;
 
-import java.net.URL;
 import java.nio.charset.StandardCharsets;
 import java.util.Map;
 import java.util.stream.Collectors;
 import org.apache.commons.io.IOUtils;
 import org.apache.solr.client.api.model.ZooKeeperStat;
-import org.apache.solr.client.solrj.apache.HttpSolrClient;
 import org.apache.solr.client.solrj.request.ZookeeperReadApi;
 import org.apache.solr.cloud.SolrCloudTestCase;
 import org.apache.solr.common.SolrException;
 import org.apache.zookeeper.CreateMode;
 import org.junit.After;
-import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
@@ -42,22 +39,6 @@ public class ZookeeperReadAPITest extends SolrCloudTestCase {
     configureCluster(1).addConfig("conf", configset("cloud-minimal")).configure();
   }
 
-  private URL baseUrl;
-  private String basezk;
-  private String basezkls;
-
-  @Before
-  @Override
-  public void setUp() throws Exception {
-    super.setUp();
-
-    baseUrl = cluster.getJettySolrRunner(0).getBaseUrl();
-
-    String baseUrlV2 = cluster.getJettySolrRunner(0).getBaseURLV2().toString();
-    basezk = baseUrlV2 + "/cluster/zookeeper/data";
-    basezkls = baseUrlV2 + "/cluster/zookeeper/children";
-  }
-
   @After
   @Override
   public void tearDown() throws Exception {
@@ -66,96 +47,93 @@ public class ZookeeperReadAPITest extends SolrCloudTestCase {
 
   @Test
   public void testZkread() throws Exception {
-    try (HttpSolrClient client = new HttpSolrClient.Builder(baseUrl.toString()).build()) {
-      final var securityJsonRequest = new ZookeeperReadApi.ReadNode("/security.json");
-      final var securityJsonResponse = securityJsonRequest.process(client);
-      assertEquals(200, securityJsonResponse.getHttpStatus());
-      try (final var stream = securityJsonResponse.getResponseStream()) {
-        final var securityJsonContent = IOUtils.toString(stream, StandardCharsets.UTF_8);
-        assertNotNull(securityJsonContent);
-      }
-
-      final var configListRequest = new ZookeeperReadApi.ListNodes("/configs");
-      final var configListResponse = configListRequest.process(client);
-      assertEquals(
-          6, configListResponse.unknownProperties().get("/configs").get("_default").children);
-      assertEquals(2, configListResponse.unknownProperties().get("/configs").get("conf").children);
-      assertEquals(0, configListResponse.stat.version);
-
-      final var configDataRequest = new ZookeeperReadApi.ReadNode("/configs");
-      final var configDataResponse = configDataRequest.process(client);
-      // /configs exists but has no data, so API returns '200 OK' with empty response body
-      assertEquals(200, configDataResponse.getHttpStatus());
-      try (final var stream = configDataResponse.getResponseStream()) {
-        assertEquals("", IOUtils.toString(stream, StandardCharsets.UTF_8));
-      }
+    var client = cluster.getJettySolrRunner(0).getSolrClient();
+    final var securityJsonRequest = new ZookeeperReadApi.ReadNode("/security.json");
+    final var securityJsonResponse = securityJsonRequest.process(client);
+    assertEquals(200, securityJsonResponse.getHttpStatus());
+    try (final var stream = securityJsonResponse.getResponseStream()) {
+      final var securityJsonContent = IOUtils.toString(stream, StandardCharsets.UTF_8);
+      assertNotNull(securityJsonContent);
+    }
 
-      byte[] bytes = new byte[1024 * 5];
-      for (int i = 0; i < bytes.length; i++) {
-        bytes[i] = (byte) random().nextInt(128);
-      }
-      try {
-        cluster.getZkClient().create("/configs/_default/testdata", bytes, CreateMode.PERSISTENT);
+    final var configListRequest = new ZookeeperReadApi.ListNodes("/configs");
+    final var configListResponse = configListRequest.process(client);
+    assertEquals(
+        6, configListResponse.unknownProperties().get("/configs").get("_default").children);
+    assertEquals(2, configListResponse.unknownProperties().get("/configs").get("conf").children);
+    assertEquals(0, configListResponse.stat.version);
+
+    final var configDataRequest = new ZookeeperReadApi.ReadNode("/configs");
+    final var configDataResponse = configDataRequest.process(client);
+    // /configs exists but has no data, so API returns '200 OK' with empty response body
+    assertEquals(200, configDataResponse.getHttpStatus());
+    try (final var stream = configDataResponse.getResponseStream()) {
+      assertEquals("", IOUtils.toString(stream, StandardCharsets.UTF_8));
+    }
 
-        final var testDataRequest = new ZookeeperReadApi.ReadNode("/configs/_default/testdata");
-        final var testDataResponse = testDataRequest.process(client);
-        assertEquals(200, testDataResponse.getHttpStatus());
-        try (final var stream = testDataResponse.getResponseStream()) {
-          final var foundContents = stream.readAllBytes();
-          for (int i = 0; i < foundContents.length; i++) {
-            assertEquals(foundContents[i], bytes[i]);
-          }
+    byte[] bytes = new byte[1024 * 5];
+    for (int i = 0; i < bytes.length; i++) {
+      bytes[i] = (byte) random().nextInt(128);
+    }
+    try {
+      cluster.getZkClient().create("/configs/_default/testdata", bytes, CreateMode.PERSISTENT);
+
+      final var testDataRequest = new ZookeeperReadApi.ReadNode("/configs/_default/testdata");
+      final var testDataResponse = testDataRequest.process(client);
+      assertEquals(200, testDataResponse.getHttpStatus());
+      try (final var stream = testDataResponse.getResponseStream()) {
+        final var foundContents = stream.readAllBytes();
+        for (int i = 0; i < foundContents.length; i++) {
+          assertEquals(foundContents[i], bytes[i]);
         }
-      } finally {
-        cluster.getZkClient().delete("/configs/_default/testdata", -1);
       }
+    } finally {
+      cluster.getZkClient().delete("/configs/_default/testdata", -1);
     }
   }
 
   @Test
   public void testRequestingDataFromNonexistentNodeReturnsAnError() throws Exception {
-    try (HttpSolrClient client = new HttpSolrClient.Builder(baseUrl.toString()).build()) {
-      final var missingNodeReq = new ZookeeperReadApi.ReadNode("/configs/_default/nonexistentnode");
-      final var missingNodeResponse = missingNodeReq.process(client);
-      assertEquals(404, missingNodeResponse.getHttpStatus());
-
-      final var expected =
-          expectThrows(
-              SolrException.class, () -> missingNodeResponse.getResponseStreamIfSuccessful());
-      assertEquals(404, expected.code());
-    }
+    var client = cluster.getJettySolrRunner(0).getSolrClient();
+    final var missingNodeReq = new ZookeeperReadApi.ReadNode("/configs/_default/nonexistentnode");
+    final var missingNodeResponse = missingNodeReq.process(client);
+    assertEquals(404, missingNodeResponse.getHttpStatus());
+
+    final var expected =
+        expectThrows(
+            SolrException.class, () -> missingNodeResponse.getResponseStreamIfSuccessful());
+    assertEquals(404, expected.code());
   }
 
   @Test
   public void testCanListChildNodes() throws Exception {
-    try (HttpSolrClient client = new HttpSolrClient.Builder(baseUrl.toString()).build()) {
-      final var listDefaultFilesReq = new ZookeeperReadApi.ListNodes("/configs/_default");
-      final var listDefaultFilesResponse = listDefaultFilesReq.process(client);
-
-      // At the top level, the response contains a key with the value of the specified zkPath
-      assertEquals(1, listDefaultFilesResponse.unknownProperties().size());
-      assertEquals(
-          "/configs/_default",
-          listDefaultFilesResponse.unknownProperties().keySet().stream()
-              .collect(Collectors.toList())
-              .get(0));
-
-      // Under the specified zkPath is a key for each child, with values being that stat for that
-      // node.
-      // The actual stat values vary a good bit so aren't very useful to assert on, so let's just
-      // make sure all of the expected child nodes were found.
-      final Map<String, ZooKeeperStat> childStatsByPath =
-          listDefaultFilesResponse.unknownProperties().get("/configs/_default");
-      assertEquals(6, childStatsByPath.size());
-      assertThat(
-          childStatsByPath.keySet(),
-          containsInAnyOrder(
-              "protwords.txt",
-              "solrconfig.xml",
-              "synonyms.txt",
-              "stopwords.txt",
-              "managed-schema.xml",
-              "lang"));
-    }
+    var client = cluster.getJettySolrRunner(0).getSolrClient();
+    final var listDefaultFilesReq = new ZookeeperReadApi.ListNodes("/configs/_default");
+    final var listDefaultFilesResponse = listDefaultFilesReq.process(client);
+
+    // At the top level, the response contains a key with the value of the specified zkPath
+    assertEquals(1, listDefaultFilesResponse.unknownProperties().size());
+    assertEquals(
+        "/configs/_default",
+        listDefaultFilesResponse.unknownProperties().keySet().stream()
+            .collect(Collectors.toList())
+            .get(0));
+
+    // Under the specified zkPath is a key for each child, with values being that stat for that
+    // node.
+    // The actual stat values vary a good bit so aren't very useful to assert on, so let's just
+    // make sure all of the expected child nodes were found.
+    final Map<String, ZooKeeperStat> childStatsByPath =
+        listDefaultFilesResponse.unknownProperties().get("/configs/_default");
+    assertEquals(6, childStatsByPath.size());
+    assertThat(
+        childStatsByPath.keySet(),
+        containsInAnyOrder(
+            "protwords.txt",
+            "solrconfig.xml",
+            "synonyms.txt",
+            "stopwords.txt",
+            "managed-schema.xml",
+            "lang"));
   }
 }
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 b73f7cfab9d..6b48759e734 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
@@ -18,15 +18,10 @@
 package org.apache.solr.handler.admin;
 
 import java.io.IOException;
-import java.net.URL;
 import java.util.List;
 import java.util.Map;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
-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.apache.HttpSolrClient;
 import org.apache.solr.client.solrj.request.GenericSolrRequest;
 import org.apache.solr.client.solrj.response.json.JsonMapResponseParser;
 import org.apache.solr.cloud.SolrCloudTestCase;
@@ -42,22 +37,15 @@ public class ZookeeperStatusHandlerFailureTest extends SolrCloudTestCase {
     cluster.getZkServer().shutdown();
   }
 
-  /*
-   Test the monitoring endpoint, when no Zookeeper is answering. There should still be a response
-  */
+  /**
+   * Test the monitoring endpoint, when no Zookeeper is answering. There should still be a response
+   */
   @Test
-  public void monitorZookeeperAfterZkShutdown()
-      throws IOException,
-          SolrServerException,
-          InterruptedException,
-          ExecutionException,
-          TimeoutException {
-    URL baseUrl = cluster.getJettySolrRunner(0).getBaseUrl();
-    HttpSolrClient solr = new HttpSolrClient.Builder(baseUrl.toString()).build();
+  public void monitorZookeeperAfterZkShutdown() throws IOException, SolrServerException {
     GenericSolrRequest mntrReq =
         new GenericSolrRequest(SolrRequest.METHOD.GET, "/admin/zookeeper/status");
     mntrReq.setResponseParser(new JsonMapResponseParser());
-    NamedList<Object> nl = solr.httpUriRequest(mntrReq).future.get(10000, TimeUnit.MILLISECONDS);
+    NamedList<Object> nl = cluster.getJettySolrRunner(0).getSolrClient().request(mntrReq);
 
     assertEquals("zkStatus", nl.getName(1));
     @SuppressWarnings({"unchecked"})
@@ -71,6 +59,5 @@ public class ZookeeperStatusHandlerFailureTest extends SolrCloudTestCase {
     @SuppressWarnings({"unchecked"})
     Map<String, Object> details = (Map<String, Object>) detailsList.get(0);
     assertEquals(false, details.get("ok"));
-    solr.close();
   }
 }
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 a180edacfbe..8e6735ef966 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
@@ -23,24 +23,17 @@ import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 import java.io.IOException;
-import java.net.URL;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
-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.apache.HttpSolrClient;
 import org.apache.solr.client.solrj.request.GenericSolrRequest;
 import org.apache.solr.client.solrj.response.json.JsonMapResponseParser;
 import org.apache.solr.cloud.SolrCloudTestCase;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.cloud.ZkDynamicConfig;
 import org.apache.solr.common.util.NamedList;
-import org.junit.After;
-import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.mockito.Answers;
@@ -53,35 +46,17 @@ public class ZookeeperStatusHandlerTest extends SolrCloudTestCase {
     configureCluster(1).addConfig("conf", configset("cloud-minimal")).configure();
   }
 
-  @Before
-  @Override
-  public void setUp() throws Exception {
-    super.setUp();
-  }
-
-  @After
-  @Override
-  public void tearDown() throws Exception {
-    super.tearDown();
-  }
-
   /*
    Test the monitoring endpoint, used in the Cloud => ZkStatus Admin UI screen
    NOTE: We do not currently test with multiple zookeepers, but the only difference is that there are multiple "details" objects and mode is "ensemble"...
   */
   @Test
-  public void monitorZookeeper()
-      throws IOException,
-          SolrServerException,
-          InterruptedException,
-          ExecutionException,
-          TimeoutException {
-    URL baseUrl = cluster.getJettySolrRunner(0).getBaseUrl();
-    HttpSolrClient solr = new HttpSolrClient.Builder(baseUrl.toString()).build();
+  public void monitorZookeeper() throws IOException, SolrServerException {
+    var solr = cluster.getJettySolrRunner(0).getSolrClient();
     GenericSolrRequest mntrReq =
         new GenericSolrRequest(SolrRequest.METHOD.GET, "/admin/zookeeper/status");
     mntrReq.setResponseParser(new JsonMapResponseParser());
-    NamedList<Object> nl = solr.httpUriRequest(mntrReq).future.get(10000, TimeUnit.MILLISECONDS);
+    NamedList<Object> nl = solr.request(mntrReq);
 
     assertEquals("zkStatus", nl.getName(1));
     @SuppressWarnings({"unchecked"})
@@ -96,7 +71,6 @@ public class ZookeeperStatusHandlerTest extends SolrCloudTestCase {
     Map<String, Object> details = (Map<String, Object>) detailsList.get(0);
     assertEquals(true, details.get("ok"));
     assertTrue(Integer.parseInt((String) details.get("zk_znode_count")) > 50);
-    solr.close();
   }
 
   @Test
diff --git a/solr/core/src/test/org/apache/solr/handler/component/DistributedCombinedQueryComponentTest.java b/solr/core/src/test/org/apache/solr/handler/component/DistributedCombinedQueryComponentTest.java
index 28fe45eaa07..17423bba672 100644
--- a/solr/core/src/test/org/apache/solr/handler/component/DistributedCombinedQueryComponentTest.java
+++ b/solr/core/src/test/org/apache/solr/handler/component/DistributedCombinedQueryComponentTest.java
@@ -22,8 +22,6 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import org.apache.solr.BaseDistributedSearchTestCase;
-import org.apache.solr.client.solrj.apache.HttpSolrClient;
-import org.apache.solr.client.solrj.jetty.HttpJettySolrClient;
 import org.apache.solr.client.solrj.response.QueryResponse;
 import org.apache.solr.common.SolrInputDocument;
 import org.apache.solr.common.params.CommonParams;
@@ -94,14 +92,11 @@ public class DistributedCombinedQueryComponentTest extends BaseDistributedSearch
     clients.sort(
         (client1, client2) -> {
           try {
-            if (client2 instanceof HttpJettySolrClient httpClient2
-                && client1 instanceof HttpSolrClient httpClient1)
-              return new URI(httpClient1.getBaseURL()).getPort()
-                  - new URI(httpClient2.getBaseURL()).getPort();
+            return new URI(client1.getBaseURL()).getPort()
+                - new URI(client2.getBaseURL()).getPort();
           } catch (URISyntaxException e) {
             throw new RuntimeException("Unable to get URI from SolrClient", e);
           }
-          return 0;
         });
     for (SolrInputDocument doc : docs) {
       indexDoc(doc);
diff --git a/solr/core/src/test/org/apache/solr/handler/component/DistributedQueryElevationComponentTest.java b/solr/core/src/test/org/apache/solr/handler/component/DistributedQueryElevationComponentTest.java
index 2c66147212e..6a794b8d768 100644
--- a/solr/core/src/test/org/apache/solr/handler/component/DistributedQueryElevationComponentTest.java
+++ b/solr/core/src/test/org/apache/solr/handler/component/DistributedQueryElevationComponentTest.java
@@ -18,8 +18,6 @@ package org.apache.solr.handler.component;
 
 import org.apache.lucene.util.Constants;
 import org.apache.solr.BaseDistributedSearchTestCase;
-import org.apache.solr.client.solrj.SolrClient;
-import org.apache.solr.client.solrj.apache.HttpSolrClient;
 import org.apache.solr.client.solrj.request.SolrQuery;
 import org.apache.solr.client.solrj.response.JavaBinResponseParser;
 import org.apache.solr.client.solrj.response.QueryResponse;
@@ -165,26 +163,20 @@ public class DistributedQueryElevationComponentTest extends BaseDistributedSearc
     assertEquals("6", document.getFieldValue("id"));
     assertEquals(true, document.getFieldValue("[elevated]"));
 
-    // Force javabin format
-    final String clientUrl = jettys.get(0).getBaseUrl().toString();
-    try (SolrClient client =
-        new HttpSolrClient.Builder(clientUrl)
-            .withDefaultCollection("collection1")
-            .withResponseParser(new JavaBinResponseParser())
-            .build(); ) {
-      SolrQuery solrQuery =
-          new SolrQuery("XXXX")
-              .setParam("qt", "/elevate")
-              .setParam("shards.qt", "/elevate")
-              .setRows(500)
-              .setFields("id,[elevated]")
-              .setParam("enableElevation", "true")
-              .setParam("forceElevation", "true")
-              .setParam("elevateIds", "6", "wt", "javabin")
-              .setSort("id", SolrQuery.ORDER.desc);
-      setDistributedParams(solrQuery);
-      response = client.query(solrQuery);
-    }
+    var client = jettys.getFirst().getSolrClient();
+    assertTrue(client.getParser() instanceof JavaBinResponseParser);
+    SolrQuery solrQuery =
+        new SolrQuery("XXXX")
+            .setParam("qt", "/elevate")
+            .setParam("shards.qt", "/elevate")
+            .setRows(500)
+            .setFields("id,[elevated]")
+            .setParam("enableElevation", "true")
+            .setParam("forceElevation", "true")
+            .setParam("elevateIds", "6")
+            .setSort("id", SolrQuery.ORDER.desc);
+    setDistributedParams(solrQuery);
+    response = client.query(DEFAULT_TEST_CORENAME, solrQuery);
 
     assertTrue(response.getResults().getNumFound() > 0);
     document = response.getResults().get(0);
diff --git a/solr/core/src/test/org/apache/solr/handler/component/SearchHandlerTest.java b/solr/core/src/test/org/apache/solr/handler/component/SearchHandlerTest.java
index 579c02491a9..79592e3bc4c 100644
--- a/solr/core/src/test/org/apache/solr/handler/component/SearchHandlerTest.java
+++ b/solr/core/src/test/org/apache/solr/handler/component/SearchHandlerTest.java
@@ -22,8 +22,6 @@ import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
 import org.apache.solr.SolrTestCaseJ4;
-import org.apache.solr.client.solrj.SolrClient;
-import org.apache.solr.client.solrj.apache.HttpSolrClient;
 import org.apache.solr.client.solrj.impl.CloudSolrClient;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
 import org.apache.solr.client.solrj.request.QueryRequest;
@@ -158,14 +156,10 @@ public class SearchHandlerTest extends SolrTestCaseJ4 {
       Replica replica = getRandomEntry(slice.getReplicas());
       JettySolrRunner jetty = miniCluster.getReplicaJetty(replica);
       // Use the replica's core URL to avoid ZK communication
-      try (SolrClient client =
-          new HttpSolrClient.Builder(replica.getBaseUrl())
-              .withDefaultCollection(replica.getCoreName())
-              .build()) {
-        jetty.getCoreContainer().getZkController().getZkClient().close();
-        rsp = req.process(client);
-        assertFalse(rsp.getResponseHeader().getBooleanArg("zkConnected"));
-      }
+      var client = jetty.getSolrClient();
+      jetty.getCoreContainer().getZkController().getZkClient().close();
+      rsp = req.process(client, replica.getCoreName());
+      assertFalse(rsp.getResponseHeader().getBooleanArg("zkConnected"));
     } finally {
       miniCluster.shutdown();
     }
@@ -209,13 +203,11 @@ public class SearchHandlerTest extends SolrTestCaseJ4 {
       JettySolrRunner disconnectedJetty = miniCluster.getReplicaJetty(disconnectedReplica);
       req = new QueryRequest(params); // TODO Can be removed when SOLR-17314 fixed
       // Use the replica's core URL to avoid ZK communication
-      try (SolrClient solrClient =
-          new HttpSolrClient.Builder(disconnectedReplica.getBaseUrl())
-              .withDefaultCollection(disconnectedReplica.getCoreName())
-              .build()) {
-        ignoreException("ZooKeeper is not connected");
-        disconnectedJetty.getCoreContainer().getZkController().getZkClient().close();
-        req.process(solrClient);
+      var solrClient = disconnectedJetty.getSolrClient();
+      ignoreException("ZooKeeper is not connected");
+      disconnectedJetty.getCoreContainer().getZkController().getZkClient().close();
+      try {
+        req.process(solrClient, disconnectedReplica.getCoreName());
         fail(
             "An exception should be thrown when ZooKeeper is not connected and shards.tolerant=requireZkConnected");
       } catch (Exception e) {
@@ -268,15 +260,13 @@ public class SearchHandlerTest extends SolrTestCaseJ4 {
         connectedSlice = getRandomEntry(slices);
       }
       Replica connectedReplica = connectedSlice.getReplicas().iterator().next();
-      try (SolrClient solrClient =
-          new HttpSolrClient.Builder(connectedReplica.getBaseUrl())
-              .withDefaultCollection(connectedReplica.getCoreName())
-              .build()) {
-        ignoreException("ZooKeeper is not connected");
-        ignoreException("no active servers hosting shard:");
-        JettySolrRunner disconnectedJetty = miniCluster.getReplicaJetty(disconnectedReplica);
-        disconnectedJetty.getCoreContainer().getZkController().getZkClient().close();
-        req.process(solrClient);
+      var solrClient = miniCluster.getReplicaJetty(connectedReplica).getSolrClient();
+      ignoreException("ZooKeeper is not connected");
+      ignoreException("no active servers hosting shard:");
+      JettySolrRunner disconnectedJetty = miniCluster.getReplicaJetty(disconnectedReplica);
+      disconnectedJetty.getCoreContainer().getZkController().getZkClient().close();
+      try {
+        req.process(solrClient, connectedReplica.getCoreName());
         fail(
             "An exception should be thrown when ZooKeeper is not connected and shards.tolerant=requireZkConnected");
       } catch (Exception e) {
diff --git a/solr/core/src/test/org/apache/solr/schema/TestCloudManagedSchema.java b/solr/core/src/test/org/apache/solr/schema/TestCloudManagedSchema.java
index 959ea8554cd..6a584ddb165 100644
--- a/solr/core/src/test/org/apache/solr/schema/TestCloudManagedSchema.java
+++ b/solr/core/src/test/org/apache/solr/schema/TestCloudManagedSchema.java
@@ -22,8 +22,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
 import org.apache.solr.client.api.model.CoreStatusResponse;
-import org.apache.solr.client.solrj.SolrClient;
-import org.apache.solr.client.solrj.apache.HttpSolrClient;
 import org.apache.solr.client.solrj.request.QueryRequest;
 import org.apache.solr.client.solrj.request.json.JacksonContentWriter;
 import org.apache.solr.cloud.AbstractFullDistribZkTestBase;
@@ -60,22 +58,20 @@ public class TestCloudManagedSchema extends AbstractFullDistribZkTestBase {
     request.setPath("/admin/cores");
     int which = r.nextInt(clients.size());
 
-    // create a client that does not have the /collection1 as part of the URL.
-    try (SolrClient rootClient =
-        new HttpSolrClient.Builder(buildUrl(jettys.get(which).getLocalPort())).build()) {
-      NamedList<?> namedListResponse = rootClient.request(request);
-      final var statusByCore =
-          JacksonContentWriter.DEFAULT_MAPPER.convertValue(
-              namedListResponse.get("status"),
-              new TypeReference<Map<String, CoreStatusResponse.SingleCoreData>>() {});
-      final String coreName = statusByCore.keySet().stream().findFirst().get();
-      final var collectionStatus = statusByCore.get(coreName);
-      // Make sure the upgrade to managed schema happened
-      assertEquals(
-          "Schema resource name differs from expected name",
-          "managed-schema.xml",
-          collectionStatus.schema);
-    }
+    // use a client that does not have the /collection1 as part of the URL.
+    var rootClient = jettys.get(which).getSolrClient();
+    NamedList<?> namedListResponse = rootClient.request(request);
+    final var statusByCore =
+        JacksonContentWriter.DEFAULT_MAPPER.convertValue(
+            namedListResponse.get("status"),
+            new TypeReference<Map<String, CoreStatusResponse.SingleCoreData>>() {});
+    final String coreName = statusByCore.keySet().stream().findFirst().get();
+    final var collectionStatus = statusByCore.get(coreName);
+    // Make sure the upgrade to managed schema happened
+    assertEquals(
+        "Schema resource name differs from expected name",
+        "managed-schema.xml",
+        collectionStatus.schema);
 
     try (SolrZkClient zkClient =
         new SolrZkClient.Builder()
diff --git a/solr/core/src/test/org/apache/solr/search/facet/DistributedFacetSimpleRefinementLongTailTest.java b/solr/core/src/test/org/apache/solr/search/facet/DistributedFacetSimpleRefinementLongTailTest.java
index 8c8193281be..34e4d646120 100644
--- a/solr/core/src/test/org/apache/solr/search/facet/DistributedFacetSimpleRefinementLongTailTest.java
+++ b/solr/core/src/test/org/apache/solr/search/facet/DistributedFacetSimpleRefinementLongTailTest.java
@@ -87,7 +87,7 @@ public class DistributedFacetSimpleRefinementLongTailTest extends BaseDistribute
     checkSubFacetStats();
   }
 
-  public static void buildIndexes(final List<SolrClient> clients, final String statField)
+  public static void buildIndexes(final List<? extends SolrClient> clients, final String statField)
       throws Exception {
 
     assertEquals("This indexing code assumes exactly 3 shards/clients", 3, clients.size());
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 d08280c17bc..166b9f440c6 100644
--- a/solr/core/src/test/org/apache/solr/security/BasicAuthIntegrationTest.java
+++ b/solr/core/src/test/org/apache/solr/security/BasicAuthIntegrationTest.java
@@ -31,7 +31,6 @@ import org.apache.solr.client.solrj.RemoteSolrException;
 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.apache.CloudLegacySolrClient;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
 import org.apache.solr.client.solrj.request.GenericSolrRequest;
 import org.apache.solr.client.solrj.request.QueryRequest;
@@ -136,9 +135,9 @@ public class BasicAuthIntegrationTest extends SolrCloudAuthTestCase {
     }
 
     // avoid bad connection races due to shutdown
-    final var apacheHttpClient = ((CloudLegacySolrClient) cluster.getSolrClient()).getHttpClient();
-    apacheHttpClient.getConnectionManager().closeExpiredConnections();
-    apacheHttpClient.getConnectionManager().closeIdleConnections(1, TimeUnit.MILLISECONDS);
+    // var apacheHttpClient = ((CloudLegacySolrClient) cluster.getSolrClient()).getHttpClient();
+    // apacheHttpClient.getConnectionManager().closeExpiredConnections();
+    // apacheHttpClient.getConnectionManager().closeIdleConnections(1, TimeUnit.MILLISECONDS);
 
     RemoteSolrException exp =
         expectThrows(
diff --git a/solr/core/src/test/org/apache/solr/servlet/TestRequestRateLimiter.java b/solr/core/src/test/org/apache/solr/servlet/TestRequestRateLimiter.java
index 43f627d917a..87ec473b13a 100644
--- a/solr/core/src/test/org/apache/solr/servlet/TestRequestRateLimiter.java
+++ b/solr/core/src/test/org/apache/solr/servlet/TestRequestRateLimiter.java
@@ -38,6 +38,7 @@ import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.LongAdder;
+import org.apache.lucene.tests.util.LuceneTestCase;
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.client.solrj.RemoteSolrException;
 import org.apache.solr.client.solrj.SolrClient;
@@ -55,6 +56,7 @@ import org.junit.BeforeClass;
 import org.junit.Test;
 import org.mockito.Mockito;
 
+@LuceneTestCase.AwaitsFix(bugUrl = "https://issues.apache.org/jira/browse/SOLR-17810")
 public class TestRequestRateLimiter extends SolrCloudTestCase {
   private static final String FIRST_COLLECTION = "c1";
   private static final String SECOND_COLLECTION = "c2";
diff --git a/solr/core/src/test/org/apache/solr/update/DeleteByIdWithRouterFieldTest.java b/solr/core/src/test/org/apache/solr/update/DeleteByIdWithRouterFieldTest.java
index ff9ff482e53..4a92852cbdc 100644
--- a/solr/core/src/test/org/apache/solr/update/DeleteByIdWithRouterFieldTest.java
+++ b/solr/core/src/test/org/apache/solr/update/DeleteByIdWithRouterFieldTest.java
@@ -24,9 +24,8 @@ import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
 import org.apache.lucene.tests.util.TestUtil;
-import org.apache.lucene.util.IOUtils;
 import org.apache.solr.client.solrj.SolrClient;
-import org.apache.solr.client.solrj.apache.HttpSolrClient;
+import org.apache.solr.client.solrj.impl.CollectionScopedSolrClient;
 import org.apache.solr.client.solrj.impl.LBSolrClient;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
 import org.apache.solr.client.solrj.request.UpdateRequest;
@@ -77,22 +76,21 @@ public class DeleteByIdWithRouterFieldTest extends SolrCloudTestCase {
             .process(cluster.getSolrClient())
             .isSuccess());
 
-    solrClient = cluster.newSolrClient(COLL);
+    solrClient = cluster.getSolrClient(COLL);
 
     ClusterState clusterState = cluster.getSolrClient().getClusterState();
     for (Replica replica : clusterState.getCollection(COLL).getReplicas()) {
       clients.add(
-          new HttpSolrClient.Builder(replica.getBaseUrl())
-              .withDefaultCollection(replica.getCoreName())
-              .build());
+          new CollectionScopedSolrClient(
+              cluster.getReplicaJetty(replica).getSolrClient(), replica.getCoreName()));
     }
   }
 
   @AfterClass
   public static void afterClass() throws Exception {
-    IOUtils.close(clients);
+    // lifecycle internally managed; we simply GC here
     clients.clear();
-    IOUtils.close(solrClient);
+    solrClient = null;
 
     RVAL_PRE = null;
   }
diff --git a/solr/core/src/test/org/apache/solr/update/SolrCmdDistributorTest.java b/solr/core/src/test/org/apache/solr/update/SolrCmdDistributorTest.java
index 59f28f3409e..aeb41353204 100644
--- a/solr/core/src/test/org/apache/solr/update/SolrCmdDistributorTest.java
+++ b/solr/core/src/test/org/apache/solr/update/SolrCmdDistributorTest.java
@@ -28,7 +28,7 @@ import java.util.concurrent.atomic.AtomicInteger;
 import org.apache.solr.BaseDistributedSearchTestCase;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrServerException;
-import org.apache.solr.client.solrj.apache.HttpSolrClient;
+import org.apache.solr.client.solrj.impl.HttpSolrClient;
 import org.apache.solr.client.solrj.request.LukeRequest;
 import org.apache.solr.client.solrj.request.SolrQuery;
 import org.apache.solr.client.solrj.request.UpdateRequest;
@@ -159,7 +159,7 @@ public class SolrCmdDistributorTest extends BaseDistributedSearchTestCase {
       nodeProps =
           new ZkNodeProps(
               ZkStateReader.BASE_URL_PROP,
-              ((HttpSolrClient) controlClient).getBaseURL(),
+              controlClient.getBaseURL(),
               ZkStateReader.CORE_NAME_PROP,
               controlClient.getDefaultCollection());
       nodes.add(new StdNode(new ZkCoreNodeProps(nodeProps)));
@@ -182,7 +182,7 @@ public class SolrCmdDistributorTest extends BaseDistributedSearchTestCase {
       numFound = controlClient.query(new SolrQuery("*:*")).getResults().getNumFound();
       assertEquals(1, numFound);
 
-      client = (HttpSolrClient) clients.get(0);
+      client = clients.getFirst();
       nodeProps =
           new ZkNodeProps(
               ZkStateReader.BASE_URL_PROP,
@@ -269,17 +269,16 @@ public class SolrCmdDistributorTest extends BaseDistributedSearchTestCase {
       int cnt = atLeast(303);
       for (int i = 0; i < cnt; i++) {
         nodes.clear();
-        for (SolrClient c : clients) {
+        for (HttpSolrClient c : clients) {
           if (random().nextBoolean()) {
             continue;
           }
-          HttpSolrClient httpClient = (HttpSolrClient) c;
           nodeProps =
               new ZkNodeProps(
                   ZkStateReader.BASE_URL_PROP,
-                  httpClient.getBaseURL(),
+                  c.getBaseURL(),
                   ZkStateReader.CORE_NAME_PROP,
-                  httpClient.getDefaultCollection());
+                  c.getDefaultCollection());
           nodes.add(new StdNode(new ZkCoreNodeProps(nodeProps)));
         }
         AddUpdateCommand c = new AddUpdateCommand(null);
@@ -292,14 +291,13 @@ public class SolrCmdDistributorTest extends BaseDistributedSearchTestCase {
 
       nodes.clear();
 
-      for (SolrClient c : clients) {
-        HttpSolrClient httpClient = (HttpSolrClient) c;
+      for (HttpSolrClient c : clients) {
         nodeProps =
             new ZkNodeProps(
                 ZkStateReader.BASE_URL_PROP,
-                httpClient.getBaseURL(),
+                c.getBaseURL(),
                 ZkStateReader.CORE_NAME_PROP,
-                httpClient.getDefaultCollection());
+                c.getDefaultCollection());
 
         nodes.add(new StdNode(new ZkCoreNodeProps(nodeProps)));
       }
@@ -369,7 +367,7 @@ public class SolrCmdDistributorTest extends BaseDistributedSearchTestCase {
   }
 
   private void testDeletes(boolean dbq, boolean withFailures) throws Exception {
-    final HttpSolrClient solrclient = (HttpSolrClient) clients.get(0);
+    final var solrclient = clients.getFirst();
     solrclient.commit(true, true);
     long numFoundBefore = solrclient.query(new SolrQuery("*:*")).getResults().getNumFound();
     final MockStreamingSolrClients streamingClients =
@@ -437,7 +435,7 @@ public class SolrCmdDistributorTest extends BaseDistributedSearchTestCase {
   }
 
   private void testMinRfOnRetries(NodeType nodeType) throws Exception {
-    final HttpSolrClient solrclient = (HttpSolrClient) clients.get(0);
+    final var solrclient = clients.getFirst();
     final MockStreamingSolrClients streamingClients =
         new MockStreamingSolrClients(updateShardHandler);
     try (SolrCmdDistributor cmdDistrib = new SolrCmdDistributor(streamingClients, 0)) {
@@ -499,7 +497,7 @@ public class SolrCmdDistributorTest extends BaseDistributedSearchTestCase {
     try (SolrCmdDistributor cmdDistrib = new SolrCmdDistributor(streamingClients, 0)) {
       streamingClients.setExp(Exp.CONNECT_EXCEPTION);
       ArrayList<Node> nodes = new ArrayList<>();
-      final HttpSolrClient solrClient1 = (HttpSolrClient) clients.get(0);
+      final var solrClient1 = clients.getFirst();
 
       final AtomicInteger retries = new AtomicInteger();
       ZkNodeProps nodeProps =
@@ -617,7 +615,7 @@ public class SolrCmdDistributorTest extends BaseDistributedSearchTestCase {
   }
 
   private void testOneRetry(NodeType nodeType) throws Exception {
-    final HttpSolrClient solrclient = (HttpSolrClient) clients.get(0);
+    final var solrclient = clients.getFirst();
     long numFoundBefore = solrclient.query(new SolrQuery("*:*")).getResults().getNumFound();
     final MockStreamingSolrClients streamingClients =
         new MockStreamingSolrClients(updateShardHandler);
@@ -678,7 +676,7 @@ public class SolrCmdDistributorTest extends BaseDistributedSearchTestCase {
 
   private void testNodeWontRetryBadRequest(NodeType nodeType) throws Exception {
     ignoreException("Bad Request");
-    final HttpSolrClient solrclient = (HttpSolrClient) clients.get(0);
+    final var solrclient = clients.getFirst();
     long numFoundBefore = solrclient.query(new SolrQuery("*:*")).getResults().getNumFound();
     final MockStreamingSolrClients streamingClients =
         new MockStreamingSolrClients(updateShardHandler);
@@ -739,7 +737,7 @@ public class SolrCmdDistributorTest extends BaseDistributedSearchTestCase {
   }
 
   private void testForwardNodeWontRetrySocketError() throws Exception {
-    final HttpSolrClient solrclient = (HttpSolrClient) clients.get(0);
+    final var solrclient = clients.getFirst();
     long numFoundBefore = solrclient.query(new SolrQuery("*:*")).getResults().getNumFound();
     final MockStreamingSolrClients streamingClients =
         new MockStreamingSolrClients(updateShardHandler);
@@ -788,7 +786,7 @@ public class SolrCmdDistributorTest extends BaseDistributedSearchTestCase {
   }
 
   private void testStdNodeRetriesSocketError() throws Exception {
-    final HttpSolrClient solrclient = (HttpSolrClient) clients.get(0);
+    final var solrclient = clients.getFirst();
     final MockStreamingSolrClients streamingClients =
         new MockStreamingSolrClients(updateShardHandler);
     try (SolrCmdDistributor cmdDistrib = new SolrCmdDistributor(streamingClients, 0)) {
@@ -828,7 +826,7 @@ public class SolrCmdDistributorTest extends BaseDistributedSearchTestCase {
   private void testRetryNodeAgainstBadAddress() throws SolrServerException, IOException {
     // Test RetryNode
     try (SolrCmdDistributor cmdDistrib = new SolrCmdDistributor(updateShardHandler)) {
-      final HttpSolrClient solrclient = (HttpSolrClient) clients.get(0);
+      final var solrclient = clients.getFirst();
       long numFoundBefore = solrclient.query(new SolrQuery("*:*")).getResults().getNumFound();
 
       ArrayList<Node> nodes = new ArrayList<>();
@@ -914,17 +912,13 @@ public class SolrCmdDistributorTest extends BaseDistributedSearchTestCase {
     try (SolrCmdDistributor cmdDistrib = new SolrCmdDistributor(updateShardHandler)) {
       for (int i = 0; i < 3; i++) {
         nodes.clear();
-        for (SolrClient c : clients) {
+        for (HttpSolrClient c : clients) {
           if (random().nextBoolean()) {
             continue;
           }
-          HttpSolrClient httpClient = (HttpSolrClient) c;
           ZkNodeProps nodeProps =
               new ZkNodeProps(
-                  ZkStateReader.BASE_URL_PROP,
-                  httpClient.getBaseURL(),
-                  ZkStateReader.CORE_NAME_PROP,
-                  "");
+                  ZkStateReader.BASE_URL_PROP, c.getBaseURL(), ZkStateReader.CORE_NAME_PROP, "");
           StdNode node = new StdNode(new ZkCoreNodeProps(nodeProps));
           nodes.add(node);
         }
diff --git a/solr/core/src/test/org/apache/solr/update/TestInPlaceUpdatesDistrib.java b/solr/core/src/test/org/apache/solr/update/TestInPlaceUpdatesDistrib.java
index 084f94aa964..8a1dd5fd46c 100644
--- a/solr/core/src/test/org/apache/solr/update/TestInPlaceUpdatesDistrib.java
+++ b/solr/core/src/test/org/apache/solr/update/TestInPlaceUpdatesDistrib.java
@@ -40,7 +40,7 @@ import org.apache.lucene.index.NoMergePolicy;
 import org.apache.lucene.tests.util.TestUtil;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrServerException;
-import org.apache.solr.client.solrj.apache.HttpSolrClient;
+import org.apache.solr.client.solrj.impl.HttpSolrClient;
 import org.apache.solr.client.solrj.request.UpdateRequest;
 import org.apache.solr.client.solrj.request.schema.SchemaRequest.Field;
 import org.apache.solr.client.solrj.response.UpdateResponse;
@@ -120,8 +120,8 @@ public class TestInPlaceUpdatesDistrib extends AbstractFullDistribZkTestBase {
     fixShardCount(3);
   }
 
-  private SolrClient LEADER = null;
-  private List<SolrClient> NONLEADERS = null;
+  private HttpSolrClient LEADER = null;
+  private List<HttpSolrClient> NONLEADERS = null;
 
   @Test
   @ShardsFixed(num = 3)
@@ -226,8 +226,8 @@ public class TestInPlaceUpdatesDistrib extends AbstractFullDistribZkTestBase {
     leader = shard1.getLeader();
 
     String leaderBaseUrl = zkStateReader.getBaseUrlForNodeName(leader.getNodeName());
-    for (SolrClient solrClient : clients) {
-      if (((HttpSolrClient) solrClient).getBaseURL().startsWith(leaderBaseUrl)) {
+    for (HttpSolrClient solrClient : clients) {
+      if (solrClient.getBaseURL().startsWith(leaderBaseUrl)) {
         LEADER = solrClient;
       }
     }
@@ -238,8 +238,8 @@ public class TestInPlaceUpdatesDistrib extends AbstractFullDistribZkTestBase {
         continue;
       }
       String baseUrl = zkStateReader.getBaseUrlForNodeName(rep.getNodeName());
-      for (SolrClient client : clients) {
-        if (((HttpSolrClient) client).getBaseURL().startsWith(baseUrl)) {
+      for (HttpSolrClient client : clients) {
+        if (client.getBaseURL().startsWith(baseUrl)) {
           NONLEADERS.add(client);
         }
       }
@@ -267,7 +267,7 @@ public class TestInPlaceUpdatesDistrib extends AbstractFullDistribZkTestBase {
     assertEquals("title0", sdoc.get("title_s"));
     long version0 = (long) sdoc.get("_version_");
 
-    for (SolrClient client : NONLEADERS) {
+    for (HttpSolrClient client : NONLEADERS) {
       SolrDocument doc = client.getById(String.valueOf(0), params("distrib", "false"));
       assertEquals(inplace_updatable_float, doc.get("inplace_updatable_float"));
       assertEquals(version0, doc.get("_version_"));
@@ -281,7 +281,7 @@ public class TestInPlaceUpdatesDistrib extends AbstractFullDistribZkTestBase {
     assertEquals("title0", sdoc.get("title_s"));
     long version1 = (long) sdoc.get("_version_");
 
-    for (SolrClient client : NONLEADERS) {
+    for (HttpSolrClient client : NONLEADERS) {
       SolrDocument doc = client.getById(String.valueOf(0), params("distrib", "false"));
       assertNull(doc.get("inplace_updatable_float"));
       assertEquals(version1, doc.get("_version_"));
@@ -372,7 +372,8 @@ public class TestInPlaceUpdatesDistrib extends AbstractFullDistribZkTestBase {
     }
 
     // assert both replicas have same effect
-    for (SolrClient client : NONLEADERS) { // 0th is re-ordered replica, 1st is well-ordered replica
+    for (HttpSolrClient client :
+        NONLEADERS) { // 0th is re-ordered replica, 1st is well-ordered replica
       SolrDocument doc = client.getById(String.valueOf(0), params("distrib", "false"));
       assertNull("This doc was supposed to have been deleted, but was: " + doc, doc);
     }
@@ -662,7 +663,7 @@ public class TestInPlaceUpdatesDistrib extends AbstractFullDistribZkTestBase {
     assertEquals(luceneDocids.size(), valuesList.size());
     final long numFoundExpected = luceneDocids.size();
 
-    for (SolrClient client : clients) {
+    for (HttpSolrClient client : clients) {
       final String clientDebug =
           client.toString() + (LEADER.equals(client) ? " (leader)" : " (not leader)");
       final String msg = "'" + debug + "' results against client: " + clientDebug;
@@ -838,7 +839,7 @@ public class TestInPlaceUpdatesDistrib extends AbstractFullDistribZkTestBase {
     assertThat(e.getMessage(), containsString("For input string: \"NOT_NUMBER\""));
 
     // RTG from tlog(s)
-    for (SolrClient client : clients) {
+    for (HttpSolrClient client : clients) {
       final String clientDebug =
           client.toString() + (LEADER.equals(client) ? " (leader)" : " (not leader)");
       sdoc = client.getById("100", params("distrib", "false"));
@@ -868,7 +869,7 @@ public class TestInPlaceUpdatesDistrib extends AbstractFullDistribZkTestBase {
    */
   private List<Integer> getInternalDocIds(String id) throws SolrServerException, IOException {
     List<Integer> ret = new ArrayList<>(clients.size());
-    for (SolrClient client : clients) {
+    for (HttpSolrClient client : clients) {
       SolrDocument doc = client.getById(id, params("distrib", "false", "fl", "[docid]"));
       Object docid = doc.get("[docid]");
       assertNotNull(docid);
@@ -960,23 +961,23 @@ public class TestInPlaceUpdatesDistrib extends AbstractFullDistribZkTestBase {
     }
 
     // assert both replicas have same effect
-    for (SolrClient client : NONLEADERS) { // 0th is re-ordered replica, 1st is well-ordered replica
+    for (HttpSolrClient client :
+        NONLEADERS) { // 0th is re-ordered replica, 1st is well-ordered replica
       if (log.isInfoEnabled()) {
-        log.info("Testing client: {}", ((HttpSolrClient) client).getBaseURL());
+        log.info("Testing client: {}", client.getBaseURL());
       }
       assertReplicaValue(
           client,
           0,
           "inplace_updatable_float",
           (newinplace_updatable_float + (float) (updates.size() - 1)),
-          "inplace_updatable_float didn't match for replica at client: "
-              + ((HttpSolrClient) client).getBaseURL());
+          "inplace_updatable_float didn't match for replica at client: " + client.getBaseURL());
       assertReplicaValue(
           client,
           0,
           "title_s",
           "title0_new",
-          "Title didn't match for replica at client: " + ((HttpSolrClient) client).getBaseURL());
+          "Title didn't match for replica at client: " + client.getBaseURL());
       assertEquals(version0 + updates.size(), getReplicaValue(client, 0, "_version_"));
     }
 
@@ -1064,7 +1065,8 @@ public class TestInPlaceUpdatesDistrib extends AbstractFullDistribZkTestBase {
     }
 
     // assert both replicas have same effect
-    for (SolrClient client : NONLEADERS) { // 0th is re-ordered replica, 1st is well-ordered replica
+    for (HttpSolrClient client :
+        NONLEADERS) { // 0th is re-ordered replica, 1st is well-ordered replica
       SolrDocument doc = client.getById(String.valueOf(0), params("distrib", "false"));
       assertNull("This doc was supposed to have been deleted, but was: " + doc, doc);
     }
@@ -1154,9 +1156,7 @@ public class TestInPlaceUpdatesDistrib extends AbstractFullDistribZkTestBase {
       // the resurrection can happen from there (instead of the leader)
       update.setParam(
           DistributedUpdateProcessor.DISTRIB_FROM,
-          ((HttpSolrClient) NONLEADERS.get(1)).getBaseURL()
-              + "/"
-              + NONLEADERS.get(1).getDefaultCollection());
+          (NONLEADERS.get(1)).getBaseURL() + "/" + NONLEADERS.get(1).getDefaultCollection());
       AsyncUpdateWithRandomCommit task =
           new AsyncUpdateWithRandomCommit(update, NONLEADERS.get(0), random().nextLong());
       updateResponses.add(threadpool.submit(task));
@@ -1196,8 +1196,8 @@ public class TestInPlaceUpdatesDistrib extends AbstractFullDistribZkTestBase {
     assertEquals(updateResponses.size(), successful);
 
     if (log.isInfoEnabled()) {
-      log.info("Non leader 0: {}", ((HttpSolrClient) NONLEADERS.get(0)).getBaseURL());
-      log.info("Non leader 1: {}", ((HttpSolrClient) NONLEADERS.get(1)).getBaseURL()); // nowarn
+      log.info("Non leader 0: {}", NONLEADERS.get(0).getBaseURL());
+      log.info("Non leader 1: {}", NONLEADERS.get(1).getBaseURL()); // nowarn
     }
 
     SolrDocument doc0 = NONLEADERS.get(0).getById(String.valueOf(0), params("distrib", "false"));
@@ -1206,11 +1206,11 @@ public class TestInPlaceUpdatesDistrib extends AbstractFullDistribZkTestBase {
     log.info("Doc in both replica 0: {}", doc0);
     log.info("Doc in both replica 1: {}", doc1);
     // assert both replicas have same effect
-    for (SolrClient client : NONLEADERS) { // 0th is re-ordered replica, 1st is well-ordered replica
+    for (HttpSolrClient client :
+        NONLEADERS) { // 0th is re-ordered replica, 1st is well-ordered replica
       SolrDocument doc = client.getById(String.valueOf(0), params("distrib", "false"));
-      assertNotNull("Client: " + ((HttpSolrClient) client).getBaseURL(), doc);
-      assertEquals(
-          "Client: " + ((HttpSolrClient) client).getBaseURL(), 5, doc.getFieldValue(field));
+      assertNotNull("Client: " + client.getBaseURL(), doc);
+      assertEquals("Client: " + client.getBaseURL(), 5, doc.getFieldValue(field));
     }
 
     log.info("reorderedDBQsResurrectionTest: This test passed fine...");
@@ -1284,7 +1284,7 @@ public class TestInPlaceUpdatesDistrib extends AbstractFullDistribZkTestBase {
           "The replica receiving reordered updates must not have gone down", 3, numActiveReplicas);
     }
 
-    for (SolrClient client : clients) {
+    for (HttpSolrClient client : clients) {
       TimeOut timeout = new TimeOut(30, TimeUnit.SECONDS, TimeSource.NANO_TIME);
       try {
         timeout.waitFor(
@@ -1304,27 +1304,25 @@ public class TestInPlaceUpdatesDistrib extends AbstractFullDistribZkTestBase {
       }
     }
 
-    for (SolrClient client : clients) {
+    for (HttpSolrClient client : clients) {
+      String baseURL = client.getBaseURL();
       if (log.isInfoEnabled()) {
-        log.info("Testing client (Fetch missing test): {}", ((HttpSolrClient) client).getBaseURL());
+        log.info("Testing client (Fetch missing test): {}", baseURL);
         log.info(
-            "Version at {} is: {}",
-            ((HttpSolrClient) client).getBaseURL(),
-            getReplicaValue(client, 1, "_version_")); // nowarn
+            "Version at {} is: {}", baseURL, getReplicaValue(client, 1, "_version_")); // nowarn
       }
       assertReplicaValue(
           client,
           1,
           "inplace_updatable_float",
           (newinplace_updatable_float + 2.0f),
-          "inplace_updatable_float didn't match for replica at client: "
-              + ((HttpSolrClient) client).getBaseURL());
+          "inplace_updatable_float didn't match for replica at client: " + baseURL);
       assertReplicaValue(
           client,
           1,
           "title_s",
           "title1_new",
-          "Title didn't match for replica at client: " + ((HttpSolrClient) client).getBaseURL());
+          "Title didn't match for replica at client: " + baseURL);
     }
 
     // Try another round of these updates, this time with a delete request at the end.
@@ -1488,7 +1486,7 @@ public class TestInPlaceUpdatesDistrib extends AbstractFullDistribZkTestBase {
   }
 
   UpdateRequest simulatedDeleteRequest(String query, long version) {
-    String baseUrl = getBaseUrl((HttpSolrClient) LEADER);
+    String baseUrl = getBaseUrl(LEADER);
 
     UpdateRequest ur = new UpdateRequest();
     ur.deleteByQuery(query);
@@ -1688,14 +1686,13 @@ public class TestInPlaceUpdatesDistrib extends AbstractFullDistribZkTestBase {
           "The replica receiving reordered updates must not have gone down", 3, numActiveReplicas);
     }
 
-    for (SolrClient client : clients) {
+    for (HttpSolrClient client : clients) {
       if (log.isInfoEnabled()) {
         log.info(
-            "Testing client (testDBQUsingUpdatedFieldFromDroppedUpdate): {}",
-            ((HttpSolrClient) client).getBaseURL());
+            "Testing client (testDBQUsingUpdatedFieldFromDroppedUpdate): {}", client.getBaseURL());
         log.info(
             "Version at {} is: {}",
-            ((HttpSolrClient) client).getBaseURL(),
+            client.getBaseURL(),
             getReplicaValue(client, 1, "_version_")); // nowarn
       }
       assertNull(client.getById("1", params("distrib", "false")));
diff --git a/solr/core/src/test/org/apache/solr/util/tracing/TestSimplePropagatorDistributedTracing.java b/solr/core/src/test/org/apache/solr/util/tracing/TestSimplePropagatorDistributedTracing.java
index d66e8933e6d..31567090e0c 100644
--- a/solr/core/src/test/org/apache/solr/util/tracing/TestSimplePropagatorDistributedTracing.java
+++ b/solr/core/src/test/org/apache/solr/util/tracing/TestSimplePropagatorDistributedTracing.java
@@ -25,7 +25,6 @@ import java.util.List;
 import java.util.Optional;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrServerException;
-import org.apache.solr.client.solrj.apache.CloudLegacySolrClient;
 import org.apache.solr.client.solrj.impl.CloudSolrClient;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
 import org.apache.solr.client.solrj.request.QueryRequest;
@@ -72,7 +71,7 @@ public class TestSimplePropagatorDistributedTracing extends SolrCloudTestCase {
 
     try (LogListener reqLog = LogListener.info(SolrCore.class.getName() + ".Request")) {
 
-      try (SolrClient testClient = newCloudLegacySolrClient()) {
+      try (SolrClient testClient = newCloudSolrClient()) {
         // verify all query events have the same auto-generated trace id
         var r1 = testClient.query(COLLECTION, new SolrQuery("*:*"));
         assertEquals(0, r1.getStatus());
@@ -108,7 +107,7 @@ public class TestSimplePropagatorDistributedTracing extends SolrCloudTestCase {
   public void testUpdateRequest() throws IOException, SolrServerException {
     try (LogListener reqLog = LogListener.info(LogUpdateProcessorFactory.class.getName())) {
 
-      try (SolrClient testClient = newCloudLegacySolrClient()) {
+      try (SolrClient testClient = newCloudSolrClient()) {
         // verify all indexing events have trace id present
         testClient.add(COLLECTION, sdoc("id", "1"));
         testClient.add(COLLECTION, sdoc("id", "3"));
@@ -170,12 +169,6 @@ public class TestSimplePropagatorDistributedTracing extends SolrCloudTestCase {
     }
   }
 
-  private CloudSolrClient newCloudLegacySolrClient() {
-    return new CloudLegacySolrClient.Builder(
-            List.of(cluster.getZkServer().getZkAddress()), Optional.empty())
-        .build();
-  }
-
   private CloudSolrClient newCloudSolrClient() {
     var builder =
         new CloudSolrClient.Builder(
diff --git a/solr/cross-dc-manager/gradle.lockfile b/solr/cross-dc-manager/gradle.lockfile
index 8984aca0004..1bdb0d594aa 100644
--- a/solr/cross-dc-manager/gradle.lockfile
+++ b/solr/cross-dc-manager/gradle.lockfile
@@ -129,9 +129,6 @@ org.apache.commons:commons-math3:3.6.1=jarValidation,runtimeClasspath,runtimeLib
 org.apache.curator:curator-client:5.9.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 org.apache.curator:curator-framework:5.9.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 org.apache.curator:curator-test:5.9.0=jarValidation,testRuntimeClasspath
-org.apache.httpcomponents:httpclient:4.5.14=jarValidation,testRuntimeClasspath
-org.apache.httpcomponents:httpcore:4.4.16=jarValidation,testRuntimeClasspath
-org.apache.httpcomponents:httpmime:4.5.14=jarValidation,testRuntimeClasspath
 org.apache.kafka:kafka-clients:3.9.2=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 org.apache.kafka:kafka-group-coordinator-api:3.9.2=jarValidation,runtimeClasspath,runtimeLibs,testRuntimeClasspath
 org.apache.kafka:kafka-group-coordinator:3.9.2=jarValidation,runtimeClasspath,runtimeLibs,testRuntimeClasspath
diff --git a/solr/licenses/httpmime-4.5.14.jar.sha1 b/solr/licenses/httpmime-4.5.14.jar.sha1
deleted file mode 100644
index 3c37fe334ef..00000000000
--- a/solr/licenses/httpmime-4.5.14.jar.sha1
+++ /dev/null
@@ -1 +0,0 @@
-6662758a1f1cb1149cf916bdac28332e0902ec44
diff --git a/solr/licenses/httpmime-LICENSE-ASL.txt b/solr/licenses/httpmime-LICENSE-ASL.txt
deleted file mode 100644
index 2c41ec88f61..00000000000
--- a/solr/licenses/httpmime-LICENSE-ASL.txt
+++ /dev/null
@@ -1,182 +0,0 @@
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-   
-This project contains annotations derived from JCIP-ANNOTATIONS
-Copyright (c) 2005 Brian Goetz and Tim Peierls.
-See http://www.jcip.net and the Creative Commons Attribution License 
-(http://creativecommons.org/licenses/by/2.5)
-
diff --git a/solr/licenses/httpmime-NOTICE.txt b/solr/licenses/httpmime-NOTICE.txt
deleted file mode 100644
index 3a98f4542f5..00000000000
--- a/solr/licenses/httpmime-NOTICE.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-Apache HttpComponents Client
-Copyright 1999-2011 The Apache Software Foundation
-
-This product includes software developed by
-The Apache Software Foundation (http://www.apache.org/).
-
-This project contains annotations derived from JCIP-ANNOTATIONS
-Copyright (c) 2005 Brian Goetz and Tim Peierls. See http://www.jcip.net
\ No newline at end of file
diff --git a/solr/modules/analysis-extras/gradle.lockfile b/solr/modules/analysis-extras/gradle.lockfile
index 7d49e2a062e..17b91291137 100644
--- a/solr/modules/analysis-extras/gradle.lockfile
+++ b/solr/modules/analysis-extras/gradle.lockfile
@@ -85,9 +85,6 @@ org.apache.commons:commons-math3:3.6.1=apiHelper,jarValidation,runtimeClasspath,
 org.apache.curator:curator-client:5.9.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 org.apache.curator:curator-framework:5.9.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 org.apache.curator:curator-test:5.9.0=jarValidation,testRuntimeClasspath
-org.apache.httpcomponents:httpclient:4.5.14=jarValidation,testRuntimeClasspath
-org.apache.httpcomponents:httpcore:4.4.16=jarValidation,testRuntimeClasspath
-org.apache.httpcomponents:httpmime:4.5.14=jarValidation,testRuntimeClasspath
 org.apache.logging.log4j:log4j-1.2-api:2.25.3=solrPlatformLibs
 org.apache.logging.log4j:log4j-api:2.25.3=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 org.apache.logging.log4j:log4j-core:2.25.3=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
diff --git a/solr/modules/clustering/gradle.lockfile b/solr/modules/clustering/gradle.lockfile
index fe993d664b1..654ccb27369 100644
--- a/solr/modules/clustering/gradle.lockfile
+++ b/solr/modules/clustering/gradle.lockfile
@@ -82,9 +82,6 @@ org.apache.commons:commons-math3:3.6.1=jarValidation,runtimeClasspath,runtimeLib
 org.apache.curator:curator-client:5.9.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 org.apache.curator:curator-framework:5.9.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 org.apache.curator:curator-test:5.9.0=jarValidation,testRuntimeClasspath
-org.apache.httpcomponents:httpclient:4.5.14=jarValidation,testRuntimeClasspath
-org.apache.httpcomponents:httpcore:4.4.16=jarValidation,testRuntimeClasspath
-org.apache.httpcomponents:httpmime:4.5.14=jarValidation,testRuntimeClasspath
 org.apache.logging.log4j:log4j-1.2-api:2.25.3=solrPlatformLibs
 org.apache.logging.log4j:log4j-api:2.25.3=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 org.apache.logging.log4j:log4j-core:2.25.3=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
diff --git a/solr/modules/cross-dc/gradle.lockfile b/solr/modules/cross-dc/gradle.lockfile
index 8f080ee3226..84658e4112c 100644
--- a/solr/modules/cross-dc/gradle.lockfile
+++ b/solr/modules/cross-dc/gradle.lockfile
@@ -86,9 +86,6 @@ org.apache.commons:commons-math3:3.6.1=apiHelper,jarValidation,runtimeClasspath,
 org.apache.curator:curator-client:5.9.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 org.apache.curator:curator-framework:5.9.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 org.apache.curator:curator-test:5.9.0=jarValidation,testRuntimeClasspath
-org.apache.httpcomponents:httpclient:4.5.14=jarValidation,testRuntimeClasspath
-org.apache.httpcomponents:httpcore:4.4.16=jarValidation,testRuntimeClasspath
-org.apache.httpcomponents:httpmime:4.5.14=jarValidation,testRuntimeClasspath
 org.apache.kafka:kafka-clients:3.9.2=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
 org.apache.logging.log4j:log4j-1.2-api:2.25.3=solrPlatformLibs
 org.apache.logging.log4j:log4j-api:2.25.3=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
diff --git a/solr/modules/cuvs/gradle.lockfile b/solr/modules/cuvs/gradle.lockfile
index f3a07fed5be..f6fad5bd5d7 100644
--- a/solr/modules/cuvs/gradle.lockfile
+++ b/solr/modules/cuvs/gradle.lockfile
@@ -84,9 +84,6 @@ org.apache.commons:commons-math3:3.6.1=jarValidation,runtimeClasspath,runtimeLib
 org.apache.curator:curator-client:5.9.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 org.apache.curator:curator-framework:5.9.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 org.apache.curator:curator-test:5.9.0=jarValidation,testRuntimeClasspath
-org.apache.httpcomponents:httpclient:4.5.14=jarValidation,testRuntimeClasspath
-org.apache.httpcomponents:httpcore:4.4.16=jarValidation,testRuntimeClasspath
-org.apache.httpcomponents:httpmime:4.5.14=jarValidation,testRuntimeClasspath
 org.apache.logging.log4j:log4j-1.2-api:2.25.3=solrPlatformLibs
 org.apache.logging.log4j:log4j-api:2.25.3=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 org.apache.logging.log4j:log4j-core:2.25.3=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
diff --git a/solr/modules/extraction/gradle.lockfile b/solr/modules/extraction/gradle.lockfile
index 5cc7ac48914..15b3efad0e9 100644
--- a/solr/modules/extraction/gradle.lockfile
+++ b/solr/modules/extraction/gradle.lockfile
@@ -87,9 +87,6 @@ org.apache.commons:commons-math3:3.6.1=jarValidation,runtimeClasspath,runtimeLib
 org.apache.curator:curator-client:5.9.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 org.apache.curator:curator-framework:5.9.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 org.apache.curator:curator-test:5.9.0=jarValidation,testRuntimeClasspath
-org.apache.httpcomponents:httpclient:4.5.14=jarValidation,testRuntimeClasspath
-org.apache.httpcomponents:httpcore:4.4.16=jarValidation,testRuntimeClasspath
-org.apache.httpcomponents:httpmime:4.5.14=jarValidation,testRuntimeClasspath
 org.apache.logging.log4j:log4j-1.2-api:2.25.3=solrPlatformLibs
 org.apache.logging.log4j:log4j-api:2.25.3=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 org.apache.logging.log4j:log4j-core:2.25.3=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
diff --git a/solr/modules/gcs-repository/gradle.lockfile b/solr/modules/gcs-repository/gradle.lockfile
index 5acfbd7f146..99f89b864ae 100644
--- a/solr/modules/gcs-repository/gradle.lockfile
+++ b/solr/modules/gcs-repository/gradle.lockfile
@@ -138,7 +138,6 @@ org.apache.curator:curator-framework:5.9.0=apiHelper,compileClasspath,jarValidat
 org.apache.curator:curator-test:5.9.0=jarValidation,testRuntimeClasspath
 org.apache.httpcomponents:httpclient:4.5.14=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
 org.apache.httpcomponents:httpcore:4.4.16=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
-org.apache.httpcomponents:httpmime:4.5.14=jarValidation,testRuntimeClasspath
 org.apache.logging.log4j:log4j-1.2-api:2.25.3=solrPlatformLibs
 org.apache.logging.log4j:log4j-api:2.25.3=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 org.apache.logging.log4j:log4j-core:2.25.3=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
diff --git a/solr/modules/jwt-auth/gradle.lockfile b/solr/modules/jwt-auth/gradle.lockfile
index 8464a065b74..cadc16b9bed 100644
--- a/solr/modules/jwt-auth/gradle.lockfile
+++ b/solr/modules/jwt-auth/gradle.lockfile
@@ -100,9 +100,6 @@ org.apache.commons:commons-math3:3.6.1=jarValidation,runtimeClasspath,runtimeLib
 org.apache.curator:curator-client:5.9.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 org.apache.curator:curator-framework:5.9.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 org.apache.curator:curator-test:5.9.0=jarValidation,testRuntimeClasspath
-org.apache.httpcomponents:httpclient:4.5.14=jarValidation,testRuntimeClasspath
-org.apache.httpcomponents:httpcore:4.4.16=jarValidation,testRuntimeClasspath
-org.apache.httpcomponents:httpmime:4.5.14=jarValidation,testRuntimeClasspath
 org.apache.logging.log4j:log4j-1.2-api:2.25.3=solrPlatformLibs
 org.apache.logging.log4j:log4j-api:2.25.3=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 org.apache.logging.log4j:log4j-core:2.25.3=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
diff --git a/solr/modules/langid/gradle.lockfile b/solr/modules/langid/gradle.lockfile
index a18bf239038..2396cdd3dcd 100644
--- a/solr/modules/langid/gradle.lockfile
+++ b/solr/modules/langid/gradle.lockfile
@@ -83,9 +83,6 @@ org.apache.commons:commons-math3:3.6.1=jarValidation,runtimeClasspath,runtimeLib
 org.apache.curator:curator-client:5.9.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 org.apache.curator:curator-framework:5.9.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 org.apache.curator:curator-test:5.9.0=jarValidation,testRuntimeClasspath
-org.apache.httpcomponents:httpclient:4.5.14=jarValidation,testRuntimeClasspath
-org.apache.httpcomponents:httpcore:4.4.16=jarValidation,testRuntimeClasspath
-org.apache.httpcomponents:httpmime:4.5.14=jarValidation,testRuntimeClasspath
 org.apache.logging.log4j:log4j-1.2-api:2.25.3=solrPlatformLibs
 org.apache.logging.log4j:log4j-api:2.25.3=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 org.apache.logging.log4j:log4j-core:2.25.3=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
diff --git a/solr/modules/language-models/gradle.lockfile b/solr/modules/language-models/gradle.lockfile
index 5b075c16b16..47c378b8dcc 100644
--- a/solr/modules/language-models/gradle.lockfile
+++ b/solr/modules/language-models/gradle.lockfile
@@ -97,9 +97,6 @@ org.apache.commons:commons-math3:3.6.1=jarValidation,runtimeClasspath,runtimeLib
 org.apache.curator:curator-client:5.9.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 org.apache.curator:curator-framework:5.9.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 org.apache.curator:curator-test:5.9.0=jarValidation,testRuntimeClasspath
-org.apache.httpcomponents:httpclient:4.5.14=jarValidation,testRuntimeClasspath
-org.apache.httpcomponents:httpcore:4.4.16=jarValidation,testRuntimeClasspath
-org.apache.httpcomponents:httpmime:4.5.14=jarValidation,testRuntimeClasspath
 org.apache.logging.log4j:log4j-1.2-api:2.25.3=solrPlatformLibs
 org.apache.logging.log4j:log4j-api:2.25.3=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 org.apache.logging.log4j:log4j-core:2.25.3=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
diff --git a/solr/modules/ltr/gradle.lockfile b/solr/modules/ltr/gradle.lockfile
index 31b6497f52a..7ca60c54cd9 100644
--- a/solr/modules/ltr/gradle.lockfile
+++ b/solr/modules/ltr/gradle.lockfile
@@ -83,9 +83,6 @@ org.apache.commons:commons-math3:3.6.1=jarValidation,runtimeClasspath,runtimeLib
 org.apache.curator:curator-client:5.9.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 org.apache.curator:curator-framework:5.9.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 org.apache.curator:curator-test:5.9.0=jarValidation,testRuntimeClasspath
-org.apache.httpcomponents:httpclient:4.5.14=jarValidation,testRuntimeClasspath
-org.apache.httpcomponents:httpcore:4.4.16=jarValidation,testRuntimeClasspath
-org.apache.httpcomponents:httpmime:4.5.14=jarValidation,testRuntimeClasspath
 org.apache.logging.log4j:log4j-1.2-api:2.25.3=solrPlatformLibs
 org.apache.logging.log4j:log4j-api:2.25.3=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 org.apache.logging.log4j:log4j-core:2.25.3=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
diff --git a/solr/modules/opentelemetry/gradle.lockfile b/solr/modules/opentelemetry/gradle.lockfile
index 1c4a9af14ee..0174a62dbc7 100644
--- a/solr/modules/opentelemetry/gradle.lockfile
+++ b/solr/modules/opentelemetry/gradle.lockfile
@@ -113,9 +113,6 @@ org.apache.commons:commons-math3:3.6.1=jarValidation,runtimeClasspath,runtimeLib
 org.apache.curator:curator-client:5.9.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 org.apache.curator:curator-framework:5.9.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 org.apache.curator:curator-test:5.9.0=jarValidation,testRuntimeClasspath
-org.apache.httpcomponents:httpclient:4.5.14=jarValidation,testRuntimeClasspath
-org.apache.httpcomponents:httpcore:4.4.16=jarValidation,testRuntimeClasspath
-org.apache.httpcomponents:httpmime:4.5.14=jarValidation,testRuntimeClasspath
 org.apache.logging.log4j:log4j-1.2-api:2.25.3=solrPlatformLibs
 org.apache.logging.log4j:log4j-api:2.25.3=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 org.apache.logging.log4j:log4j-core:2.25.3=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
diff --git a/solr/modules/s3-repository/gradle.lockfile b/solr/modules/s3-repository/gradle.lockfile
index 33ed49188ac..02f300bdc12 100644
--- a/solr/modules/s3-repository/gradle.lockfile
+++ b/solr/modules/s3-repository/gradle.lockfile
@@ -109,7 +109,6 @@ org.apache.curator:curator-framework:5.9.0=apiHelper,compileClasspath,jarValidat
 org.apache.curator:curator-test:5.9.0=jarValidation,testRuntimeClasspath
 org.apache.httpcomponents:httpclient:4.5.14=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
 org.apache.httpcomponents:httpcore:4.4.16=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,testCompileClasspath,testRuntimeClasspath
-org.apache.httpcomponents:httpmime:4.5.14=jarValidation,testRuntimeClasspath
 org.apache.logging.log4j:log4j-1.2-api:2.25.3=solrPlatformLibs
 org.apache.logging.log4j:log4j-api:2.25.3=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 org.apache.logging.log4j:log4j-core:2.25.3=apiHelper,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
diff --git a/solr/modules/scripting/gradle.lockfile b/solr/modules/scripting/gradle.lockfile
index a7e7d265bdb..bbbaf0d040d 100644
--- a/solr/modules/scripting/gradle.lockfile
+++ b/solr/modules/scripting/gradle.lockfile
@@ -82,9 +82,6 @@ org.apache.commons:commons-math3:3.6.1=jarValidation,runtimeClasspath,runtimeLib
 org.apache.curator:curator-client:5.9.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 org.apache.curator:curator-framework:5.9.0=compileClasspath,jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testCompileClasspath,testRuntimeClasspath
 org.apache.curator:curator-test:5.9.0=jarValidation,testRuntimeClasspath
-org.apache.httpcomponents:httpclient:4.5.14=jarValidation,testRuntimeClasspath
-org.apache.httpcomponents:httpcore:4.4.16=jarValidation,testRuntimeClasspath
-org.apache.httpcomponents:httpmime:4.5.14=jarValidation,testRuntimeClasspath
 org.apache.logging.log4j:log4j-1.2-api:2.25.3=solrPlatformLibs
 org.apache.logging.log4j:log4j-api:2.25.3=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 org.apache.logging.log4j:log4j-core:2.25.3=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
diff --git a/solr/modules/sql/gradle.lockfile b/solr/modules/sql/gradle.lockfile
index cf90fc4098a..1c421ce780b 100644
--- a/solr/modules/sql/gradle.lockfile
+++ b/solr/modules/sql/gradle.lockfile
@@ -93,9 +93,6 @@ org.apache.curator:curator-test:5.9.0=jarValidation,testRuntimeClasspath
 org.apache.httpcomponents.client5:httpclient5:5.2.1=jarValidation,runtimeClasspath,runtimeLibs,testRuntimeClasspath
 org.apache.httpcomponents.core5:httpcore5-h2:5.2=jarValidation,runtimeClasspath,runtimeLibs,testRuntimeClasspath
 org.apache.httpcomponents.core5:httpcore5:5.2.3=jarValidation,runtimeClasspath,runtimeLibs,testRuntimeClasspath
-org.apache.httpcomponents:httpclient:4.5.14=jarValidation,testRuntimeClasspath
-org.apache.httpcomponents:httpcore:4.4.16=jarValidation,testRuntimeClasspath
-org.apache.httpcomponents:httpmime:4.5.14=jarValidation,testRuntimeClasspath
 org.apache.logging.log4j:log4j-1.2-api:2.25.3=solrPlatformLibs
 org.apache.logging.log4j:log4j-api:2.25.3=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
 org.apache.logging.log4j:log4j-core:2.25.3=jarValidation,runtimeClasspath,runtimeLibs,solrPlatformLibs,testRuntimeClasspath
diff --git a/solr/solr-ref-guide/gradle.lockfile b/solr/solr-ref-guide/gradle.lockfile
index 48caaa13399..28de5c8ea09 100644
--- a/solr/solr-ref-guide/gradle.lockfile
+++ b/solr/solr-ref-guide/gradle.lockfile
@@ -90,9 +90,6 @@ org.apache.curator:curator-test:5.9.0=testRuntimeClasspath
 org.apache.httpcomponents.client5:httpclient5:5.2.1=testRuntimeClasspath
 org.apache.httpcomponents.core5:httpcore5-h2:5.2=testRuntimeClasspath
 org.apache.httpcomponents.core5:httpcore5:5.2.3=testRuntimeClasspath
-org.apache.httpcomponents:httpclient:4.5.14=testRuntimeClasspath
-org.apache.httpcomponents:httpcore:4.4.16=testRuntimeClasspath
-org.apache.httpcomponents:httpmime:4.5.14=testRuntimeClasspath
 org.apache.logging.log4j:log4j-api:2.25.3=testRuntimeClasspath
 org.apache.logging.log4j:log4j-core:2.25.3=testRuntimeClasspath
 org.apache.logging.log4j:log4j-slf4j2-impl:2.25.3=testRuntimeClasspath
diff --git a/solr/solrj-jetty/gradle.lockfile b/solr/solrj-jetty/gradle.lockfile
index fb8788bfb51..828bcb3b9d7 100644
--- a/solr/solrj-jetty/gradle.lockfile
+++ b/solr/solrj-jetty/gradle.lockfile
@@ -83,9 +83,6 @@ org.apache.commons:commons-math3:3.6.1=jarValidation,testRuntimeClasspath
 org.apache.curator:curator-client:5.9.0=jarValidation,testCompileClasspath,testRuntimeClasspath
 org.apache.curator:curator-framework:5.9.0=jarValidation,testCompileClasspath,testRuntimeClasspath
 org.apache.curator:curator-test:5.9.0=jarValidation,testRuntimeClasspath
-org.apache.httpcomponents:httpclient:4.5.14=jarValidation,testRuntimeClasspath
-org.apache.httpcomponents:httpcore:4.4.16=jarValidation,testRuntimeClasspath
-org.apache.httpcomponents:httpmime:4.5.14=jarValidation,testRuntimeClasspath
 org.apache.logging.log4j:log4j-api:2.25.3=jarValidation,testRuntimeClasspath
 org.apache.logging.log4j:log4j-core:2.25.3=jarValidation,testRuntimeClasspath
 org.apache.logging.log4j:log4j-slf4j2-impl:2.25.3=jarValidation,testRuntimeClasspath
diff --git a/solr/solrj-jetty/src/test/org/apache/solr/client/solrj/jetty/HttpJettySolrClientCompatibilityTest.java b/solr/solrj-jetty/src/test/org/apache/solr/client/solrj/jetty/HttpJettySolrClientCompatibilityTest.java
index 5badd40211a..af9248be307 100644
--- a/solr/solrj-jetty/src/test/org/apache/solr/client/solrj/jetty/HttpJettySolrClientCompatibilityTest.java
+++ b/solr/solrj-jetty/src/test/org/apache/solr/client/solrj/jetty/HttpJettySolrClientCompatibilityTest.java
@@ -62,6 +62,7 @@ public class HttpJettySolrClientCompatibilityTest extends SolrTestCaseJ4 {
             .build();
     EnvUtils.setProperty(
         ALLOW_PATHS_SYSPROP, ExternalPaths.SERVER_HOME.toAbsolutePath().toString());
+    System.setProperty("solr.http1", "true");
     solrTestRule.startSolr(createTempDir(), new Properties(), jettyConfig);
     solrTestRule.newCollection().withConfigSet(ExternalPaths.TECHPRODUCTS_CONFIGSET).create();
 
@@ -114,6 +115,7 @@ public class HttpJettySolrClientCompatibilityTest extends SolrTestCaseJ4 {
 
     EnvUtils.setProperty(
         ALLOW_PATHS_SYSPROP, ExternalPaths.SERVER_HOME.toAbsolutePath().toString());
+    System.setProperty("solr.http1", "true");
     solrTestRule.startSolr(createTempDir(), new Properties(), jettyConfig);
     solrTestRule.newCollection().withConfigSet(ExternalPaths.TECHPRODUCTS_CONFIGSET).create();
 
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/PreemptiveBasicAuthClientCustomizerTest.java b/solr/solrj-jetty/src/test/org/apache/solr/client/solrj/jetty/PreemptiveBasicAuthClientCustomizerTest.java
similarity index 88%
rename from solr/solrj/src/test/org/apache/solr/client/solrj/impl/PreemptiveBasicAuthClientCustomizerTest.java
rename to solr/solrj-jetty/src/test/org/apache/solr/client/solrj/jetty/PreemptiveBasicAuthClientCustomizerTest.java
index ae76c810232..cf5af87a16a 100644
--- a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/PreemptiveBasicAuthClientCustomizerTest.java
+++ b/solr/solrj-jetty/src/test/org/apache/solr/client/solrj/jetty/PreemptiveBasicAuthClientCustomizerTest.java
@@ -24,7 +24,7 @@ import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.Properties;
 import org.apache.solr.SolrTestCase;
-import org.apache.solr.client.solrj.apache.HttpClientUtil;
+import org.apache.solr.client.solrj.impl.SolrHttpConstants;
 
 public class PreemptiveBasicAuthClientCustomizerTest extends SolrTestCase {
 
@@ -70,8 +70,10 @@ public class PreemptiveBasicAuthClientCustomizerTest extends SolrTestCase {
         PreemptiveBasicAuthClientCustomizer.SYS_PROP_BASIC_AUTH_CREDENTIALS, "foo:bar");
     PreemptiveBasicAuthClientCustomizer.CredentialsResolver credentialsResolver =
         new PreemptiveBasicAuthClientCustomizer.CredentialsResolver();
-    assertEquals("foo", credentialsResolver.defaultParams.get(HttpClientUtil.PROP_BASIC_AUTH_USER));
-    assertEquals("bar", credentialsResolver.defaultParams.get(HttpClientUtil.PROP_BASIC_AUTH_PASS));
+    assertEquals(
+        "foo", credentialsResolver.defaultParams.get(SolrHttpConstants.PROP_BASIC_AUTH_USER));
+    assertEquals(
+        "bar", credentialsResolver.defaultParams.get(SolrHttpConstants.PROP_BASIC_AUTH_PASS));
   }
 
   public void testCredentialsFromConfigFile() throws IOException {
@@ -86,7 +88,9 @@ public class PreemptiveBasicAuthClientCustomizerTest extends SolrTestCase {
         PreemptiveBasicAuthClientCustomizer.SYS_PROP_HTTP_CLIENT_CONFIG, f.toString());
     PreemptiveBasicAuthClientCustomizer.CredentialsResolver credentialsResolver =
         new PreemptiveBasicAuthClientCustomizer.CredentialsResolver();
-    assertEquals("foo", credentialsResolver.defaultParams.get(HttpClientUtil.PROP_BASIC_AUTH_USER));
-    assertEquals("bar", credentialsResolver.defaultParams.get(HttpClientUtil.PROP_BASIC_AUTH_PASS));
+    assertEquals(
+        "foo", credentialsResolver.defaultParams.get(SolrHttpConstants.PROP_BASIC_AUTH_USER));
+    assertEquals(
+        "bar", credentialsResolver.defaultParams.get(SolrHttpConstants.PROP_BASIC_AUTH_PASS));
   }
 }
diff --git a/solr/solrj-streaming/build.gradle b/solr/solrj-streaming/build.gradle
index dc321b3ced7..710bb45ed61 100644
--- a/solr/solrj-streaming/build.gradle
+++ b/solr/solrj-streaming/build.gradle
@@ -30,6 +30,7 @@ dependencies {
   testImplementation project(':solr:test-framework')
   testImplementation project(':solr:core')
   testImplementation project(':solr:solrj-zookeeper')
+  testImplementation project(':solr:solrj-jetty')
 
   testRuntimeOnly project(':solr:modules:sql')
   testRuntimeOnly libs.hsqldb.hsqldb // runtime because via JDBC reflection
diff --git a/solr/solrj-streaming/gradle.lockfile b/solr/solrj-streaming/gradle.lockfile
index 8c4849cdd99..ecfdb32e82e 100644
--- a/solr/solrj-streaming/gradle.lockfile
+++ b/solr/solrj-streaming/gradle.lockfile
@@ -91,9 +91,6 @@ org.apache.curator:curator-test:5.9.0=jarValidation,testRuntimeClasspath
 org.apache.httpcomponents.client5:httpclient5:5.2.1=jarValidation,testRuntimeClasspath
 org.apache.httpcomponents.core5:httpcore5-h2:5.2=jarValidation,testRuntimeClasspath
 org.apache.httpcomponents.core5:httpcore5:5.2.3=jarValidation,testRuntimeClasspath
-org.apache.httpcomponents:httpclient:4.5.14=jarValidation,testRuntimeClasspath
-org.apache.httpcomponents:httpcore:4.4.16=jarValidation,testRuntimeClasspath
-org.apache.httpcomponents:httpmime:4.5.14=jarValidation,testRuntimeClasspath
 org.apache.logging.log4j:log4j-api:2.25.3=jarValidation,testRuntimeClasspath
 org.apache.logging.log4j:log4j-core:2.25.3=jarValidation,testRuntimeClasspath
 org.apache.logging.log4j:log4j-slf4j2-impl:2.25.3=jarValidation,testRuntimeClasspath
diff --git a/solr/solrj-streaming/src/test/org/apache/solr/client/solrj/io/graph/GraphExpressionTest.java b/solr/solrj-streaming/src/test/org/apache/solr/client/solrj/io/graph/GraphExpressionTest.java
index a33c66ff019..4dfcbf3d0d7 100644
--- a/solr/solrj-streaming/src/test/org/apache/solr/client/solrj/io/graph/GraphExpressionTest.java
+++ b/solr/solrj-streaming/src/test/org/apache/solr/client/solrj/io/graph/GraphExpressionTest.java
@@ -18,9 +18,6 @@
 package org.apache.solr.client.solrj.io.graph;
 
 import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashSet;
@@ -57,7 +54,6 @@ import org.apache.solr.cloud.AbstractFullDistribZkTestBase;
 import org.apache.solr.cloud.SolrCloudTestCase;
 import org.apache.solr.common.params.ModifiableSolrParams;
 import org.apache.solr.common.util.NamedList;
-import org.apache.solr.embedded.JettySolrRunner;
 import org.apache.solr.util.BaseTestHarness;
 import org.junit.Before;
 import org.junit.BeforeClass;
@@ -1237,13 +1233,8 @@ public class GraphExpressionTest extends SolrCloudTestCase {
         .add(id, "5", "from_s", "jim", "to_s", "ann", "message_t", "Hello steve")
         .commit(cluster.getSolrClient(), COLLECTION);
 
-    commit();
+    SolrClient client = cluster.getRandomJetty(random()).getSolrClient();
 
-    List<JettySolrRunner> runners = cluster.getJettySolrRunners();
-    JettySolrRunner runner = runners.get(0);
-    String url = runner.getBaseUrl().toString();
-
-    SolrClient client = getHttpSolrClient(url);
     ModifiableSolrParams params = new ModifiableSolrParams();
 
     String expr =
@@ -1261,9 +1252,7 @@ public class GraphExpressionTest extends SolrCloudTestCase {
 
     NamedList<Object> genericResponse = client.request(query);
 
-    InputStream stream = (InputStream) genericResponse.get(InputStreamResponseParser.STREAM_KEY);
-    InputStreamReader reader = new InputStreamReader(stream, StandardCharsets.UTF_8);
-    String xml = readString(reader);
+    String xml = InputStreamResponseParser.consumeResponseToString(genericResponse);
     // Validate the nodes
     String error =
         BaseTestHarness.validateXPath(
@@ -1292,16 +1281,6 @@ public class GraphExpressionTest extends SolrCloudTestCase {
     client.close();
   }
 
-  private String readString(InputStreamReader reader) throws Exception {
-    StringBuilder builder = new StringBuilder();
-    int c = 0;
-    while ((c = reader.read()) != -1) {
-      builder.append(((char) c));
-    }
-
-    return builder.toString();
-  }
-
   protected List<Tuple> getTuples(TupleStream tupleStream) throws IOException {
     tupleStream.open();
     List<Tuple> tuples = new ArrayList<>();
diff --git a/solr/solrj-zookeeper/gradle.lockfile b/solr/solrj-zookeeper/gradle.lockfile
index 4ae0314a45f..bcb085e10bc 100644
--- a/solr/solrj-zookeeper/gradle.lockfile
+++ b/solr/solrj-zookeeper/gradle.lockfile
@@ -99,9 +99,6 @@ org.apache.commons:commons-math3:3.6.1=jarValidation,testRuntimeClasspath
 org.apache.curator:curator-client:5.9.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
 org.apache.curator:curator-framework:5.9.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
 org.apache.curator:curator-test:5.9.0=jarValidation,testRuntimeClasspath
-org.apache.httpcomponents:httpclient:4.5.14=jarValidation,testRuntimeClasspath
-org.apache.httpcomponents:httpcore:4.4.16=jarValidation,testRuntimeClasspath
-org.apache.httpcomponents:httpmime:4.5.14=jarValidation,testRuntimeClasspath
 org.apache.logging.log4j:log4j-api:2.25.3=jarValidation,testRuntimeClasspath
 org.apache.logging.log4j:log4j-core:2.25.3=jarValidation,testRuntimeClasspath
 org.apache.logging.log4j:log4j-slf4j2-impl:2.25.3=jarValidation,testRuntimeClasspath
diff --git a/solr/solrj/gradle.lockfile b/solr/solrj/gradle.lockfile
index cf92fdbe4e6..4ed1261d2bf 100644
--- a/solr/solrj/gradle.lockfile
+++ b/solr/solrj/gradle.lockfile
@@ -84,9 +84,6 @@ org.apache.commons:commons-math3:3.6.1=jarValidation,testRuntimeClasspath
 org.apache.curator:curator-client:5.9.0=jarValidation,testCompileClasspath,testRuntimeClasspath
 org.apache.curator:curator-framework:5.9.0=jarValidation,testCompileClasspath,testRuntimeClasspath
 org.apache.curator:curator-test:5.9.0=jarValidation,testRuntimeClasspath
-org.apache.httpcomponents:httpclient:4.5.14=jarValidation,testRuntimeClasspath
-org.apache.httpcomponents:httpcore:4.4.16=jarValidation,testRuntimeClasspath
-org.apache.httpcomponents:httpmime:4.5.14=jarValidation,testRuntimeClasspath
 org.apache.logging.log4j:log4j-api:2.25.3=jarValidation,testRuntimeClasspath
 org.apache.logging.log4j:log4j-core:2.25.3=jarValidation,testRuntimeClasspath
 org.apache.logging.log4j:log4j-slf4j2-impl:2.25.3=jarValidation,testRuntimeClasspath
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/SolrExampleBinaryTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/SolrExampleBinaryTest.java
index 102a2beda12..c58f51cf379 100644
--- a/solr/solrj/src/test/org/apache/solr/client/solrj/SolrExampleBinaryTest.java
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/SolrExampleBinaryTest.java
@@ -17,7 +17,6 @@
 package org.apache.solr.client.solrj;
 
 import org.apache.solr.SolrTestCaseJ4.SuppressSSL;
-import org.apache.solr.client.solrj.apache.HttpSolrClient;
 import org.apache.solr.client.solrj.request.JavaBinRequestWriter;
 import org.apache.solr.client.solrj.response.JavaBinResponseParser;
 
@@ -30,9 +29,8 @@ public class SolrExampleBinaryTest extends SolrExampleTests {
 
   @Override
   public SolrClient createNewSolrClient() {
-    return new HttpSolrClient.Builder(solrTestRule.getBaseUrl())
-        .withDefaultCollection(DEFAULT_TEST_COLLECTION_NAME)
-        .allowMultiPartPost(random().nextBoolean())
+    return solrTestRule
+        .newSolrClientBuilder()
         .withRequestWriter(new JavaBinRequestWriter())
         .withResponseParser(new JavaBinResponseParser())
         .build();
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/SolrExampleCborTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/SolrExampleCborTest.java
index 59031c28b7e..6456aa5824c 100644
--- a/solr/solrj/src/test/org/apache/solr/client/solrj/SolrExampleCborTest.java
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/SolrExampleCborTest.java
@@ -27,7 +27,6 @@ import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import org.apache.solr.client.solrj.apache.HttpSolrClient;
 import org.apache.solr.client.solrj.request.JavaBinRequestWriter;
 import org.apache.solr.client.solrj.request.RequestWriter;
 import org.apache.solr.client.solrj.request.UpdateRequest;
@@ -47,9 +46,8 @@ public class SolrExampleCborTest extends SolrExampleTests {
 
   @Override
   public SolrClient createNewSolrClient() {
-    return new HttpSolrClient.Builder(solrTestRule.getBaseUrl())
-        .withDefaultCollection(DEFAULT_TEST_COLLECTION_NAME)
-        .allowMultiPartPost(random().nextBoolean())
+    return solrTestRule
+        .newSolrClientBuilder()
         .withRequestWriter(cborRequestWriter())
         .withResponseParser(cborResponseParser())
         .build();
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/SolrExampleTests.java b/solr/solrj/src/test/org/apache/solr/client/solrj/SolrExampleTests.java
index 50cf5879a8f..ca9ef93e524 100644
--- a/solr/solrj/src/test/org/apache/solr/client/solrj/SolrExampleTests.java
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/SolrExampleTests.java
@@ -44,10 +44,10 @@ import java.util.Map;
 import java.util.Random;
 import org.apache.lucene.tests.util.TestUtil;
 import org.apache.solr.SolrTestCaseJ4.SuppressSSL;
-import org.apache.solr.client.solrj.apache.HttpSolrClient;
 import org.apache.solr.client.solrj.embedded.EmbeddedSolrServer;
 import org.apache.solr.client.solrj.embedded.SolrExampleStreamingHttp2Test;
 import org.apache.solr.client.solrj.embedded.SolrExampleStreamingTest.ErrorTrackingConcurrentUpdateSolrClient;
+import org.apache.solr.client.solrj.jetty.HttpJettySolrClient;
 import org.apache.solr.client.solrj.request.AbstractUpdateRequest;
 import org.apache.solr.client.solrj.request.AbstractUpdateRequest.ACTION;
 import org.apache.solr.client.solrj.request.ContentStreamUpdateRequest;
@@ -722,7 +722,7 @@ public abstract class SolrExampleTests extends SolrExampleTestsBase {
     doc.addField("id", "DOCID2");
     doc.addField("name", "hello");
 
-    if (client instanceof HttpSolrClient) {
+    if (client instanceof HttpJettySolrClient) {
       ex = expectThrows(SolrException.class, () -> client.add(doc));
       assertEquals(400, ex.code());
       assertTrue(ex.getMessage().indexOf("contains multiple values for uniqueKey") > 0);
@@ -2386,7 +2386,7 @@ public abstract class SolrExampleTests extends SolrExampleTestsBase {
     doc.addField(field, oper);
     try {
       client.add(doc);
-      if (client instanceof HttpSolrClient) {
+      if (client instanceof HttpJettySolrClient) {
         // XXX concurrent client reports exceptions differently
         fail("Operation should throw an exception!");
       } else if (client instanceof ErrorTrackingConcurrentUpdateSolrClient concurrentClient) {
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/SolrExampleXMLTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/SolrExampleXMLTest.java
index 1b0efddca91..64a7f3d483c 100644
--- a/solr/solrj/src/test/org/apache/solr/client/solrj/SolrExampleXMLTest.java
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/SolrExampleXMLTest.java
@@ -17,7 +17,6 @@
 package org.apache.solr.client.solrj;
 
 import org.apache.solr.SolrTestCaseJ4.SuppressSSL;
-import org.apache.solr.client.solrj.apache.HttpSolrClient;
 import org.apache.solr.client.solrj.request.XMLRequestWriter;
 import org.apache.solr.client.solrj.response.XMLResponseParser;
 
@@ -30,9 +29,8 @@ public class SolrExampleXMLTest extends SolrExampleTests {
 
   @Override
   public SolrClient createNewSolrClient() {
-    return new HttpSolrClient.Builder(solrTestRule.getBaseUrl())
-        .withDefaultCollection(DEFAULT_TEST_COLLECTION_NAME)
-        .allowMultiPartPost(random().nextBoolean())
+    return solrTestRule
+        .newSolrClientBuilder()
         .withRequestWriter(new XMLRequestWriter())
         .withResponseParser(new XMLResponseParser())
         .build();
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/TestBatchUpdate.java b/solr/solrj/src/test/org/apache/solr/client/solrj/TestBatchUpdate.java
index d64b999560b..46c929c50b0 100644
--- a/solr/solrj/src/test/org/apache/solr/client/solrj/TestBatchUpdate.java
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/TestBatchUpdate.java
@@ -22,8 +22,8 @@ import java.io.IOException;
 import java.util.Iterator;
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.SolrTestCaseJ4.SuppressSSL;
-import org.apache.solr.client.solrj.apache.HttpSolrClient;
 import org.apache.solr.client.solrj.beans.Field;
+import org.apache.solr.client.solrj.jetty.HttpJettySolrClient;
 import org.apache.solr.client.solrj.request.JavaBinRequestWriter;
 import org.apache.solr.client.solrj.request.SolrQuery;
 import org.apache.solr.client.solrj.request.XMLRequestWriter;
@@ -60,7 +60,8 @@ public class TestBatchUpdate extends SolrTestCaseJ4 {
   @Test
   public void testWithXml() throws Exception {
     try (SolrClient client =
-        new HttpSolrClient.Builder(solrTestRule.getBaseUrl())
+        new HttpJettySolrClient.Builder(solrTestRule.getJetty().getBaseUrl().toString())
+            .withHttpClient(solrTestRule.getJetty().getSolrClient())
             .withDefaultCollection(DEFAULT_TEST_CORENAME)
             .withRequestWriter(new XMLRequestWriter())
             .build()) {
@@ -72,10 +73,7 @@ public class TestBatchUpdate extends SolrTestCaseJ4 {
   @Test
   public void testWithBinary() throws Exception {
     try (SolrClient client =
-        new HttpSolrClient.Builder(solrTestRule.getBaseUrl())
-            .withDefaultCollection(DEFAULT_TEST_CORENAME)
-            .withRequestWriter(new JavaBinRequestWriter())
-            .build()) {
+        solrTestRule.newSolrClientBuilder().withRequestWriter(new JavaBinRequestWriter()).build()) {
       client.deleteByQuery("*:*"); // delete everything!
       doIt(client);
     }
@@ -84,10 +82,7 @@ public class TestBatchUpdate extends SolrTestCaseJ4 {
   @Test
   public void testWithBinaryBean() throws Exception {
     try (SolrClient client =
-        new HttpSolrClient.Builder(solrTestRule.getBaseUrl())
-            .withDefaultCollection(DEFAULT_TEST_CORENAME)
-            .withRequestWriter(new JavaBinRequestWriter())
-            .build()) {
+        solrTestRule.newSolrClientBuilder().withRequestWriter(new JavaBinRequestWriter()).build()) {
       client.deleteByQuery("*:*"); // delete everything!
       final int[] counter = new int[1];
       counter[0] = 0;
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/TestSolrJErrorHandling.java b/solr/solrj/src/test/org/apache/solr/client/solrj/TestSolrJErrorHandling.java
index 023874d77dd..8f0a6b913a1 100644
--- a/solr/solrj/src/test/org/apache/solr/client/solrj/TestSolrJErrorHandling.java
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/TestSolrJErrorHandling.java
@@ -39,7 +39,6 @@ import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.atomic.AtomicInteger;
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.SolrTestCaseJ4.SuppressSSL;
-import org.apache.solr.client.solrj.apache.HttpSolrClient;
 import org.apache.solr.client.solrj.request.JavaBinRequestWriter;
 import org.apache.solr.client.solrj.request.XMLRequestWriter;
 import org.apache.solr.client.solrj.response.QueryResponse;
@@ -114,10 +113,7 @@ public class TestSolrJErrorHandling extends SolrTestCaseJ4 {
   @Test
   public void testWithXml() throws Exception {
     try (SolrClient client =
-        new HttpSolrClient.Builder(solrTestRule.getBaseUrl())
-            .withDefaultCollection(DEFAULT_TEST_CORENAME)
-            .withRequestWriter(new XMLRequestWriter())
-            .build()) {
+        solrTestRule.newSolrClientBuilder().withRequestWriter(new XMLRequestWriter()).build()) {
       client.deleteByQuery("*:*"); // delete everything!
       doIt(client);
     }
@@ -126,10 +122,7 @@ public class TestSolrJErrorHandling extends SolrTestCaseJ4 {
   @Test
   public void testWithBinary() throws Exception {
     try (SolrClient client =
-        new HttpSolrClient.Builder(solrTestRule.getBaseUrl())
-            .withDefaultCollection(DEFAULT_TEST_CORENAME)
-            .withRequestWriter(new JavaBinRequestWriter())
-            .build()) {
+        solrTestRule.newSolrClientBuilder().withRequestWriter(new JavaBinRequestWriter()).build()) {
       client.deleteByQuery("*:*"); // delete everything!
       doIt(client);
     }
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/embedded/SolrExampleStreamingBinaryTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/embedded/SolrExampleStreamingBinaryTest.java
index 3a66dc2c7bc..32aa7164cd1 100644
--- a/solr/solrj/src/test/org/apache/solr/client/solrj/embedded/SolrExampleStreamingBinaryTest.java
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/embedded/SolrExampleStreamingBinaryTest.java
@@ -20,6 +20,7 @@ import java.util.ArrayList;
 import java.util.List;
 import org.apache.solr.SolrTestCaseJ4.SuppressSSL;
 import org.apache.solr.client.solrj.SolrClient;
+import org.apache.solr.client.solrj.jetty.HttpJettySolrClient;
 import org.apache.solr.client.solrj.request.JavaBinRequestWriter;
 import org.apache.solr.client.solrj.request.SolrQuery;
 import org.apache.solr.client.solrj.response.JavaBinResponseParser;
@@ -35,15 +36,18 @@ public class SolrExampleStreamingBinaryTest extends SolrExampleStreamingTest {
   @Override
   public SolrClient createNewSolrClient() {
 
+    var httpJettyClient =
+        new HttpJettySolrClient.Builder()
+            .withResponseParser(new JavaBinResponseParser())
+            .withRequestWriter(new JavaBinRequestWriter())
+            .build();
     SolrClient client =
-        new ErrorTrackingConcurrentUpdateSolrClient.Builder(solrTestRule.getBaseUrl())
+        new ErrorTrackingConcurrentUpdateSolrClient.Builder(
+                solrTestRule.getBaseUrl(), httpJettyClient, true)
             .withDefaultCollection(DEFAULT_TEST_CORENAME)
             .withQueueSize(2)
             .withThreadCount(5)
-            .withResponseParser(new JavaBinResponseParser())
-            .withRequestWriter(new JavaBinRequestWriter())
             .build();
-
     return client;
   }
 
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/embedded/SolrExampleStreamingTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/embedded/SolrExampleStreamingTest.java
index 0625ab90591..be869ada9bf 100644
--- a/solr/solrj/src/test/org/apache/solr/client/solrj/embedded/SolrExampleStreamingTest.java
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/embedded/SolrExampleStreamingTest.java
@@ -22,7 +22,8 @@ import java.util.EnumSet;
 import java.util.List;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrExampleTests;
-import org.apache.solr.client.solrj.apache.ConcurrentUpdateSolrClient;
+import org.apache.solr.client.solrj.jetty.ConcurrentUpdateJettySolrClient;
+import org.apache.solr.client.solrj.jetty.HttpJettySolrClient;
 import org.apache.solr.client.solrj.request.AbstractUpdateRequest;
 import org.apache.solr.client.solrj.request.UpdateRequest;
 import org.apache.solr.client.solrj.request.XMLRequestWriter;
@@ -38,12 +39,16 @@ public class SolrExampleStreamingTest extends SolrExampleTests {
   @Override
   public SolrClient createNewSolrClient() {
     // smaller queue size hits locks more often
-    return new ErrorTrackingConcurrentUpdateSolrClient.Builder(solrTestRule.getBaseUrl())
+    var httpJettyClient =
+        new HttpJettySolrClient.Builder()
+            .withResponseParser(new XMLResponseParser())
+            .withRequestWriter(new XMLRequestWriter())
+            .build();
+    return new ErrorTrackingConcurrentUpdateSolrClient.Builder(
+            solrTestRule.getBaseUrl(), httpJettyClient, true)
         .withDefaultCollection(DEFAULT_TEST_COLLECTION_NAME)
         .withQueueSize(2)
         .withThreadCount(5)
-        .withResponseParser(new XMLResponseParser())
-        .withRequestWriter(new XMLRequestWriter())
         .build();
   }
 
@@ -52,8 +57,9 @@ public class SolrExampleStreamingTest extends SolrExampleTests {
     // TODO these failures are not the same as recorded by the client
     final List<Throwable> failures = new ArrayList<>();
     final String serverUrl = solrTestRule.getBaseUrl() + "/" + DEFAULT_TEST_CORENAME;
-    try (ConcurrentUpdateSolrClient concurrentClient =
-        new FailureRecordingConcurrentUpdateSolrClient.Builder(serverUrl)
+    try (var concurrentClient =
+        new FailureRecordingConcurrentUpdateSolrClient.Builder(
+                serverUrl, (HttpJettySolrClient) solrTestRule.getSolrClient(), false)
             .withQueueSize(2)
             .withThreadCount(2)
             .build()) {
@@ -79,7 +85,7 @@ public class SolrExampleStreamingTest extends SolrExampleTests {
     }
   }
 
-  static class FailureRecordingConcurrentUpdateSolrClient extends ConcurrentUpdateSolrClient {
+  static class FailureRecordingConcurrentUpdateSolrClient extends ConcurrentUpdateJettySolrClient {
     private final List<Throwable> failures = new ArrayList<>();
 
     public FailureRecordingConcurrentUpdateSolrClient(Builder builder) {
@@ -91,9 +97,9 @@ public class SolrExampleStreamingTest extends SolrExampleTests {
       failures.add(ex);
     }
 
-    static class Builder extends ConcurrentUpdateSolrClient.Builder {
-      public Builder(String baseSolrUrl) {
-        super(baseSolrUrl);
+    static class Builder extends ConcurrentUpdateJettySolrClient.Builder {
+      public Builder(String baseSolrUrl, HttpJettySolrClient solrClient, boolean closeClient) {
+        super(baseSolrUrl, solrClient, closeClient);
       }
 
       @Override
@@ -103,7 +109,8 @@ public class SolrExampleStreamingTest extends SolrExampleTests {
     }
   }
 
-  public static class ErrorTrackingConcurrentUpdateSolrClient extends ConcurrentUpdateSolrClient {
+  public static class ErrorTrackingConcurrentUpdateSolrClient
+      extends ConcurrentUpdateJettySolrClient {
     public Throwable lastError = null;
 
     public ErrorTrackingConcurrentUpdateSolrClient(Builder builder) {
@@ -115,10 +122,10 @@ public class SolrExampleStreamingTest extends SolrExampleTests {
       lastError = ex;
     }
 
-    public static class Builder extends ConcurrentUpdateSolrClient.Builder {
+    public static class Builder extends ConcurrentUpdateJettySolrClient.Builder {
 
-      public Builder(String baseSolrUrl) {
-        super(baseSolrUrl);
+      public Builder(String baseSolrUrl, HttpJettySolrClient solrClient, boolean closeClient) {
+        super(baseSolrUrl, solrClient, closeClient);
       }
 
       @Override
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudHttp2SolrClientBadInputTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudHttp2SolrClientBadInputTest.java
index 5f2fc5ee67a..f08cfcce42f 100644
--- a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudHttp2SolrClientBadInputTest.java
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudHttp2SolrClientBadInputTest.java
@@ -37,7 +37,7 @@ public class CloudHttp2SolrClientBadInputTest extends SolrCloudTestCase {
 
   @Test
   public void testDeleteByIdReportsInvalidIdLists() throws Exception {
-    try (SolrClient client = new RandomizingCloudHttp2SolrClientBuilder(cluster).build()) {
+    try (SolrClient client = new RandomizingCloudSolrClientBuilder(cluster).build()) {
       assertExceptionThrownWithMessageContaining(
           IllegalArgumentException.class,
           List.of("ids", "null"),
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudHttp2SolrClientTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudHttp2SolrClientTest.java
index 3d2f32fc2a7..88ae42fc32c 100644
--- a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudHttp2SolrClientTest.java
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudHttp2SolrClientTest.java
@@ -34,6 +34,7 @@ import java.util.Locale;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 import org.apache.lucene.tests.util.TestUtil;
 import org.apache.solr.client.solrj.RemoteSolrException;
@@ -1062,7 +1063,7 @@ public class CloudHttp2SolrClientTest extends SolrCloudTestCase {
             .sendDirectUpdatesToAnyShardReplica()
             .withDefaultCollection(COL)
             // don't let collection cache entries get expired, even on a slow machine...
-            .withCollectionCacheTtl(Integer.MAX_VALUE)
+            .withCollectionCacheTtl(1, TimeUnit.DAYS)
             .build()) {
 
       // do a query to populate stale_client's cache...
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudSolrClientCacheTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudSolrClientCacheTest.java
index 8d279ea773b..706e334f1d1 100644
--- a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudSolrClientCacheTest.java
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudSolrClientCacheTest.java
@@ -47,7 +47,7 @@ import java.util.function.Supplier;
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.client.solrj.SolrRequest;
 import org.apache.solr.client.solrj.SolrServerException;
-import org.apache.solr.client.solrj.apache.LBHttpSolrClient;
+import org.apache.solr.client.solrj.jetty.LBJettySolrClient;
 import org.apache.solr.client.solrj.request.UpdateRequest;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.cloud.ClusterState;
@@ -95,13 +95,16 @@ public class CloudSolrClientCacheTest extends SolrTestCaseJ4 {
     NamedList<Object> okResponse = new NamedList<>();
     okResponse.add("responseHeader", new NamedList<>(Map.of("status", 0)));
 
-    LBHttpSolrClient mockLbclient = getMockLbHttpSolrClient(responses);
+    LBJettySolrClient mockLbclient = getMockLbHttpSolrClient(responses);
     AtomicInteger lbhttpRequestCount = new AtomicInteger();
     try (ClusterStateProvider clusterStateProvider = getStateProvider(livenodes, refs);
         CloudSolrClient cloudClient =
-            new RandomizingCloudSolrClientBuilder(clusterStateProvider)
-                .withLBHttpSolrClient(mockLbclient)
-                .build()) {
+            new RandomizingCloudSolrClientBuilder(clusterStateProvider) {
+              @Override
+              protected LBSolrClient createOrGetLbClient(HttpSolrClient myClient) {
+                return mockLbclient;
+              }
+            }.build()) {
       livenodes.addAll(Set.of("192.168.1.108:7574_solr", "192.168.1.108:8983_solr"));
       ClusterState cs =
           ClusterState.createFromJson(
@@ -297,9 +300,9 @@ public class CloudSolrClientCacheTest extends SolrTestCaseJ4 {
   }
 
   @SuppressWarnings({"unchecked"})
-  private LBHttpSolrClient getMockLbHttpSolrClient(Map<String, Function<?, ?>> responses)
+  private LBJettySolrClient getMockLbHttpSolrClient(Map<String, Function<?, ?>> responses)
       throws Exception {
-    LBHttpSolrClient mockLbclient = mock(LBHttpSolrClient.class);
+    var mockLbclient = mock(LBJettySolrClient.class);
 
     when(mockLbclient.request(any(LBSolrClient.Req.class)))
         .then(
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/LBHttpSolrClientBadInputTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/LBHttpSolrClientBadInputTest.java
index e82dcb1ca9f..ae429783d4f 100644
--- a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/LBHttpSolrClientBadInputTest.java
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/LBHttpSolrClientBadInputTest.java
@@ -23,7 +23,7 @@ import java.util.ArrayList;
 import java.util.List;
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.client.solrj.SolrClient;
-import org.apache.solr.client.solrj.apache.LBHttpSolrClient;
+import org.apache.solr.client.solrj.jetty.LBJettySolrClient;
 import org.apache.solr.common.util.EnvUtils;
 import org.apache.solr.util.ExternalPaths;
 import org.apache.solr.util.SolrJettyTestRule;
@@ -51,8 +51,7 @@ public class LBHttpSolrClientBadInputTest extends SolrTestCaseJ4 {
   @Test
   public void testDeleteByIdReportsInvalidIdLists() throws Exception {
     try (SolrClient client =
-        new LBHttpSolrClient.Builder()
-            .withBaseEndpoint(solrTestRule.getBaseUrl())
+        new LBJettySolrClient.Builder(solrTestRule.getJetty().getSolrClient())
             .withDefaultCollection(ANY_COLLECTION)
             .build()) {
       assertExceptionThrownWithMessageContaining(
@@ -82,7 +81,7 @@ public class LBHttpSolrClientBadInputTest extends SolrTestCaseJ4 {
     }
 
     try (SolrClient client =
-        new LBHttpSolrClient.Builder().withBaseEndpoint(solrTestRule.getBaseUrl()).build()) {
+        new LBJettySolrClient.Builder(solrTestRule.getJetty().getSolrClient()).build()) {
       assertExceptionThrownWithMessageContaining(
           IllegalArgumentException.class,
           List.of("ids", "null"),
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/SendUpdatesToLeadersOverrideTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/SendUpdatesToLeadersOverrideTest.java
index 5d31f782379..ab27c827c62 100644
--- a/solr/solrj/src/test/org/apache/solr/client/solrj/impl/SendUpdatesToLeadersOverrideTest.java
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/impl/SendUpdatesToLeadersOverrideTest.java
@@ -31,7 +31,6 @@ import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
 import org.apache.solr.client.solrj.SolrClient;
-import org.apache.solr.client.solrj.apache.CloudLegacySolrClient;
 import org.apache.solr.client.solrj.request.AbstractUpdateRequest;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
 import org.apache.solr.client.solrj.request.UpdateRequest;
@@ -207,12 +206,6 @@ public class SendUpdatesToLeadersOverrideTest extends SolrCloudTestCase {
   }
 
   public void testBuilderImplicitBehavior() throws Exception {
-    try (CloudSolrClient client =
-        new CloudLegacySolrClient.Builder(
-                List.of(cluster.getZkServer().getZkAddress()), Optional.empty())
-            .build()) {
-      assertTrue(client.isUpdatesToLeaders());
-    }
     try (CloudSolrClient client =
         new CloudSolrClient.Builder(List.of(cluster.getZkServer().getZkAddress()), Optional.empty())
             .build()) {
@@ -220,28 +213,6 @@ public class SendUpdatesToLeadersOverrideTest extends SolrCloudTestCase {
     }
   }
 
-  public void testLegacyClientThatDefaultsToLeaders() throws Exception {
-    try (CloudSolrClient client =
-        new CloudLegacySolrClient.Builder(
-                List.of(cluster.getZkServer().getZkAddress()), Optional.empty())
-            .sendUpdatesOnlyToShardLeaders()
-            .build()) {
-      checkUpdatesDefaultToLeaders(client);
-      checkUpdatesWithSendToLeadersFalse(client);
-    }
-  }
-
-  public void testLegacyClientThatDoesNotDefaultToLeaders() throws Exception {
-    try (CloudSolrClient client =
-        new CloudLegacySolrClient.Builder(
-                List.of(cluster.getZkServer().getZkAddress()), Optional.empty())
-            .sendUpdatesToAnyReplica()
-            .build()) {
-      checkUpdatesWithShardsPrefPull(client);
-      checkUpdatesWithSendToLeadersFalse(client);
-    }
-  }
-
   public void testHttp2ClientThatDefaultsToLeaders() throws Exception {
     try (CloudSolrClient client =
         new CloudSolrClient.Builder(List.of(cluster.getZkServer().getZkAddress()), Optional.empty())
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/request/TestV2Request.java b/solr/solrj/src/test/org/apache/solr/client/solrj/request/TestV2Request.java
index 09c11077fa8..aebadc76a42 100644
--- a/solr/solrj/src/test/org/apache/solr/client/solrj/request/TestV2Request.java
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/request/TestV2Request.java
@@ -24,7 +24,6 @@ import java.util.List;
 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.apache.HttpSolrClient;
 import org.apache.solr.client.solrj.response.V2Response;
 import org.apache.solr.cloud.SolrCloudTestCase;
 import org.apache.solr.common.cloud.ClusterState;
@@ -84,10 +83,7 @@ public class TestV2Request extends SolrCloudTestCase {
 
   @Test
   public void testHttpSolrClient() throws Exception {
-    SolrClient solrClient =
-        new HttpSolrClient.Builder(cluster.getJettySolrRunner(0).getBaseUrl().toString()).build();
-    doTest(solrClient);
-    solrClient.close();
+    doTest(cluster.getJettySolrRunner(0).getSolrClient());
   }
 
   @Test
@@ -149,22 +145,21 @@ public class TestV2Request extends SolrCloudTestCase {
     cs.getCollection("v2forward").forEachReplica((s, replica) -> node[0] = replica.getNodeName());
 
     // find a node that does not have a replica for this collection
-    final String[] testNode = new String[1];
-    cs.getLiveNodes()
-        .forEach(
-            s -> {
-              if (!s.equals(node[0])) testNode[0] = s;
-            });
-
-    String testServer = cluster.getZkStateReader().getBaseUrlForNodeName(testNode[0]);
+    String testNode =
+        cs.getLiveNodes().stream().filter(s -> !s.equals(node[0])).findAny().orElseThrow();
+
     V2Request v2r =
         new V2Request.Builder("/c/v2forward/_introspect")
             .withMethod(SolrRequest.METHOD.GET)
             .build();
 
-    try (SolrClient client1 = new HttpSolrClient.Builder().withBaseSolrUrl(testServer).build()) {
-      V2Response rsp = v2r.process(client1);
-      assertEquals("0", rsp._getStr("responseHeader/status"));
-    }
+    V2Response rsp =
+        v2r.process(
+            cluster.getJettySolrRunners().stream()
+                .filter(j -> testNode.equals(j.getNodeName()))
+                .findAny()
+                .orElseThrow()
+                .getSolrClient());
+    assertEquals("0", rsp._getStr("responseHeader/status"));
   }
 }
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/response/InputStreamResponseParserTest.java b/solr/solrj/src/test/org/apache/solr/client/solrj/response/InputStreamResponseParserTest.java
index 984d19864d4..68eaaf8f861 100644
--- a/solr/solrj/src/test/org/apache/solr/client/solrj/response/InputStreamResponseParserTest.java
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/response/InputStreamResponseParserTest.java
@@ -26,7 +26,6 @@ import java.util.List;
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrServerException;
-import org.apache.solr.client.solrj.apache.HttpSolrClient;
 import org.apache.solr.client.solrj.request.QueryRequest;
 import org.apache.solr.client.solrj.request.SolrQuery;
 import org.apache.solr.common.SolrDocument;
@@ -72,8 +71,8 @@ public class InputStreamResponseParserTest extends SolrTestCaseJ4 {
   public void testQueryParse() throws Exception {
 
     try (SolrClient client =
-        new HttpSolrClient.Builder(solrTestRule.getBaseUrl())
-            .withDefaultCollection(DEFAULT_TEST_CORENAME)
+        solrTestRule
+            .newSolrClientBuilder()
             .withResponseParser(new InputStreamResponseParser("xml"))
             .build()) {
       SolrQuery query = new SolrQuery("id:1234");
diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/response/TestSuggesterResponse.java b/solr/solrj/src/test/org/apache/solr/client/solrj/response/TestSuggesterResponse.java
index d159bcc5328..713470c4bff 100644
--- a/solr/solrj/src/test/org/apache/solr/client/solrj/response/TestSuggesterResponse.java
+++ b/solr/solrj/src/test/org/apache/solr/client/solrj/response/TestSuggesterResponse.java
@@ -24,7 +24,6 @@ import java.util.Map;
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrServerException;
-import org.apache.solr.client.solrj.apache.HttpSolrClient;
 import org.apache.solr.client.solrj.request.QueryRequest;
 import org.apache.solr.client.solrj.request.SolrQuery;
 import org.apache.solr.common.SolrInputDocument;
@@ -144,10 +143,6 @@ public class TestSuggesterResponse extends SolrTestCaseJ4 {
   private SolrClient createSuggestSolrClient() {
     final ResponseParser randomParser =
         random().nextBoolean() ? new JavaBinResponseParser() : new XMLResponseParser();
-    return new HttpSolrClient.Builder()
-        .withBaseSolrUrl(solrTestRule.getBaseUrl())
-        .withDefaultCollection(DEFAULT_TEST_COLLECTION_NAME)
-        .withResponseParser(randomParser)
-        .build();
+    return solrTestRule.newSolrClientBuilder().withResponseParser(randomParser).build();
   }
 }
diff --git a/solr/test-framework/build.gradle b/solr/test-framework/build.gradle
index c116e384469..a871997786e 100644
--- a/solr/test-framework/build.gradle
+++ b/solr/test-framework/build.gradle
@@ -64,8 +64,6 @@ dependencies {
   implementation libs.apache.log4j.api
   implementation libs.apache.log4j.core
   implementation libs.commonscli.commonscli
-  implementation libs.apache.httpcomponents.httpclient
-  implementation libs.apache.httpcomponents.httpcore
   implementation libs.opentelemetry.api
   implementation(libs.opentelemetry.exporter.prometheus) {
     transitive = false // most things don't want or already have
@@ -97,9 +95,4 @@ dependencies {
     exclude group: "io.prometheus", module: "prometheus-metrics-shaded-protobuf"
     exclude group: "io.prometheus", module: "prometheus-metrics-config"
   })
-
-  implementation(libs.apache.httpcomponents.httpmime, {
-    exclude group: "commons-codec", module: "commons-codec"
-    exclude group: "commons-logging", module: "commons-logging"
-  })
 }
diff --git a/solr/test-framework/gradle.lockfile b/solr/test-framework/gradle.lockfile
index ee3bc67a6e5..f6abfef03ba 100644
--- a/solr/test-framework/gradle.lockfile
+++ b/solr/test-framework/gradle.lockfile
@@ -33,7 +33,7 @@ com.j256.simplemagic:simplemagic:1.17=apiHelper,jarValidation,runtimeClasspath,t
 com.jayway.jsonpath:json-path:2.9.0=apiHelper,jarValidation,runtimeClasspath,testRuntimeClasspath
 com.tdunning:t-digest:3.3=apiHelper,jarValidation,runtimeClasspath,testRuntimeClasspath
 commons-cli:commons-cli:1.11.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
-commons-codec:commons-codec:1.21.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
+commons-codec:commons-codec:1.21.0=apiHelper,jarValidation,runtimeClasspath,testRuntimeClasspath
 commons-io:commons-io:2.21.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
 io.dropwizard.metrics:metrics-core:4.2.33=apiHelper,compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
 io.github.eisop:dataflow-errorprone:3.41.0-eisop1=annotationProcessor,errorprone,testAnnotationProcessor
@@ -83,9 +83,6 @@ org.apache.commons:commons-math3:3.6.1=apiHelper,jarValidation,runtimeClasspath,
 org.apache.curator:curator-client:5.9.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
 org.apache.curator:curator-framework:5.9.0=apiHelper,compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
 org.apache.curator:curator-test:5.9.0=compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.apache.httpcomponents:httpclient:4.5.14=compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.apache.httpcomponents:httpcore:4.4.16=compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.apache.httpcomponents:httpmime:4.5.14=compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
 org.apache.logging.log4j:log4j-api:2.25.3=apiHelper,compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
 org.apache.logging.log4j:log4j-core:2.25.3=apiHelper,compileClasspath,jarValidation,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
 org.apache.logging.log4j:log4j-slf4j2-impl:2.25.3=apiHelper,jarValidation,runtimeClasspath,testRuntimeClasspath
diff --git a/solr/test-framework/src/java/org/apache/solr/BaseDistributedSearchTestCase.java b/solr/test-framework/src/java/org/apache/solr/BaseDistributedSearchTestCase.java
index 5249457ecd8..ca2c5657de8 100644
--- a/solr/test-framework/src/java/org/apache/solr/BaseDistributedSearchTestCase.java
+++ b/solr/test-framework/src/java/org/apache/solr/BaseDistributedSearchTestCase.java
@@ -50,6 +50,7 @@ import org.apache.lucene.util.Constants;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrResponse;
 import org.apache.solr.client.solrj.SolrServerException;
+import org.apache.solr.client.solrj.impl.HttpSolrClient;
 import org.apache.solr.client.solrj.request.UpdateRequest;
 import org.apache.solr.client.solrj.response.QueryResponse;
 import org.apache.solr.client.solrj.response.UpdateResponse;
@@ -173,14 +174,14 @@ public abstract class BaseDistributedSearchTestCase extends SolrTestCaseJ4 {
   }
 
   protected volatile JettySolrRunner controlJetty;
-  protected final List<SolrClient> clients = Collections.synchronizedList(new ArrayList<>());
+  protected final List<HttpSolrClient> clients = Collections.synchronizedList(new ArrayList<>());
   protected final List<JettySolrRunner> jettys = Collections.synchronizedList(new ArrayList<>());
 
   protected volatile String[] deadServers;
   protected volatile String shards;
   protected volatile String[] shardsArr;
   protected volatile Path testDir;
-  protected volatile SolrClient controlClient;
+  protected volatile HttpSolrClient controlClient;
 
   // to stress with higher thread counts and requests, make sure the junit
   // xml formatter is not being used (all output will be buffered before
@@ -451,7 +452,7 @@ public abstract class BaseDistributedSearchTestCase extends SolrTestCaseJ4 {
     return null;
   }
 
-  protected SolrClient createNewSolrClient(int port) {
+  protected HttpSolrClient createNewSolrClient(int port) {
     return getHttpSolrClient(buildUrl(port), DEFAULT_TEST_CORENAME);
   }
 
diff --git a/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java b/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
index 26668690557..4f4dedd1eec 100644
--- a/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
+++ b/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
@@ -76,9 +76,6 @@ import org.apache.lucene.tests.util.LuceneTestCase.SuppressFileSystems;
 import org.apache.lucene.tests.util.TestUtil;
 import org.apache.lucene.util.Constants;
 import org.apache.solr.client.solrj.SolrClient;
-import org.apache.solr.client.solrj.apache.CloudLegacySolrClient;
-import org.apache.solr.client.solrj.apache.HttpClientUtil;
-import org.apache.solr.client.solrj.apache.HttpSolrClient;
 import org.apache.solr.client.solrj.impl.CloudSolrClient;
 import org.apache.solr.client.solrj.impl.ClusterStateProvider;
 import org.apache.solr.client.solrj.jetty.HttpJettySolrClient;
@@ -281,9 +278,6 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
     newRandomConfig();
 
     sslConfig = buildSSLConfig();
-    // based on randomized SSL config, set SocketFactoryRegistryProvider appropriately
-    HttpClientUtil.setSocketFactoryRegistryProvider(
-        sslConfig.buildClientSocketFactoryRegistryProvider());
     HttpJettySolrClient.setDefaultSSLConfig(sslConfig.buildClientSSLConfig());
     if (isSSLMode()) {
       // SolrCloud tests should usually clear this
@@ -316,7 +310,6 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
     } finally {
       TestInjection.reset();
       initCoreDataDir = null;
-      HttpClientUtil.resetHttpClientBuilder();
       HttpJettySolrClient.resetSslContextFactory();
 
       clearNumericTypesProperties();
@@ -2474,55 +2467,8 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
         rsp -> rsp.getRequestStatus().isFinal());
   }
 
-  /**
-   * A variant of {@link org.apache.solr.client.solrj.impl.CloudSolrClient.Builder} that will
-   * randomize some internal settings.
-   */
-  public static class RandomizingCloudHttp2SolrClientBuilder extends CloudSolrClient.Builder {
-
-    public RandomizingCloudHttp2SolrClientBuilder(List<String> zkHosts, Optional<String> zkChroot) {
-      super(zkHosts, zkChroot);
-      randomizeCloudSolrClient();
-    }
-
-    public RandomizingCloudHttp2SolrClientBuilder(ClusterStateProvider stateProvider) {
-      super(new ArrayList<>());
-      this.stateProvider = stateProvider;
-      randomizeCloudSolrClient();
-    }
-
-    public RandomizingCloudHttp2SolrClientBuilder(MiniSolrCloudCluster cluster) {
-      super(new ArrayList<>());
-      if (random().nextBoolean()) {
-        this.zkHosts.add(cluster.getZkServer().getZkAddress());
-      } else {
-        populateSolrUrls(cluster);
-      }
-
-      randomizeCloudSolrClient();
-    }
-
-    private void populateSolrUrls(MiniSolrCloudCluster cluster) {
-      if (random().nextBoolean()) {
-        final List<JettySolrRunner> solrNodes = cluster.getJettySolrRunners();
-        for (JettySolrRunner node : solrNodes) {
-          this.solrUrls.add(node.getBaseUrl().toString());
-        }
-      } else {
-        this.solrUrls.add(cluster.getRandomJetty(random()).getBaseUrl().toString());
-      }
-    }
-
-    private void randomizeCloudSolrClient() {
-      this.directUpdatesToLeadersOnly = random().nextBoolean();
-      this.shardLeadersOnly = random().nextBoolean();
-      this.parallelUpdates = random().nextBoolean();
-    }
-  }
-
-  /** A variant of {@code CloudSolrClient.Builder} that will randomize some internal settings. */
-  @Deprecated
-  public static class RandomizingCloudSolrClientBuilder extends CloudLegacySolrClient.Builder {
+  /** A builder that will randomize some internal settings. */
+  public static class RandomizingCloudSolrClientBuilder extends CloudSolrClient.Builder {
 
     public RandomizingCloudSolrClientBuilder(List<String> zkHosts, Optional<String> zkChroot) {
       super(zkHosts, zkChroot);
@@ -2530,11 +2476,13 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
     }
 
     public RandomizingCloudSolrClientBuilder(ClusterStateProvider stateProvider) {
+      super(new ArrayList<>());
       this.stateProvider = stateProvider;
       randomizeCloudSolrClient();
     }
 
     public RandomizingCloudSolrClientBuilder(MiniSolrCloudCluster cluster) {
+      super(new ArrayList<>());
       if (random().nextBoolean()) {
         this.zkHosts.add(cluster.getZkServer().getZkAddress());
       } else {
@@ -2569,12 +2517,14 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
    *
    * @param url the base URL for a Solr node. Should not contain a core or collection name.
    */
-  public static HttpSolrClient getHttpSolrClient(String url) {
-    return new HttpSolrClient.Builder(url).build();
+  @Deprecated // probably use an existing client like on a testRule/jettyRunner
+  public static HttpJettySolrClient getHttpSolrClient(String url) {
+    return new HttpJettySolrClient.Builder(url).build();
   }
 
   /** Create a basic HttpSolrClient pointed at the specified replica */
-  public static HttpSolrClient getHttpSolrClient(Replica replica) {
+  @Deprecated // probably use an existing client like on a testRule/jettyRunner
+  public static HttpJettySolrClient getHttpSolrClient(Replica replica) {
     return getHttpSolrClient(replica.getBaseUrl(), replica.getCoreName());
   }
 
@@ -2587,8 +2537,9 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
    * @param defaultCoreName the name of a core that the created client should default to when making
    *     core-aware requests
    */
-  public static HttpSolrClient getHttpSolrClient(String url, String defaultCoreName) {
-    return new HttpSolrClient.Builder(url).withDefaultCollection(defaultCoreName).build();
+  @Deprecated // probably use an existing client like on a testRule/jettyRunner
+  public static HttpJettySolrClient getHttpSolrClient(String url, String defaultCoreName) {
+    return new HttpJettySolrClient.Builder(url).withDefaultCollection(defaultCoreName).build();
   }
 
   /**
diff --git a/solr/test-framework/src/java/org/apache/solr/client/solrj/apache/CloudLegacySolrClient.java b/solr/test-framework/src/java/org/apache/solr/client/solrj/apache/CloudLegacySolrClient.java
deleted file mode 100644
index d6bd7302409..00000000000
--- a/solr/test-framework/src/java/org/apache/solr/client/solrj/apache/CloudLegacySolrClient.java
+++ /dev/null
@@ -1,417 +0,0 @@
-/*
- * 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.apache;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.concurrent.TimeUnit;
-import org.apache.http.NoHttpResponseException;
-import org.apache.http.client.HttpClient;
-import org.apache.http.conn.ConnectTimeoutException;
-import org.apache.solr.client.solrj.impl.CloudSolrClient;
-import org.apache.solr.client.solrj.impl.ClusterStateProvider;
-import org.apache.solr.client.solrj.impl.LBSolrClient;
-import org.apache.solr.client.solrj.impl.SolrZkClientTimeout;
-import org.apache.solr.client.solrj.impl.SolrZkClientTimeout.SolrZkClientTimeoutAware;
-import org.apache.solr.client.solrj.request.JavaBinRequestWriter;
-import org.apache.solr.client.solrj.request.UpdateRequest;
-import org.apache.solr.client.solrj.response.JavaBinResponseParser;
-import org.apache.solr.common.SolrException;
-import org.apache.solr.common.cloud.DocCollection;
-import org.apache.solr.common.cloud.DocRouter;
-import org.apache.solr.common.params.ModifiableSolrParams;
-import org.apache.solr.common.util.IOUtils;
-import org.apache.solr.common.util.NamedList;
-
-/**
- * SolrJ client class to communicate with SolrCloud. Instances of this class communicate with
- * Zookeeper to discover Solr endpoints for SolrCloud collections, and then use the {@link
- * LBHttpSolrClient} to issue requests.
- *
- * @deprecated Please use {@link CloudSolrClient}
- */
-@Deprecated(since = "9.0")
-public class CloudLegacySolrClient extends CloudSolrClient {
-
-  private final ClusterStateProvider stateProvider;
-  private final LBHttpSolrClient lbClient;
-  private final boolean shutdownLBHttpSolrServer;
-  private HttpClient myClient;
-  private final boolean clientIsInternal;
-
-  public static final String STATE_VERSION = CloudSolrClient.STATE_VERSION;
-
-  /**
-   * Create a new client object that connects to Zookeeper and is always aware of the SolrCloud
-   * state. If there is a fully redundant Zookeeper quorum and SolrCloud has enough replicas for
-   * every shard in a collection, there is no single point of failure. Updates will be sent to shard
-   * leaders by default.
-   *
-   * @param builder a {@link Builder} with the options used to create the client.
-   */
-  protected CloudLegacySolrClient(Builder builder) {
-    super(builder.shardLeadersOnly, builder.parallelUpdates, builder.directUpdatesToLeadersOnly);
-    this.stateProvider = builder.stateProvider;
-    this.retryExpiryTimeNano = builder.retryExpiryTimeNano;
-    this.defaultCollection = builder.defaultCollection;
-    this.collectionStateCache.timeToLiveMs =
-        TimeUnit.MILLISECONDS.convert(builder.timeToLiveSeconds, TimeUnit.SECONDS);
-    this.clientIsInternal = builder.httpClient == null;
-    this.shutdownLBHttpSolrServer = builder.loadBalancedSolrClient == null;
-    if (builder.lbClientBuilder != null) {
-      propagateLBClientConfigOptions(builder);
-      builder.loadBalancedSolrClient = builder.lbClientBuilder.build();
-    }
-    if (builder.loadBalancedSolrClient != null)
-      builder.httpClient = builder.loadBalancedSolrClient.getHttpClient();
-    this.myClient =
-        (builder.httpClient == null) ? HttpClientUtil.createClient(null) : builder.httpClient;
-    if (builder.loadBalancedSolrClient == null)
-      builder.loadBalancedSolrClient = createLBHttpSolrClient(builder, myClient);
-    this.lbClient = builder.loadBalancedSolrClient;
-  }
-
-  private void propagateLBClientConfigOptions(Builder builder) {
-    final LBHttpSolrClient.Builder lbBuilder = builder.lbClientBuilder;
-    lbBuilder.withConnectionTimeout(builder.connectionTimeoutMillis, TimeUnit.MILLISECONDS);
-    lbBuilder.withSocketTimeout(builder.socketTimeoutMillis, TimeUnit.MILLISECONDS);
-  }
-
-  @Override
-  protected Map<String, LBSolrClient.Req> createRoutes(
-      UpdateRequest updateRequest,
-      ModifiableSolrParams routableParams,
-      DocCollection col,
-      DocRouter router,
-      Map<String, List<String>> urlMap,
-      String idField) {
-    return urlMap == null
-        ? null
-        : updateRequest.getRoutesToCollection(router, col, urlMap, routableParams, idField);
-  }
-
-  @Override
-  protected RouteException getRouteException(
-      SolrException.ErrorCode serverError,
-      NamedList<Throwable> exceptions,
-      Map<String, ? extends LBSolrClient.Req> routes) {
-    return new RouteException(serverError, exceptions, routes);
-  }
-
-  @Override
-  public void close() {
-    IOUtils.closeQuietly(stateProvider);
-
-    if (shutdownLBHttpSolrServer) {
-      lbClient.close();
-    }
-
-    if (clientIsInternal && myClient != null) {
-      HttpClientUtil.close(myClient);
-    }
-
-    super.close();
-  }
-
-  @Override
-  public LBHttpSolrClient getLbClient() {
-    return lbClient;
-  }
-
-  public HttpClient getHttpClient() {
-    return myClient;
-  }
-
-  @Override
-  public ClusterStateProvider getClusterStateProvider() {
-    return stateProvider;
-  }
-
-  @Override
-  protected boolean wasCommError(Throwable rootCause) {
-    return super.wasCommError(rootCause)
-        || rootCause instanceof ConnectTimeoutException
-        || rootCause instanceof NoHttpResponseException;
-  }
-
-  private static LBHttpSolrClient createLBHttpSolrClient(
-      Builder cloudSolrClientBuilder, HttpClient httpClient) {
-    final LBHttpSolrClient.Builder lbBuilder = new LBHttpSolrClient.Builder();
-    lbBuilder.withHttpClient(httpClient);
-    lbBuilder.withConnectionTimeout(
-        cloudSolrClientBuilder.connectionTimeoutMillis, TimeUnit.MILLISECONDS);
-    lbBuilder.withSocketTimeout(cloudSolrClientBuilder.socketTimeoutMillis, TimeUnit.MILLISECONDS);
-    lbBuilder.withRequestWriter(new JavaBinRequestWriter());
-    lbBuilder.withResponseParser(new JavaBinResponseParser());
-
-    return lbBuilder.build();
-  }
-
-  /** Constructs {@link CloudLegacySolrClient} instances from provided configuration. */
-  public static class Builder extends SolrClientBuilder<Builder> {
-    protected Collection<String> zkHosts = new ArrayList<>();
-    protected List<String> solrUrls = new ArrayList<>();
-    protected String zkChroot;
-    protected LBHttpSolrClient loadBalancedSolrClient;
-    protected LBHttpSolrClient.Builder lbClientBuilder;
-    protected boolean shardLeadersOnly = true;
-    protected boolean directUpdatesToLeadersOnly = false;
-    protected boolean parallelUpdates = true;
-    protected long retryExpiryTimeNano =
-        TimeUnit.NANOSECONDS.convert(3, TimeUnit.SECONDS); // 3 seconds or 3 million nanos
-    protected ClusterStateProvider stateProvider;
-    private int zkConnectTimeout = SolrZkClientTimeout.DEFAULT_ZK_CONNECT_TIMEOUT;
-    private int zkClientTimeout = SolrZkClientTimeout.DEFAULT_ZK_CLIENT_TIMEOUT;
-    private boolean canUseZkACLs = true;
-
-    /** Constructor for use by subclasses. This constructor was public prior to version 9.0 */
-    protected Builder() {}
-
-    /**
-     * Provide a series of Solr URLs to be used when configuring {@link CloudLegacySolrClient}
-     * instances. The solr client will use these urls to understand the cluster topology, which solr
-     * nodes are active etc.
-     *
-     * <p>Provided Solr URLs are expected to point to the root Solr path
-     * ("http://hostname:8983/solr"); they should not include any collections, cores, or other path
-     * components.
-     *
-     * <p>Usage example:
-     *
-     * <pre>
-     *   final List&lt;String&gt; solrBaseUrls = new ArrayList&lt;String&gt;();
-     *   solrBaseUrls.add("http://solr1:8983/solr"); solrBaseUrls.add("http://solr2:8983/solr"); solrBaseUrls.add("http://solr3:8983/solr");
-     *   final SolrClient client = new CloudSolrClient.Builder(solrBaseUrls).build();
-     * </pre>
-     */
-    public Builder(List<String> solrUrls) {
-      this.solrUrls = solrUrls;
-    }
-
-    /** Provide an already created {@link ClusterStateProvider} instance */
-    public Builder(ClusterStateProvider stateProvider) {
-      this.stateProvider = stateProvider;
-    }
-
-    /**
-     * Provide a series of ZK hosts which will be used when configuring {@link
-     * CloudLegacySolrClient} instances.
-     *
-     * <p>Usage example when Solr stores data at the ZooKeeper root ('/'):
-     *
-     * <pre>
-     *   final List&lt;String&gt; zkServers = new ArrayList&lt;String&gt;();
-     *   zkServers.add("zookeeper1:2181"); zkServers.add("zookeeper2:2181"); zkServers.add("zookeeper3:2181");
-     *   final SolrClient client = new CloudSolrClient.Builder(zkServers, Optional.empty()).build();
-     * </pre>
-     *
-     * Usage example when Solr data is stored in a ZooKeeper chroot:
-     *
-     * <pre>
-     *    final List&lt;String&gt; zkServers = new ArrayList&lt;String&gt;();
-     *    zkServers.add("zookeeper1:2181"); zkServers.add("zookeeper2:2181"); zkServers.add("zookeeper3:2181");
-     *    final SolrClient client = new CloudSolrClient.Builder(zkServers, Optional.of("/solr")).build();
-     *  </pre>
-     *
-     * @param zkHosts a List of at least one ZooKeeper host and port (e.g. "zookeeper1:2181")
-     * @param zkChroot the path to the root ZooKeeper node containing Solr data. Provide {@code
-     *     java.util.Optional.empty()} if no ZK chroot is used.
-     */
-    public Builder(List<String> zkHosts, Optional<String> zkChroot) {
-      this.zkHosts = zkHosts;
-      if (zkChroot.isPresent()) this.zkChroot = zkChroot.get();
-    }
-
-    /** Whether to use the default ZK ACLs when building a ZK Client. */
-    public Builder canUseZkACLs(boolean canUseZkACLs) {
-      this.canUseZkACLs = canUseZkACLs;
-      return this;
-    }
-
-    /** Provides a {@link HttpClient} for the builder to use when creating clients. */
-    public Builder withLBHttpSolrClientBuilder(LBHttpSolrClient.Builder lbHttpSolrClientBuilder) {
-      this.lbClientBuilder = lbHttpSolrClientBuilder;
-      return this;
-    }
-
-    /** Provides a {@link LBHttpSolrClient} for the builder to use when creating clients. */
-    public Builder withLBHttpSolrClient(LBHttpSolrClient loadBalancedSolrClient) {
-      this.loadBalancedSolrClient = loadBalancedSolrClient;
-      return this;
-    }
-
-    /**
-     * Sets the cache ttl for DocCollection Objects cached.
-     *
-     * @param seconds ttl value in seconds
-     */
-    public Builder withCollectionCacheTtl(int seconds) {
-      assert seconds > 0;
-      this.timeToLiveSeconds = seconds;
-      return this;
-    }
-
-    /**
-     * Tells {@link Builder} that created clients should be configured such that {@link
-     * CloudSolrClient#isUpdatesToLeaders} returns <code>true</code>.
-     *
-     * @see #sendUpdatesToAnyReplica
-     * @see CloudSolrClient#isUpdatesToLeaders
-     */
-    public Builder sendUpdatesOnlyToShardLeaders() {
-      shardLeadersOnly = true;
-      return this;
-    }
-
-    /**
-     * Tells {@link Builder} that created clients should be configured such that {@link
-     * CloudSolrClient#isUpdatesToLeaders} returns <code>false</code>.
-     *
-     * @see #sendUpdatesOnlyToShardLeaders
-     * @see CloudSolrClient#isUpdatesToLeaders
-     */
-    public Builder sendUpdatesToAnyReplica() {
-      shardLeadersOnly = false;
-      return this;
-    }
-
-    /**
-     * This method has no effect.
-     *
-     * <p>In older versions of Solr, this method was an incorrectly named equivalent to {@link
-     * #sendUpdatesToAnyReplica}, which had no effect because that setting was ignored in the
-     * created clients. When the underlying {@link CloudSolrClient} behavior was fixed, this method
-     * was modified to be an explicit No-Op, since the implied behavior of sending updates to
-     * <em>all</em> replicas has never been supported, and was never intended to be supported.
-     *
-     * @see #sendUpdatesOnlyToShardLeaders
-     * @see #sendUpdatesToAnyReplica
-     * @see CloudSolrClient#isUpdatesToLeaders
-     * @see <a href="https://issues.apache.org/jira/browse/SOLR-6312">SOLR-6312</a>
-     * @deprecated Never supported
-     */
-    @Deprecated
-    public Builder sendUpdatesToAllReplicasInShard() {
-      return this;
-    }
-
-    /**
-     * Tells {@link Builder} that created clients should send direct updates to shard leaders only.
-     *
-     * <p>UpdateRequests whose leaders cannot be found will "fail fast" on the client side with a
-     * {@link SolrException}
-     *
-     * @see #sendDirectUpdatesToAnyShardReplica
-     * @see CloudSolrClient#isDirectUpdatesToLeadersOnly
-     */
-    public Builder sendDirectUpdatesToShardLeadersOnly() {
-      directUpdatesToLeadersOnly = true;
-      return this;
-    }
-
-    /**
-     * Tells {@link Builder} that created clients can send updates to any shard replica (shard
-     * leaders and non-leaders).
-     *
-     * <p>Shard leaders are still preferred, but the created clients will fall back to using other
-     * replicas if a leader cannot be found.
-     *
-     * @see #sendDirectUpdatesToShardLeadersOnly
-     * @see CloudSolrClient#isDirectUpdatesToLeadersOnly
-     */
-    public Builder sendDirectUpdatesToAnyShardReplica() {
-      directUpdatesToLeadersOnly = false;
-      return this;
-    }
-
-    /**
-     * Tells {@link Builder} whether created clients should send shard updates serially or in
-     * parallel
-     *
-     * <p>When an {@link UpdateRequest} affects multiple shards, {@link CloudLegacySolrClient}
-     * splits it up and sends a request to each affected shard. This setting chooses whether those
-     * sub-requests are sent serially or in parallel.
-     *
-     * <p>If not set, this defaults to 'true' and sends sub-requests in parallel.
-     */
-    public Builder withParallelUpdates(boolean parallelUpdates) {
-      this.parallelUpdates = parallelUpdates;
-      return this;
-    }
-
-    /**
-     * Sets the Zk connection timeout
-     *
-     * @param zkConnectTimeout timeout value
-     * @param unit time unit
-     */
-    public Builder withZkConnectTimeout(int zkConnectTimeout, TimeUnit unit) {
-      this.zkConnectTimeout = Math.toIntExact(unit.toMillis(zkConnectTimeout));
-      return this;
-    }
-
-    /**
-     * Sets the Zk client session timeout
-     *
-     * @param zkClientTimeout timeout value
-     * @param unit time unit
-     */
-    public Builder withZkClientTimeout(int zkClientTimeout, TimeUnit unit) {
-      this.zkClientTimeout = Math.toIntExact(unit.toMillis(zkClientTimeout));
-      return this;
-    }
-
-    /** Create a {@link CloudLegacySolrClient} based on the provided configuration. */
-    public CloudLegacySolrClient build() {
-      if (stateProvider == null) {
-        if (!zkHosts.isEmpty() && !solrUrls.isEmpty()) {
-          throw new IllegalArgumentException(
-              "Both zkHost(s) & solrUrl(s) have been specified. Only specify one.");
-        } else if (!zkHosts.isEmpty()) {
-          this.stateProvider =
-              ClusterStateProvider.newZkClusterStateProvider(zkHosts, zkChroot, canUseZkACLs);
-          if (stateProvider instanceof SolrZkClientTimeoutAware timeoutAware) {
-            timeoutAware.setZkClientTimeout(zkClientTimeout);
-            timeoutAware.setZkConnectTimeout(zkConnectTimeout);
-          }
-        } else if (!this.solrUrls.isEmpty()) {
-          try {
-            stateProvider = new HttpClusterStateProvider(solrUrls, httpClient);
-          } catch (Exception e) {
-            throw new RuntimeException(
-                "Couldn't initialize a HttpClusterStateProvider (is/are the "
-                    + "Solr server(s), "
-                    + solrUrls
-                    + ", down?)",
-                e);
-          }
-        } else {
-          throw new IllegalArgumentException("Both zkHosts and solrUrl cannot be null.");
-        }
-      }
-      return new CloudLegacySolrClient(this);
-    }
-
-    @Override
-    public Builder getThis() {
-      return this;
-    }
-  }
-}
diff --git a/solr/test-framework/src/java/org/apache/solr/client/solrj/apache/ConcurrentUpdateSolrClient.java b/solr/test-framework/src/java/org/apache/solr/client/solrj/apache/ConcurrentUpdateSolrClient.java
deleted file mode 100644
index a042bdae536..00000000000
--- a/solr/test-framework/src/java/org/apache/solr/client/solrj/apache/ConcurrentUpdateSolrClient.java
+++ /dev/null
@@ -1,887 +0,0 @@
-/*
- * 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.apache;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.lang.invoke.MethodHandles;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayDeque;
-import java.util.Locale;
-import java.util.Objects;
-import java.util.Queue;
-import java.util.Set;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-import org.apache.http.HttpResponse;
-import org.apache.http.HttpStatus;
-import org.apache.http.NameValuePair;
-import org.apache.http.client.config.RequestConfig;
-import org.apache.http.client.methods.HttpPost;
-import org.apache.http.entity.ContentProducer;
-import org.apache.http.entity.EntityTemplate;
-import org.apache.solr.client.solrj.RemoteSolrException;
-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.ConcurrentUpdateBaseSolrClient;
-import org.apache.solr.client.solrj.impl.StallDetection;
-import org.apache.solr.client.solrj.request.UpdateRequest;
-import org.apache.solr.client.solrj.util.ClientUtils;
-import org.apache.solr.common.SolrException;
-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.params.UpdateParams;
-import org.apache.solr.common.util.ExecutorUtil;
-import org.apache.solr.common.util.IOUtils;
-import org.apache.solr.common.util.NamedList;
-import org.apache.solr.common.util.SolrNamedThreadFactory;
-import org.apache.solr.common.util.URLUtil;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.slf4j.MDC;
-
-/**
- * ConcurrentUpdateSolrClient buffers all added documents and writes them into open HTTP
- * connections. This class is thread safe.
- *
- * <p>Params from {@link UpdateRequest} are converted to http request parameters. When params change
- * between UpdateRequests a new HTTP request is started.
- *
- * <p>Although any SolrClient request can be made with this implementation, it is only recommended
- * to use ConcurrentUpdateSolrClient with /update requests. The class {@link HttpSolrClient} is
- * better suited for the query interface.
- *
- * @deprecated Please use {@link ConcurrentUpdateBaseSolrClient}
- */
-@Deprecated(since = "9.0")
-public class ConcurrentUpdateSolrClient extends SolrClient {
-  private static final long serialVersionUID = 1L;
-  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
-  private HttpSolrClient client;
-  final BlockingQueue<Update> queue;
-  final ExecutorService scheduler;
-  final Queue<Runner> runners;
-  volatile CountDownLatch lock = null; // used to block everything
-  final int threadCount;
-  boolean shutdownExecutor = false;
-  int pollQueueTimeMillis = 250;
-  private final boolean streamDeletes;
-  private boolean internalHttpClient;
-  private final int connectionTimeout;
-  private final int soTimeout;
-  private volatile boolean closed;
-
-  AtomicInteger pollInterrupts;
-  AtomicInteger pollExits;
-  AtomicInteger blockLoops;
-  AtomicInteger emptyQueueLoops;
-
-  protected StallDetection stallDetection;
-
-  /**
-   * Use builder to construct this class. Uses the supplied HttpClient to send documents to the Solr
-   * server.
-   */
-  protected ConcurrentUpdateSolrClient(Builder builder) {
-    this.internalHttpClient = (builder.httpClient == null);
-    final var httpSolrClientBuilder =
-        (URLUtil.isBaseUrl(builder.baseSolrUrl))
-            ? new HttpSolrClient.Builder(builder.baseSolrUrl)
-            : new HttpSolrClient.Builder(URLUtil.extractBaseUrl(builder.baseSolrUrl))
-                .withDefaultCollection(URLUtil.extractCoreFromCoreUrl(builder.baseSolrUrl));
-    this.client =
-        httpSolrClientBuilder
-            .withHttpClient(builder.httpClient)
-            .withConnectionTimeout(builder.connectionTimeoutMillis, TimeUnit.MILLISECONDS)
-            .withSocketTimeout(builder.socketTimeoutMillis, TimeUnit.MILLISECONDS)
-            .withFollowRedirects(false)
-            .withTheseParamNamesInTheUrl(builder.urlParamNames)
-            .build();
-    this.queue = new LinkedBlockingQueue<>(builder.queueSize);
-    this.threadCount = builder.threadCount;
-    this.runners = new ArrayDeque<>();
-    this.streamDeletes = builder.streamDeletes;
-    this.connectionTimeout = builder.connectionTimeoutMillis;
-    this.soTimeout = builder.socketTimeoutMillis;
-    this.pollQueueTimeMillis = builder.pollQueueTime;
-    this.defaultCollection = builder.defaultCollection;
-
-    // Initialize stall detection
-    int stallTimeMillis = Integer.getInteger("solr.cloud.client.stallTime", 15000);
-
-    // make sure the stall time is larger than the polling time
-    // to give a chance for the queue to change
-    int minimalStallTime = pollQueueTimeMillis * 2;
-    if (minimalStallTime > stallTimeMillis) {
-      stallTimeMillis = minimalStallTime;
-    }
-
-    this.stallDetection = new StallDetection(stallTimeMillis, () -> queue.size());
-
-    if (builder.executorService != null) {
-      this.scheduler = builder.executorService;
-      this.shutdownExecutor = false;
-    } else {
-      this.scheduler =
-          ExecutorUtil.newMDCAwareCachedThreadPool(
-              new SolrNamedThreadFactory("concurrentUpdateScheduler"));
-      this.shutdownExecutor = true;
-    }
-
-    if (log.isDebugEnabled()) {
-      this.pollInterrupts = new AtomicInteger();
-      this.pollExits = new AtomicInteger();
-      this.blockLoops = new AtomicInteger();
-      this.emptyQueueLoops = new AtomicInteger();
-    }
-
-    // processedCount is now managed by StallDetection
-  }
-
-  public Set<String> getUrlParamNames() {
-    return this.client.getUrlParamNames();
-  }
-
-  /** Opens a connection and sends everything... */
-  @SuppressWarnings({"unchecked"})
-  class Runner implements Runnable {
-    volatile Thread thread = null;
-    volatile boolean inPoll = false;
-
-    public Thread getThread() {
-      return thread;
-    }
-
-    @Override
-    public void run() {
-      this.thread = Thread.currentThread();
-      log.debug("starting runner: {}", this);
-      // This loop is so we can continue if an element was added to the queue after the last runner
-      // exited.
-      for (; ; ) {
-        try {
-
-          sendUpdateStream();
-
-        } catch (Throwable e) {
-          if (e instanceof OutOfMemoryError) {
-            throw (OutOfMemoryError) e;
-          }
-          handleError(e);
-        } finally {
-          synchronized (runners) {
-            // check to see if anything else was added to the queue
-            if (runners.size() == 1 && !queue.isEmpty() && !ExecutorUtil.isShutdown(scheduler)) {
-              // If there is something else to process, keep last runner alive by staying in the
-              // loop.
-            } else {
-              runners.remove(this);
-              if (runners.isEmpty()) {
-                // notify anyone waiting in blockUntilFinished
-                runners.notifyAll();
-              }
-              break;
-            }
-          }
-        }
-      }
-
-      log.debug("finished: {}", this);
-    }
-
-    public void interruptPoll() {
-      Thread lthread = thread;
-      if (inPoll && lthread != null) {
-        lthread.interrupt();
-      }
-    }
-
-    //
-    // Pull from the queue multiple times and streams over a single connection.
-    // Exits on exception, interruption, or an empty queue to pull from.
-    //
-    void sendUpdateStream() throws Exception {
-
-      while (!queue.isEmpty()) {
-        HttpPost method = null;
-        HttpResponse response = null;
-
-        InputStream rspBody = null;
-        try {
-          Update update;
-          notifyQueueAndRunnersIfEmptyQueue();
-          try {
-            inPoll = true;
-            update = queue.poll(pollQueueTimeMillis, TimeUnit.MILLISECONDS);
-          } catch (InterruptedException e) {
-            if (log.isDebugEnabled()) pollInterrupts.incrementAndGet();
-            continue;
-          } finally {
-            inPoll = false;
-          }
-          if (update == null) break;
-
-          String contentType = client.requestWriter.getUpdateContentType();
-          final boolean isXml = ClientUtils.TEXT_XML.equals(contentType);
-
-          final ModifiableSolrParams origParams =
-              new ModifiableSolrParams(update.getRequest().getParams());
-          final String origTargetCollection = update.getCollection();
-
-          EntityTemplate template =
-              new EntityTemplate(
-                  new ContentProducer() {
-
-                    @Override
-                    public void writeTo(OutputStream out) throws IOException {
-
-                      if (isXml) {
-                        out.write("<stream>".getBytes(StandardCharsets.UTF_8)); // can be anything
-                      }
-                      Update upd = update;
-                      while (upd != null) {
-                        UpdateRequest req = upd.getRequest();
-                        if (!origParams.equals(req.getParams())
-                            || !Objects.equals(origTargetCollection, upd.getCollection())) {
-                          // Request has different params or destination core/collection, return to
-                          // queue
-                          queue.add(upd);
-                          break;
-                        }
-
-                        client.requestWriter.write(req, out);
-                        if (isXml) {
-                          // check for commit or optimize
-                          SolrParams params = req.getParams();
-                          if (params != null) {
-                            String fmt = null;
-                            if (params.getBool(UpdateParams.OPTIMIZE, false)) {
-                              fmt = "<optimize waitSearcher=\"%s\" />";
-                            } else if (params.getBool(UpdateParams.COMMIT, false)) {
-                              fmt = "<commit waitSearcher=\"%s\" />";
-                            }
-                            if (fmt != null) {
-                              byte[] content =
-                                  String.format(
-                                          Locale.ROOT,
-                                          fmt,
-                                          params.getBool(UpdateParams.WAIT_SEARCHER, false) + "")
-                                      .getBytes(StandardCharsets.UTF_8);
-                              out.write(content);
-                            }
-                          }
-                        }
-                        out.flush();
-
-                        notifyQueueAndRunnersIfEmptyQueue();
-                        inPoll = true;
-                        try {
-                          while (true) {
-                            try {
-                              upd = queue.poll(pollQueueTimeMillis, TimeUnit.MILLISECONDS);
-                              break;
-                            } catch (InterruptedException e) {
-                              if (log.isDebugEnabled()) pollInterrupts.incrementAndGet();
-                              if (!queue.isEmpty()) {
-                                continue;
-                              }
-                              if (log.isDebugEnabled()) pollExits.incrementAndGet();
-                              upd = null;
-                              break;
-                            } finally {
-                              inPoll = false;
-                            }
-                          }
-                        } finally {
-                          inPoll = false;
-                        }
-                      }
-
-                      if (isXml) {
-                        out.write("</stream>".getBytes(StandardCharsets.UTF_8));
-                      }
-                    }
-                  });
-
-          // The parser 'wt=' param is used instead of the original params
-          var addParams = SolrParams.of(CommonParams.WT, client.parser.getWriterType());
-          var requestParams = SolrParams.wrapDefaults(addParams, origParams);
-
-          String basePath = client.getBaseURL();
-          if (update.getCollection() != null) {
-            basePath += "/" + update.getCollection();
-          } else if (client.getDefaultCollection() != null) {
-            basePath += "/" + client.getDefaultCollection();
-          }
-
-          method = new HttpPost(basePath + "/update" + requestParams.toQueryString());
-
-          RequestConfig.Builder requestConfigBuilder =
-              HttpClientUtil.createDefaultRequestConfigBuilder();
-          requestConfigBuilder.setSocketTimeout(soTimeout);
-          requestConfigBuilder.setConnectTimeout(connectionTimeout);
-
-          method.setConfig(requestConfigBuilder.build());
-
-          method.setEntity(template);
-          method.addHeader("User-Agent", HttpSolrClient.USER_AGENT);
-          method.addHeader("Content-Type", contentType);
-
-          response =
-              client
-                  .getHttpClient()
-                  .execute(method, HttpClientUtil.createNewHttpClientRequestContext());
-
-          rspBody = response.getEntity().getContent();
-
-          int statusCode = response.getStatusLine().getStatusCode();
-          if (statusCode != HttpStatus.SC_OK) {
-            StringBuilder msg = new StringBuilder();
-            msg.append(response.getStatusLine().getReasonPhrase());
-            msg.append("\n\n\n\n");
-            msg.append("request: ").append(method.getURI());
-
-            SolrException solrExc;
-            Object remoteError = null;
-            // parse out the metadata from the SolrException
-            try {
-              String encoding = "UTF-8"; // default
-              if (response.getEntity().getContentType().getElements().length > 0) {
-                NameValuePair param =
-                    response
-                        .getEntity()
-                        .getContentType()
-                        .getElements()[0]
-                        .getParameterByName("charset");
-                if (param != null) {
-                  encoding = param.getValue();
-                }
-              }
-              NamedList<Object> resp = client.parser.processResponse(rspBody, encoding);
-              remoteError = resp.get("error");
-            } catch (Exception exc) {
-              // don't want to fail to report error if parsing the response fails
-              log.warn("Failed to parse error response from {} due to: ", client.getBaseURL(), exc);
-            } finally {
-              solrExc = new RemoteSolrException(client.getBaseURL(), statusCode, remoteError);
-            }
-
-            handleError(solrExc);
-          } else {
-            onSuccess(response);
-          }
-          stallDetection.incrementProcessedCount();
-
-        } finally {
-          try {
-            if (response != null) {
-              HttpClientUtil.consumeFully(response.getEntity());
-            }
-          } catch (Exception e) {
-            log.error("Error consuming and closing http response stream.", e);
-          }
-          notifyQueueAndRunnersIfEmptyQueue();
-        }
-      }
-    }
-  }
-
-  private void notifyQueueAndRunnersIfEmptyQueue() {
-    if (queue.size() == 0) {
-      synchronized (queue) {
-        // queue may be empty
-        queue.notifyAll();
-      }
-      synchronized (runners) {
-        // we notify runners too - if there is a high queue poll time and this is the update
-        // that emptied the queue, we make an attempt to avoid the 250ms timeout in
-        // blockUntilFinished
-        runners.notifyAll();
-      }
-    }
-  }
-
-  // *must* be called with runners monitor held, e.g. synchronized(runners){ addRunner() }
-  private void addRunner() {
-    MDC.put("ConcurrentUpdateSolrClient.url", client.getBaseURL());
-    try {
-      Runner r = new Runner();
-      runners.add(r);
-      try {
-        // this can throw an exception if the scheduler has been shutdown, but that should be fine.
-        scheduler.execute(r);
-      } catch (RuntimeException e) {
-        runners.remove(r);
-        throw e;
-      }
-    } finally {
-      MDC.remove("ConcurrentUpdateSolrClient.url");
-    }
-  }
-
-  /** Class representing an UpdateRequest and an optional collection. */
-  static class Update {
-    UpdateRequest request;
-    String collection;
-
-    /**
-     * @param request the update request.
-     * @param collection The collection, can be null.
-     */
-    public Update(UpdateRequest request, String collection) {
-      this.request = request;
-      this.collection = collection;
-    }
-
-    /**
-     * @return the update request.
-     */
-    public UpdateRequest getRequest() {
-      return request;
-    }
-
-    public void setRequest(UpdateRequest request) {
-      this.request = request;
-    }
-
-    /**
-     * @return the collection, can be null.
-     */
-    public String getCollection() {
-      return collection;
-    }
-
-    public void setCollection(String collection) {
-      this.collection = collection;
-    }
-  }
-
-  @Override
-  public NamedList<Object> request(final SolrRequest<?> request, String collection)
-      throws SolrServerException, IOException {
-    if (ClientUtils.shouldApplyDefaultCollection(collection, request))
-      collection = defaultCollection;
-    if (!(request instanceof UpdateRequest req)) {
-      return client.request(request, collection);
-    }
-
-    // this happens for commit...
-    if (streamDeletes) {
-      if ((req.getDocuments() == null || req.getDocuments().isEmpty())
-          && (req.getDeleteById() == null || req.getDeleteById().isEmpty())
-          && (req.getDeleteByIdMap() == null || req.getDeleteByIdMap().isEmpty())) {
-        if (req.getDeleteQuery() == null) {
-          blockUntilFinished();
-          return client.request(request, collection);
-        }
-      }
-    } else {
-      if ((req.getDocuments() == null || req.getDocuments().isEmpty())) {
-        blockUntilFinished();
-        return client.request(request, collection);
-      }
-    }
-
-    SolrParams params = req.getParams();
-    if (params != null) {
-      // check if it is waiting for the searcher
-      if (params.getBool(UpdateParams.WAIT_SEARCHER, false)) {
-        log.info("blocking for commit/optimize");
-        blockUntilFinished(); // empty the queue
-        return client.request(request, collection);
-      }
-    }
-
-    try {
-      CountDownLatch tmpLock = lock;
-      if (tmpLock != null) {
-        tmpLock.await();
-      }
-
-      Update update = new Update(req, collection);
-      boolean success = queue.offer(update);
-
-      for (; ; ) {
-        synchronized (runners) {
-          // see if queue is half full, and we can add more runners
-          // special case: if only using a threadCount of 1 and the queue
-          // is filling up, allow 1 additional runner to help process the queue
-          if (runners.isEmpty()
-              || (queue.remainingCapacity() < queue.size() && runners.size() < threadCount)) {
-            // We need more runners, so start a new one.
-            addRunner();
-          } else {
-            // break out of the retry loop if we added the element to the queue
-            // successfully, *and*
-            // while we are still holding the runners lock to prevent race
-            // conditions.
-            if (success) break;
-          }
-        }
-
-        // Retry to add to the queue w/o the runners lock held (else we risk
-        // temporary deadlock)
-        // This retry could also fail because
-        // 1) existing runners were not able to take off any new elements in the
-        // queue
-        // 2) the queue was filled back up since our last try
-        // If we succeed, the queue may have been completely emptied, and all
-        // runners stopped.
-        // In all cases, we should loop back to the top to see if we need to
-        // start more runners.
-        //
-        if (!success) {
-          success = queue.offer(update, 100, TimeUnit.MILLISECONDS);
-        }
-        if (!success) {
-          stallCheck();
-        }
-      }
-    } catch (InterruptedException e) {
-      log.error("interrupted", e);
-      throw new IOException(e.getLocalizedMessage());
-    }
-
-    // RETURN A DUMMY result
-    NamedList<Object> dummy = new NamedList<>();
-    dummy.add("NOTE", "the request is processed in a background stream");
-    return dummy;
-  }
-
-  void stallCheck() throws IOException {
-    stallDetection.stallCheck();
-  }
-
-  public synchronized void blockUntilFinished() throws IOException {
-    lock = new CountDownLatch(1);
-    try {
-
-      waitForEmptyQueue();
-      interruptRunnerThreadsPolling();
-
-      synchronized (runners) {
-
-        // NOTE: if the executor is shut down, runners may never become empty. A scheduled task may
-        // never be run, which means it would never remove itself from the runners list. This is why
-        // we don't wait forever and periodically check if the scheduler is shutting down.
-        int loopCount = 0;
-        while (!runners.isEmpty()) {
-
-          if (log.isDebugEnabled()) blockLoops.incrementAndGet();
-
-          if (ExecutorUtil.isShutdown(scheduler)) break;
-
-          loopCount++;
-
-          // Need to check if the queue is empty before really considering this is finished
-          // (SOLR-4260)
-          int queueSize = queue.size();
-          // stall prevention - only if queue is not empty
-          if (queueSize > 0) {
-            stallCheck();
-          }
-
-          if (queueSize > 0 && runners.isEmpty()) {
-            // TODO: can this still happen?
-            log.warn(
-                "No more runners, but queue still has {} adding more runners to process remaining requests on queue",
-                queueSize);
-            addRunner();
-          }
-
-          interruptRunnerThreadsPolling();
-
-          // try to avoid the worst case wait timeout
-          // without bad spin
-          int timeout;
-          if (loopCount < 3) {
-            timeout = 10;
-          } else if (loopCount < 10) {
-            timeout = 25;
-          } else {
-            timeout = 250;
-          }
-
-          try {
-            runners.wait(timeout);
-          } catch (InterruptedException e) {
-            Thread.currentThread().interrupt();
-          }
-        }
-      }
-    } finally {
-      lock.countDown();
-      lock = null;
-    }
-  }
-
-  private void waitForEmptyQueue() throws IOException {
-    boolean threadInterrupted = Thread.currentThread().isInterrupted();
-
-    while (!queue.isEmpty()) {
-      if (log.isDebugEnabled()) emptyQueueLoops.incrementAndGet();
-      if (ExecutorUtil.isTerminated(scheduler)) {
-        log.warn(
-            "The task queue still has elements but the update scheduler {} is terminated. Can't process any more tasks. Queue size: {}, Runners: {}. Current thread Interrupted? {}",
-            scheduler,
-            queue.size(),
-            runners.size(),
-            threadInterrupted);
-        break;
-      }
-
-      synchronized (runners) {
-        int queueSize = queue.size();
-        if (queueSize > 0 && runners.isEmpty()) {
-          log.warn(
-              "No more runners, but queue still has {} adding more runners to process remaining requests on queue",
-              queueSize);
-          addRunner();
-        }
-      }
-      synchronized (queue) {
-        try {
-          queue.wait(250);
-        } catch (InterruptedException e) {
-          // If we set the thread as interrupted again, the next time the wait it's called it's
-          // going to return immediately
-          threadInterrupted = true;
-          log.warn(
-              "Thread interrupted while waiting for update queue to be empty. There are still {} elements in the queue.",
-              queue.size());
-        }
-      }
-
-      // Only check for stalls if the queue is not empty
-      if (!queue.isEmpty()) {
-        stallCheck();
-      }
-    }
-    if (threadInterrupted) {
-      Thread.currentThread().interrupt();
-    }
-  }
-
-  public void handleError(Throwable ex) {
-    log.error("error", ex);
-  }
-
-  /**
-   * Intended to be used as an extension point for doing post-processing after a request completes.
-   */
-  public void onSuccess(HttpResponse resp) {
-    // no-op by design, override to add functionality
-  }
-
-  @Override
-  public synchronized void close() {
-    if (closed) {
-      interruptRunnerThreadsPolling();
-      return;
-    }
-    closed = true;
-
-    try {
-      if (shutdownExecutor) {
-        scheduler.shutdown();
-        interruptRunnerThreadsPolling();
-        try {
-          if (!scheduler.awaitTermination(60, TimeUnit.SECONDS)) {
-            scheduler.shutdownNow();
-            if (!scheduler.awaitTermination(60, TimeUnit.SECONDS))
-              log.error("ExecutorService did not terminate");
-          }
-        } catch (InterruptedException ie) {
-          scheduler.shutdownNow();
-          Thread.currentThread().interrupt();
-        }
-      } else {
-        interruptRunnerThreadsPolling();
-      }
-    } finally {
-      if (internalHttpClient) IOUtils.closeQuietly(client);
-      if (log.isDebugEnabled()) {
-        log.debug(
-            "STATS pollInterrupts={} pollExists={} blockLoops={} emptyQueueLoops={}",
-            pollInterrupts.get(),
-            pollExits.get(),
-            blockLoops.get(),
-            emptyQueueLoops.get());
-      }
-    }
-  }
-
-  private void interruptRunnerThreadsPolling() {
-    synchronized (runners) {
-      for (Runner runner : runners) {
-        runner.interruptPoll();
-      }
-    }
-  }
-
-  public void shutdownNow() {
-    if (closed) {
-      return;
-    }
-    closed = true;
-    try {
-
-      if (shutdownExecutor) {
-        scheduler.shutdown();
-        interruptRunnerThreadsPolling();
-        scheduler.shutdownNow(); // Cancel currently executing tasks
-        try {
-          if (!scheduler.awaitTermination(30, TimeUnit.SECONDS))
-            log.error("ExecutorService did not terminate");
-        } catch (InterruptedException ie) {
-          scheduler.shutdownNow();
-          Thread.currentThread().interrupt();
-        }
-      } else {
-        interruptRunnerThreadsPolling();
-      }
-    } finally {
-      if (internalHttpClient) IOUtils.closeQuietly(client);
-    }
-  }
-
-  /** Constructs {@link ConcurrentUpdateSolrClient} instances from provided configuration. */
-  public static class Builder extends SolrClientBuilder<Builder> {
-    protected String baseSolrUrl;
-    protected int queueSize = 10;
-    protected int threadCount;
-    protected int pollQueueTime = 250;
-    protected ExecutorService executorService;
-    protected boolean streamDeletes;
-
-    /**
-     * Initialize a Builder object, based on the provided Solr URL.
-     *
-     * <p>The provided URL must point to the root Solr path ("/solr"), for example:
-     *
-     * <pre>
-     *   SolrClient client = new ConcurrentUpdateSolrClient.Builder("http://my-solr-server:8983/solr")
-     *       .withDefaultCollection("core1")
-     *       .build();
-     *   QueryResponse resp = client.query(new SolrQuery("*:*"));
-     * </pre>
-     */
-    public Builder(String baseSolrUrl) {
-      this.baseSolrUrl = baseSolrUrl;
-    }
-
-    /**
-     * The maximum number of requests buffered by the SolrClient's internal queue before being
-     * processed by background threads.
-     *
-     * <p>This value should be carefully paired with the number of queue-consumer threads. A queue
-     * with a maximum size set too high may require more memory. A queue with a maximum size set too
-     * low may suffer decreased throughput as {@link
-     * ConcurrentUpdateSolrClient#request(SolrRequest)} calls block waiting to add requests to the
-     * queue.
-     *
-     * <p>If not set, this defaults to 10.
-     *
-     * @see #withThreadCount(int)
-     */
-    public Builder withQueueSize(int queueSize) {
-      if (queueSize <= 0) {
-        throw new IllegalArgumentException("queueSize must be a positive integer.");
-      }
-      this.queueSize = queueSize;
-      return this;
-    }
-
-    /**
-     * The maximum number of threads used to empty {@link ConcurrentUpdateSolrClient}s queue.
-     *
-     * <p>Threads are created when documents are added to the client's internal queue and exit when
-     * no updates remain in the queue.
-     *
-     * <p>This value should be carefully paired with the maximum queue capacity. A client with too
-     * few threads may suffer decreased throughput as the queue fills up and {@link
-     * ConcurrentUpdateSolrClient#request(SolrRequest)} calls block waiting to add requests to the
-     * queue.
-     */
-    public Builder withThreadCount(int threadCount) {
-      if (threadCount <= 0) {
-        throw new IllegalArgumentException("threadCount must be a positive integer.");
-      }
-
-      this.threadCount = threadCount;
-      return this;
-    }
-
-    public Builder withPollQueueTime(int pollQueueTime) {
-      this.pollQueueTime = pollQueueTime;
-      return this;
-    }
-
-    /**
-     * Provides the {@link ExecutorService} for the created client to use when servicing the
-     * update-request queue.
-     */
-    public Builder withExecutorService(ExecutorService executorService) {
-      this.executorService = executorService;
-      return this;
-    }
-
-    /**
-     * Configures created clients to always stream delete requests.
-     *
-     * <p>Streamed deletes are put into the update-queue and executed like any other update request.
-     */
-    public Builder alwaysStreamDeletes() {
-      this.streamDeletes = true;
-      return this;
-    }
-
-    /**
-     * Configures created clients to not stream delete requests.
-     *
-     * <p>With this option set when the created ConcurrentUpdateSolrClient sends a delete request it
-     * will first will lock the queue and block until all queued updates have been sent, and then
-     * send the delete request.
-     */
-    public Builder neverStreamDeletes() {
-      this.streamDeletes = false;
-      return this;
-    }
-
-    /** Create a {@link ConcurrentUpdateSolrClient} based on the provided configuration options. */
-    public ConcurrentUpdateSolrClient build() {
-      if (baseSolrUrl == null) {
-        throw new IllegalArgumentException(
-            "Cannot create HttpSolrClient without a valid baseSolrUrl!");
-      }
-
-      return new ConcurrentUpdateSolrClient(this);
-    }
-
-    @Override
-    public Builder getThis() {
-      return this;
-    }
-  }
-}
diff --git a/solr/test-framework/src/java/org/apache/solr/client/solrj/apache/HttpClientUtil.java b/solr/test-framework/src/java/org/apache/solr/client/solrj/apache/HttpClientUtil.java
deleted file mode 100644
index 59eea17c280..00000000000
--- a/solr/test-framework/src/java/org/apache/solr/client/solrj/apache/HttpClientUtil.java
+++ /dev/null
@@ -1,621 +0,0 @@
-/*
- * 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.apache;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.invoke.MethodHandles;
-import java.lang.reflect.InvocationTargetException;
-import java.nio.charset.StandardCharsets;
-import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.TimeUnit;
-import java.util.function.Consumer;
-import java.util.zip.GZIPInputStream;
-import java.util.zip.InflaterInputStream;
-import org.apache.http.Header;
-import org.apache.http.HeaderElement;
-import org.apache.http.HttpEntity;
-import org.apache.http.HttpException;
-import org.apache.http.HttpRequest;
-import org.apache.http.HttpRequestInterceptor;
-import org.apache.http.HttpResponse;
-import org.apache.http.HttpResponseInterceptor;
-import org.apache.http.auth.AuthScope;
-import org.apache.http.auth.UsernamePasswordCredentials;
-import org.apache.http.client.CredentialsProvider;
-import org.apache.http.client.HttpClient;
-import org.apache.http.client.config.RequestConfig;
-import org.apache.http.client.config.RequestConfig.Builder;
-import org.apache.http.client.methods.HttpGet;
-import org.apache.http.client.methods.HttpRequestBase;
-import org.apache.http.client.protocol.HttpClientContext;
-import org.apache.http.config.Registry;
-import org.apache.http.config.RegistryBuilder;
-import org.apache.http.conn.ConnectionKeepAliveStrategy;
-import org.apache.http.conn.socket.ConnectionSocketFactory;
-import org.apache.http.conn.socket.PlainConnectionSocketFactory;
-import org.apache.http.conn.ssl.NoopHostnameVerifier;
-import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
-import org.apache.http.entity.HttpEntityWrapper;
-import org.apache.http.impl.client.BasicCredentialsProvider;
-import org.apache.http.impl.client.CloseableHttpClient;
-import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
-import org.apache.http.impl.client.HttpClientBuilder;
-import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
-import org.apache.http.protocol.HttpContext;
-import org.apache.http.protocol.HttpRequestExecutor;
-import org.apache.http.ssl.SSLContexts;
-import org.apache.http.util.EntityUtils;
-import org.apache.solr.client.solrj.impl.SolrHttpConstants;
-import org.apache.solr.common.SolrException;
-import org.apache.solr.common.params.ModifiableSolrParams;
-import org.apache.solr.common.params.SolrParams;
-import org.apache.solr.common.util.IOUtils;
-import org.apache.solr.common.util.ObjectReleaseTracker;
-import org.apache.solr.common.util.Utils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Utility class for creating/configuring Apache {@link HttpClient} instances.
- *
- * <p>This class can touch internal HttpClient details and is subject to change.
- *
- * @lucene.experimental
- * @deprecated Used to configure the Apache HTTP client. Please use another client
- */
-@Deprecated(since = "9.0")
-public class HttpClientUtil implements SolrHttpConstants {
-
-  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
-
-  private static final int VALIDATE_AFTER_INACTIVITY_DEFAULT = 3000;
-  private static final int EVICT_IDLE_CONNECTIONS_DEFAULT = 50000;
-  private static final String VALIDATE_AFTER_INACTIVITY = "validateAfterInactivity";
-  private static final String EVICT_IDLE_CONNECTIONS = "evictIdleConnections";
-
-  // Retry http requests on error
-  public static final String PROP_USE_RETRY = "retry";
-  // Allow compression (deflate,gzip) if server supports it
-  public static final String PROP_ALLOW_COMPRESSION = "allowCompression";
-
-  // * NOTE* The following params configure the default request config and this
-  // is overridden by SolrJ clients. Use the setters on the SolrJ clients
-  // to configure these settings if that is the intent.
-
-  // Follow redirects
-  public static final String PROP_FOLLOW_REDIRECTS = "followRedirects";
-
-  /**
-   * A Java system property to select the {@linkplain SocketFactoryRegistryProvider} used for
-   * configuring the Apache HTTP clients.
-   */
-  public static final String SYS_PROP_SOCKET_FACTORY_REGISTRY_PROVIDER =
-      "solr.httpclient.socketFactory.registry.provider";
-
-  static final DefaultHttpRequestRetryHandler NO_RETRY =
-      new DefaultHttpRequestRetryHandler(0, false);
-
-  private static volatile SolrHttpClientBuilder httpClientBuilder;
-
-  private static SolrHttpClientContextBuilder httpClientRequestContextBuilder =
-      new SolrHttpClientContextBuilder();
-
-  private static volatile SocketFactoryRegistryProvider socketFactoryRegistryProvider;
-  private static volatile String cookiePolicy;
-  private static final List<HttpRequestInterceptor> interceptors = new CopyOnWriteArrayList<>();
-
-  static {
-    resetHttpClientBuilder();
-
-    // Configure the SocketFactoryRegistryProvider if user has specified the provider type.
-    String socketFactoryRegistryProviderClassName =
-        System.getProperty(SYS_PROP_SOCKET_FACTORY_REGISTRY_PROVIDER);
-    if (socketFactoryRegistryProviderClassName != null) {
-      log.debug("Using {}", socketFactoryRegistryProviderClassName);
-      try {
-        socketFactoryRegistryProvider =
-            Class.forName(socketFactoryRegistryProviderClassName)
-                .asSubclass(SocketFactoryRegistryProvider.class)
-                .getDeclaredConstructor()
-                .newInstance();
-      } catch (InstantiationException
-          | IllegalAccessException
-          | ClassNotFoundException
-          | InvocationTargetException
-          | NoSuchMethodException e) {
-        throw new RuntimeException("Unable to instantiate Solr SocketFactoryRegistryProvider", e);
-      }
-    }
-  }
-
-  public static <T> T executeGET(
-      HttpClient client, String url, Utils.InputStreamConsumer<T> consumer) throws SolrException {
-    return executeHttpMethod(client, url, consumer, new HttpGet(url));
-  }
-
-  public static <T> T executeHttpMethod(
-      HttpClient client,
-      String url,
-      Utils.InputStreamConsumer<T> consumer,
-      HttpRequestBase httpMethod) {
-    T result = null;
-    HttpResponse rsp;
-    try {
-      rsp = client.execute(httpMethod);
-    } catch (IOException e) {
-      log.error("Error in request to url : {}", url, e);
-      throw new SolrException(SolrException.ErrorCode.UNKNOWN, "Error sending request");
-    }
-    int statusCode = rsp.getStatusLine().getStatusCode();
-    if (statusCode != 200) {
-      try {
-        log.error(
-            "Failed a request to: {}, status: {}, body: {}",
-            url,
-            rsp.getStatusLine(),
-            EntityUtils.toString(rsp.getEntity(), StandardCharsets.UTF_8)); // nowarn
-      } catch (IOException e) {
-        log.error("could not print error", e);
-      }
-      throw new SolrException(SolrException.ErrorCode.getErrorCode(statusCode), "Unknown error");
-    }
-    HttpEntity entity = rsp.getEntity();
-    try {
-      InputStream is = entity.getContent();
-      if (consumer != null) {
-
-        result = consumer.accept(is);
-      }
-    } catch (IOException e) {
-      throw new SolrException(SolrException.ErrorCode.UNKNOWN, e);
-    } finally {
-      consumeFully(entity);
-    }
-    return result;
-  }
-
-  /**
-   * If the passed entity has content, make sure it is fully read and closed.
-   *
-   * @param entity to consume or null
-   */
-  public static void consumeFully(HttpEntity entity) {
-    if (entity != null) {
-      try {
-        // make sure the stream is full read
-        Utils.readFully(entity.getContent());
-      } catch (UnsupportedOperationException e) {
-        // nothing to do then
-      } catch (IOException e) {
-        // quiet
-      } finally {
-        // close the stream
-        EntityUtils.consumeQuietly(entity);
-      }
-    }
-  }
-
-  public abstract static class SocketFactoryRegistryProvider {
-    /** Must be non-null */
-    public abstract Registry<ConnectionSocketFactory> getSocketFactoryRegistry();
-  }
-
-  private static class DynamicInterceptor implements HttpRequestInterceptor {
-
-    @Override
-    public void process(HttpRequest request, HttpContext context)
-        throws HttpException, IOException {
-      // don't synchronize traversal - can lead to deadlock - CopyOnWriteArrayList is critical
-      // we also do not want to have to acquire the mutex when the list is empty or put a global
-      // mutex around the process calls
-      interceptors.forEach(
-          new Consumer<HttpRequestInterceptor>() {
-
-            @Override
-            public void accept(HttpRequestInterceptor interceptor) {
-              try {
-                interceptor.process(request, context);
-              } catch (Exception e) {
-                log.error("", e);
-              }
-            }
-          });
-    }
-  }
-
-  public static void setHttpClientBuilder(SolrHttpClientBuilder newHttpClientBuilder) {
-    httpClientBuilder = newHttpClientBuilder;
-  }
-
-  public static void setHttpClientProvider(SolrHttpClientBuilder newHttpClientBuilder) {
-    httpClientBuilder = newHttpClientBuilder;
-  }
-
-  /**
-   * @see #SYS_PROP_CHECK_PEER_NAME
-   */
-  public static void setSocketFactoryRegistryProvider(
-      SocketFactoryRegistryProvider newRegistryProvider) {
-    socketFactoryRegistryProvider = newRegistryProvider;
-  }
-
-  public static SolrHttpClientBuilder getHttpClientBuilder() {
-    return httpClientBuilder;
-  }
-
-  /**
-   * @see #SYS_PROP_CHECK_PEER_NAME
-   */
-  public static SocketFactoryRegistryProvider getSocketFactoryRegistryProvider() {
-    return socketFactoryRegistryProvider;
-  }
-
-  public static void resetHttpClientBuilder() {
-    socketFactoryRegistryProvider = new DefaultSocketFactoryRegistryProvider();
-    httpClientBuilder = SolrHttpClientBuilder.create();
-  }
-
-  private static final class DefaultSocketFactoryRegistryProvider
-      extends SocketFactoryRegistryProvider {
-    @Override
-    public Registry<ConnectionSocketFactory> getSocketFactoryRegistry() {
-      // this mimics PoolingHttpClientConnectionManager's default behavior,
-      // except that we explicitly use SSLConnectionSocketFactory.getSystemSocketFactory()
-      // to pick up the system level default SSLContext (where javax.net.ssl.* properties
-      // related to keystore & truststore are specified)
-      RegistryBuilder<ConnectionSocketFactory> builder =
-          RegistryBuilder.<ConnectionSocketFactory>create();
-      builder.register("http", PlainConnectionSocketFactory.getSocketFactory());
-
-      // logic to turn off peer host check
-      SSLConnectionSocketFactory sslConnectionSocketFactory = null;
-      boolean sslCheckPeerName =
-          toBooleanDefaultIfNull(
-              toBooleanObject(System.getProperty(HttpClientUtil.SYS_PROP_CHECK_PEER_NAME)), true);
-      if (sslCheckPeerName) {
-        sslConnectionSocketFactory = SSLConnectionSocketFactory.getSystemSocketFactory();
-      } else {
-        sslConnectionSocketFactory =
-            new SSLConnectionSocketFactory(
-                SSLContexts.createSystemDefault(), NoopHostnameVerifier.INSTANCE);
-        log.debug(
-            "{} is false, hostname checks disabled.", HttpClientUtil.SYS_PROP_CHECK_PEER_NAME);
-      }
-      builder.register("https", sslConnectionSocketFactory);
-
-      return builder.build();
-    }
-  }
-
-  /**
-   * Creates new http client by using the provided configuration.
-   *
-   * @param params http client configuration, if null a client with default configuration (no
-   *     additional configuration) is created.
-   */
-  public static CloseableHttpClient createClient(SolrParams params) {
-    return createClient(params, createPoolingConnectionManager());
-  }
-
-  /** test usage subject to change @lucene.experimental */
-  static PoolingHttpClientConnectionManager createPoolingConnectionManager() {
-    return new PoolingHttpClientConnectionManager(
-        socketFactoryRegistryProvider.getSocketFactoryRegistry());
-  }
-
-  public static CloseableHttpClient createClient(
-      SolrParams params, PoolingHttpClientConnectionManager cm) {
-    if (params == null) {
-      params = new ModifiableSolrParams();
-    }
-
-    return createClient(params, cm, false);
-  }
-
-  public static CloseableHttpClient createClient(
-      final SolrParams params,
-      PoolingHttpClientConnectionManager cm,
-      boolean sharedConnectionManager,
-      HttpRequestExecutor httpRequestExecutor) {
-    final ModifiableSolrParams config = new ModifiableSolrParams(params);
-    if (log.isDebugEnabled()) {
-      log.debug("Creating new http client, config: {}", config);
-    }
-
-    cm.setMaxTotal(params.getInt(HttpClientUtil.PROP_MAX_CONNECTIONS, 10000));
-    cm.setDefaultMaxPerRoute(params.getInt(HttpClientUtil.PROP_MAX_CONNECTIONS_PER_HOST, 10000));
-    cm.setValidateAfterInactivity(
-        Integer.getInteger(VALIDATE_AFTER_INACTIVITY, VALIDATE_AFTER_INACTIVITY_DEFAULT));
-
-    HttpClientBuilder newHttpClientBuilder = HttpClientBuilder.create();
-
-    if (sharedConnectionManager) {
-      newHttpClientBuilder.setConnectionManagerShared(true);
-    } else {
-      newHttpClientBuilder.setConnectionManagerShared(false);
-    }
-
-    ConnectionKeepAliveStrategy keepAliveStrat =
-        new ConnectionKeepAliveStrategy() {
-          @Override
-          public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
-            // we only close connections based on idle time, not ttl expiration
-            return -1;
-          }
-        };
-
-    if (httpClientBuilder.getAuthSchemeRegistryProvider() != null) {
-      newHttpClientBuilder.setDefaultAuthSchemeRegistry(
-          httpClientBuilder.getAuthSchemeRegistryProvider().getAuthSchemeRegistry());
-    }
-    if (httpClientBuilder.getCookieSpecRegistryProvider() != null) {
-      newHttpClientBuilder.setDefaultCookieSpecRegistry(
-          httpClientBuilder.getCookieSpecRegistryProvider().getCookieSpecRegistry());
-    }
-    if (httpClientBuilder.getCredentialsProviderProvider() != null) {
-      newHttpClientBuilder.setDefaultCredentialsProvider(
-          httpClientBuilder.getCredentialsProviderProvider().getCredentialsProvider());
-    }
-
-    newHttpClientBuilder.addInterceptorLast(new DynamicInterceptor());
-
-    newHttpClientBuilder =
-        newHttpClientBuilder
-            .setKeepAliveStrategy(keepAliveStrat)
-            .evictIdleConnections(
-                (long) Integer.getInteger(EVICT_IDLE_CONNECTIONS, EVICT_IDLE_CONNECTIONS_DEFAULT),
-                TimeUnit.MILLISECONDS);
-
-    if (httpRequestExecutor != null) {
-      newHttpClientBuilder.setRequestExecutor(httpRequestExecutor);
-    }
-
-    HttpClientBuilder builder = setupBuilder(newHttpClientBuilder, params);
-
-    HttpClient httpClient = builder.setConnectionManager(cm).build();
-
-    assert ObjectReleaseTracker.track(httpClient);
-    return (CloseableHttpClient) httpClient;
-  }
-
-  /** Creates new http client by using the provided configuration. */
-  public static CloseableHttpClient createClient(
-      final SolrParams params,
-      PoolingHttpClientConnectionManager cm,
-      boolean sharedConnectionManager) {
-    return createClient(params, cm, sharedConnectionManager, null);
-  }
-
-  private static HttpClientBuilder setupBuilder(HttpClientBuilder builder, SolrParams config) {
-
-    Builder requestConfigBuilder =
-        RequestConfig.custom()
-            .setRedirectsEnabled(config.getBool(HttpClientUtil.PROP_FOLLOW_REDIRECTS, false))
-            .setDecompressionEnabled(false)
-            .setConnectTimeout(
-                config.getInt(HttpClientUtil.PROP_CONNECTION_TIMEOUT, DEFAULT_CONNECT_TIMEOUT))
-            .setSocketTimeout(config.getInt(HttpClientUtil.PROP_SO_TIMEOUT, DEFAULT_SO_TIMEOUT));
-
-    String cpolicy = cookiePolicy;
-    if (cpolicy != null) {
-      requestConfigBuilder.setCookieSpec(cpolicy);
-    }
-
-    RequestConfig requestConfig = requestConfigBuilder.build();
-
-    HttpClientBuilder retBuilder = builder.setDefaultRequestConfig(requestConfig);
-
-    if (config.getBool(HttpClientUtil.PROP_USE_RETRY, true)) {
-      retBuilder =
-          retBuilder.setRetryHandler(
-              new SolrHttpRequestRetryHandler(Integer.getInteger("solr.httpclient.retries", 3)));
-
-    } else {
-      retBuilder = retBuilder.setRetryHandler(NO_RETRY);
-    }
-
-    final String basicAuthUser = config.get(HttpClientUtil.PROP_BASIC_AUTH_USER);
-    final String basicAuthPass = config.get(HttpClientUtil.PROP_BASIC_AUTH_PASS);
-
-    if (basicAuthUser != null && basicAuthPass != null) {
-      CredentialsProvider credsProvider = new BasicCredentialsProvider();
-      credsProvider.setCredentials(
-          AuthScope.ANY, new UsernamePasswordCredentials(basicAuthUser, basicAuthPass));
-      retBuilder.setDefaultCredentialsProvider(credsProvider);
-    }
-
-    if (config.getBool(HttpClientUtil.PROP_ALLOW_COMPRESSION, false)) {
-      retBuilder.addInterceptorFirst(new UseCompressionRequestInterceptor());
-      retBuilder.addInterceptorFirst(new UseCompressionResponseInterceptor());
-    } else {
-      retBuilder.disableContentCompression();
-    }
-
-    retBuilder.useSystemProperties();
-    return retBuilder;
-  }
-
-  public static void close(HttpClient httpClient) {
-
-    IOUtils.closeQuietly((CloseableHttpClient) httpClient);
-
-    assert ObjectReleaseTracker.release(httpClient);
-  }
-
-  public static void addRequestInterceptor(HttpRequestInterceptor interceptor) {
-    interceptors.add(interceptor);
-  }
-
-  public static void removeRequestInterceptor(HttpRequestInterceptor interceptor) {
-    interceptors.remove(interceptor);
-  }
-
-  public static void clearRequestInterceptors() {
-    interceptors.clear();
-  }
-
-  private static class UseCompressionRequestInterceptor implements HttpRequestInterceptor {
-
-    @Override
-    public void process(HttpRequest request, HttpContext context)
-        throws HttpException, IOException {
-      if (!request.containsHeader("Accept-Encoding")) {
-        request.addHeader("Accept-Encoding", "gzip, deflate");
-      }
-    }
-  }
-
-  private static class UseCompressionResponseInterceptor implements HttpResponseInterceptor {
-
-    @Override
-    public void process(final HttpResponse response, final HttpContext context)
-        throws HttpException, IOException {
-
-      HttpEntity entity = response.getEntity();
-      Header ceheader = entity.getContentEncoding();
-      if (ceheader != null) {
-        HeaderElement[] codecs = ceheader.getElements();
-        for (int i = 0; i < codecs.length; i++) {
-          if (codecs[i].getName().equalsIgnoreCase("gzip")) {
-            response.setEntity(new GzipDecompressingEntity(response.getEntity()));
-            return;
-          }
-          if (codecs[i].getName().equalsIgnoreCase("deflate")) {
-            response.setEntity(new DeflateDecompressingEntity(response.getEntity()));
-            return;
-          }
-        }
-      }
-    }
-  }
-
-  protected static class GzipDecompressingEntity extends HttpEntityWrapper {
-    private boolean gzipInputStreamCreated = false;
-    private InputStream gzipInputStream = null;
-
-    public GzipDecompressingEntity(final HttpEntity entity) {
-      super(entity);
-    }
-
-    /**
-     * Return a InputStream of uncompressed data. If there is an issue with the compression of the
-     * data, a null InputStream will be returned, and the underlying compressed InputStream will be
-     * closed.
-     *
-     * <p>The same input stream will be returned if the underlying entity is not repeatable. If the
-     * underlying entity is repeatable, then a new input stream will be created.
-     */
-    @Override
-    public InputStream getContent() throws IOException, IllegalStateException {
-      if (!gzipInputStreamCreated || wrappedEntity.isRepeatable()) {
-        gzipInputStreamCreated = true;
-        InputStream wrappedContent = wrappedEntity.getContent();
-        if (wrappedContent != null) {
-          try {
-            gzipInputStream = new GZIPInputStream(wrappedContent);
-          } catch (IOException ioException) {
-            try (wrappedContent) {
-              Utils.readFully(wrappedContent);
-            } catch (IOException ignored) {
-            }
-            throw new IOException("Cannot open GZipInputStream for response", ioException);
-          }
-        }
-      }
-      return gzipInputStream;
-    }
-
-    @Override
-    public long getContentLength() {
-      return -1;
-    }
-  }
-
-  private static class DeflateDecompressingEntity extends GzipDecompressingEntity {
-    public DeflateDecompressingEntity(final HttpEntity entity) {
-      super(entity);
-    }
-
-    @Override
-    public InputStream getContent() throws IOException, IllegalStateException {
-      // InflaterInputStream does not throw a ZipException in the constructor,
-      // so it does not need the same checks as the GZIPInputStream.
-      return new InflaterInputStream(wrappedEntity.getContent());
-    }
-  }
-
-  public static void setHttpClientRequestContextBuilder(
-      SolrHttpClientContextBuilder httpClientContextBuilder) {
-    httpClientRequestContextBuilder = httpClientContextBuilder;
-  }
-
-  /**
-   * Create a HttpClientContext object and {@link HttpClientContext#setUserToken(Object)} to an
-   * internal singleton. It allows reusing underlying {@link HttpClient} in connection pools if
-   * client authentication is enabled.
-   */
-  public static HttpClientContext createNewHttpClientRequestContext() {
-    HttpClientContext context =
-        httpClientRequestContextBuilder.createContext(HttpSolrClient.cacheKey);
-
-    return context;
-  }
-
-  public static Builder createDefaultRequestConfigBuilder() {
-    String cpolicy = cookiePolicy;
-    Builder builder = RequestConfig.custom();
-
-    builder
-        .setSocketTimeout(DEFAULT_SO_TIMEOUT)
-        .setConnectTimeout(DEFAULT_CONNECT_TIMEOUT)
-        .setRedirectsEnabled(false)
-        .setDecompressionEnabled(false); // we do our own compression / decompression
-    if (cpolicy != null) {
-      builder.setCookieSpec(cpolicy);
-    }
-    return builder;
-  }
-
-  public static void setCookiePolicy(String policyName) {
-    cookiePolicy = policyName;
-  }
-
-  /**
-   * @lucene.internal
-   */
-  static boolean toBooleanDefaultIfNull(Boolean bool, boolean valueIfNull) {
-    if (bool == null) {
-      return valueIfNull;
-    }
-    return bool.booleanValue() ? true : false;
-  }
-
-  /**
-   * @lucene.internal
-   */
-  static Boolean toBooleanObject(String str) {
-    if ("true".equalsIgnoreCase(str)) {
-      return Boolean.TRUE;
-    } else if ("false".equalsIgnoreCase(str)) {
-      return Boolean.FALSE;
-    }
-    // no match
-    return null;
-  }
-}
diff --git a/solr/test-framework/src/java/org/apache/solr/client/solrj/apache/HttpClusterStateProvider.java b/solr/test-framework/src/java/org/apache/solr/client/solrj/apache/HttpClusterStateProvider.java
deleted file mode 100644
index 9cbc41899f4..00000000000
--- a/solr/test-framework/src/java/org/apache/solr/client/solrj/apache/HttpClusterStateProvider.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * 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.apache;
-
-import java.io.IOException;
-import java.util.List;
-import org.apache.http.client.HttpClient;
-import org.apache.solr.client.solrj.SolrClient;
-import org.apache.solr.client.solrj.impl.BaseHttpClusterStateProvider;
-
-/**
- * Retrieves cluster state via Solr HTTP APIs
- *
- * @deprecated Please use {@link org.apache.solr.client.solrj.impl.HttpClusterStateProvider}
- */
-@Deprecated(since = "9.0")
-public class HttpClusterStateProvider extends BaseHttpClusterStateProvider {
-
-  private final HttpClient httpClient;
-  private final boolean clientIsInternal;
-
-  public HttpClusterStateProvider(List<String> solrUrls, HttpClient httpClient) throws Exception {
-    this.httpClient = httpClient == null ? HttpClientUtil.createClient(null) : httpClient;
-    this.clientIsInternal = httpClient == null;
-    initConfiguredNodes(solrUrls);
-  }
-
-  @Override
-  protected SolrClient getSolrClient(String baseUrl) {
-    return new HttpSolrClient.Builder().withBaseSolrUrl(baseUrl).withHttpClient(httpClient).build();
-  }
-
-  @Override
-  public void close() throws IOException {
-    if (this.clientIsInternal && this.httpClient != null) {
-      HttpClientUtil.close(httpClient);
-    }
-    super.close();
-  }
-}
diff --git a/solr/test-framework/src/java/org/apache/solr/client/solrj/apache/HttpSolrClient.java b/solr/test-framework/src/java/org/apache/solr/client/solrj/apache/HttpSolrClient.java
deleted file mode 100644
index 76bcd7749a3..00000000000
--- a/solr/test-framework/src/java/org/apache/solr/client/solrj/apache/HttpSolrClient.java
+++ /dev/null
@@ -1,873 +0,0 @@
-/*
- * 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.apache;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.UnsupportedEncodingException;
-import java.lang.invoke.MethodHandles;
-import java.net.ConnectException;
-import java.net.SocketTimeoutException;
-import java.net.URLDecoder;
-import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
-import java.security.Principal;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Base64;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Future;
-import java.util.stream.Collectors;
-import org.apache.http.Header;
-import org.apache.http.HttpEntity;
-import org.apache.http.HttpMessage;
-import org.apache.http.HttpResponse;
-import org.apache.http.HttpStatus;
-import org.apache.http.NameValuePair;
-import org.apache.http.client.HttpClient;
-import org.apache.http.client.config.RequestConfig;
-import org.apache.http.client.entity.UrlEncodedFormEntity;
-import org.apache.http.client.methods.HttpDelete;
-import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
-import org.apache.http.client.methods.HttpGet;
-import org.apache.http.client.methods.HttpPost;
-import org.apache.http.client.methods.HttpPut;
-import org.apache.http.client.methods.HttpRequestBase;
-import org.apache.http.client.methods.HttpUriRequest;
-import org.apache.http.client.protocol.HttpClientContext;
-import org.apache.http.conn.HttpClientConnectionManager;
-import org.apache.http.entity.BasicHttpEntity;
-import org.apache.http.entity.ContentType;
-import org.apache.http.entity.InputStreamEntity;
-import org.apache.http.entity.mime.FormBodyPart;
-import org.apache.http.entity.mime.HttpMultipartMode;
-import org.apache.http.entity.mime.MultipartEntity;
-import org.apache.http.entity.mime.content.InputStreamBody;
-import org.apache.http.entity.mime.content.StringBody;
-import org.apache.http.message.BasicHeader;
-import org.apache.http.message.BasicNameValuePair;
-import org.apache.solr.client.api.util.SolrVersion;
-import org.apache.solr.client.solrj.RemoteSolrException;
-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.HttpJdkSolrClient;
-import org.apache.solr.client.solrj.jetty.HttpJettySolrClient;
-import org.apache.solr.client.solrj.request.JavaBinRequestWriter;
-import org.apache.solr.client.solrj.request.RequestWriter;
-import org.apache.solr.client.solrj.response.InputStreamResponseParser;
-import org.apache.solr.client.solrj.response.JavaBinResponseParser;
-import org.apache.solr.client.solrj.response.ResponseParser;
-import org.apache.solr.client.solrj.util.ClientUtils;
-import org.apache.solr.common.SolrException;
-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.ContentStream;
-import org.apache.solr.common.util.ExecutorUtil;
-import org.apache.solr.common.util.NamedList;
-import org.apache.solr.common.util.SolrNamedThreadFactory;
-import org.slf4j.MDC;
-
-/**
- * A SolrClient implementation that talks directly to a Solr server via Apache HTTP client
- *
- * @deprecated Please use {@link HttpJettySolrClient} or {@link HttpJdkSolrClient}
- */
-@Deprecated(since = "9.0")
-public class HttpSolrClient extends SolrClient {
-
-  private static final Charset FALLBACK_CHARSET = StandardCharsets.UTF_8;
-  private static final long serialVersionUID = -946812319974801896L;
-
-  protected static final Set<Integer> UNMATCHED_ACCEPTED_ERROR_CODES = Set.of(429);
-
-  static final String USER_AGENT =
-      "Solr[" + MethodHandles.lookup().lookupClass().getName() + "] " + SolrVersion.LATEST_STRING;
-
-  static final Class<HttpSolrClient> cacheKey = HttpSolrClient.class;
-
-  /** The URL of the Solr server. */
-  protected volatile String baseUrl;
-
-  /**
-   * Default value: null / empty.
-   *
-   * <p>Parameters that are added to every request regardless. This may be a place to add something
-   * like an authentication token.
-   */
-  protected ModifiableSolrParams invariantParams;
-
-  /**
-   * Default response parser is BinaryResponseParser
-   *
-   * <p>This parser represents the default Response Parser chosen to parse the response if the
-   * parser were not specified as part of the request.
-   *
-   * @see JavaBinResponseParser
-   */
-  protected volatile ResponseParser parser;
-
-  /**
-   * The RequestWriter used to write all requests to Solr
-   *
-   * @see org.apache.solr.client.solrj.request.RequestWriter
-   */
-  protected volatile RequestWriter requestWriter = new JavaBinRequestWriter();
-
-  private final HttpClient httpClient;
-
-  private volatile Boolean followRedirects = false;
-
-  private volatile boolean useMultiPartPost;
-  private final boolean internalClient;
-
-  private volatile Set<String> urlParamNames = Set.of();
-  private final int connectionTimeout;
-  private final int soTimeout;
-
-  protected HttpSolrClient(Builder builder) {
-    this.baseUrl = builder.baseSolrUrl;
-    if (baseUrl.endsWith("/")) {
-      baseUrl = baseUrl.substring(0, baseUrl.length() - 1);
-    }
-
-    if (baseUrl.indexOf('?') >= 0) {
-      throw new RuntimeException(
-          "Invalid base url for solrj.  The base URL must not contain parameters: " + baseUrl);
-    }
-
-    if (builder.httpClient != null) {
-      this.internalClient = false;
-      this.followRedirects = builder.followRedirects;
-      this.httpClient = builder.httpClient;
-    } else {
-      this.internalClient = true;
-      this.followRedirects = builder.followRedirects;
-      ModifiableSolrParams params = new ModifiableSolrParams();
-      params.set(HttpClientUtil.PROP_FOLLOW_REDIRECTS, followRedirects);
-      params.set(HttpClientUtil.PROP_ALLOW_COMPRESSION, builder.compression);
-      params.set(HttpClientUtil.PROP_CONNECTION_TIMEOUT, builder.connectionTimeoutMillis);
-      params.set(HttpClientUtil.PROP_SO_TIMEOUT, builder.socketTimeoutMillis);
-      httpClient = HttpClientUtil.createClient(params);
-    }
-
-    if (builder.requestWriter != null) {
-      this.requestWriter = builder.requestWriter;
-    }
-
-    this.parser = builder.responseParser;
-    this.invariantParams = builder.invariantParams;
-    this.connectionTimeout = builder.connectionTimeoutMillis;
-    this.soTimeout = builder.socketTimeoutMillis;
-    this.useMultiPartPost = builder.useMultiPartPost;
-    this.urlParamNames = builder.urlParamNames;
-    this.defaultCollection = builder.defaultCollection;
-  }
-
-  public Set<String> getUrlParamNames() {
-    return urlParamNames;
-  }
-
-  /**
-   * Returns the connection timeout, and should be based on httpClient overriding the solrClient.
-   * For unit testing.
-   *
-   * @return the connection timeout
-   */
-  int getConnectionTimeout() {
-    return this.connectionTimeout;
-  }
-
-  /**
-   * Returns the socket timeout, and should be based on httpClient overriding the solrClient. For
-   * unit testing.
-   *
-   * @return the socket timeout
-   */
-  int getSocketTimeout() {
-    return this.soTimeout;
-  }
-
-  /**
-   * Process the request. If {@link org.apache.solr.client.solrj.SolrRequest#getResponseParser()} is
-   * null, then use {@link #getParser()}
-   *
-   * @param request The {@link org.apache.solr.client.solrj.SolrRequest} to process
-   * @return The {@link org.apache.solr.common.util.NamedList} result
-   * @throws IOException If there is a low-level I/O error.
-   * @see #request(org.apache.solr.client.solrj.SolrRequest,
-   *     org.apache.solr.client.solrj.response.ResponseParser)
-   */
-  @Override
-  public NamedList<Object> request(final SolrRequest<?> request, String collection)
-      throws SolrServerException, IOException {
-    ResponseParser responseParser = request.getResponseParser();
-    if (responseParser == null) {
-      responseParser = parser;
-    }
-    return request(request, responseParser, collection);
-  }
-
-  public NamedList<Object> request(final SolrRequest<?> request, final ResponseParser processor)
-      throws SolrServerException, IOException {
-    return request(request, processor, null);
-  }
-
-  public NamedList<Object> request(
-      final SolrRequest<?> request, final ResponseParser processor, String collection)
-      throws SolrServerException, IOException {
-    if (ClientUtils.shouldApplyDefaultCollection(collection, request))
-      collection = defaultCollection;
-    HttpRequestBase method = createMethod(request, collection);
-    setBasicAuthHeader(request, method);
-    if (request.getHeaders() != null) {
-      Map<String, String> headers = request.getHeaders();
-      for (Map.Entry<String, String> entry : headers.entrySet()) {
-        method.setHeader(entry.getKey(), entry.getValue());
-      }
-    }
-    return executeMethod(method, request.getUserPrincipal(), processor, isV2ApiRequest(request));
-  }
-
-  private boolean isV2ApiRequest(final SolrRequest<?> request) {
-    return request.getApiVersion() == SolrRequest.ApiVersion.V2;
-  }
-
-  private void setBasicAuthHeader(SolrRequest<?> request, HttpRequestBase method)
-      throws UnsupportedEncodingException {
-    if (request.getBasicAuthUser() != null && request.getBasicAuthPassword() != null) {
-      String userPass = request.getBasicAuthUser() + ":" + request.getBasicAuthPassword();
-      String encoded = Base64.getEncoder().encodeToString(userPass.getBytes(FALLBACK_CHARSET));
-      method.setHeader(new BasicHeader("Authorization", "Basic " + encoded));
-    }
-  }
-
-  /**
-   * @lucene.experimental
-   */
-  public static class HttpUriRequestResponse {
-    public HttpUriRequest httpUriRequest;
-    public Future<NamedList<Object>> future;
-  }
-
-  /**
-   * @lucene.experimental
-   */
-  public HttpUriRequestResponse httpUriRequest(final SolrRequest<?> request)
-      throws SolrServerException, IOException {
-    ResponseParser responseParser = request.getResponseParser();
-    if (responseParser == null) {
-      responseParser = parser;
-    }
-    return httpUriRequest(request, responseParser);
-  }
-
-  /**
-   * @lucene.experimental
-   */
-  public HttpUriRequestResponse httpUriRequest(
-      final SolrRequest<?> request, final ResponseParser processor)
-      throws SolrServerException, IOException {
-    HttpUriRequestResponse mrr = new HttpUriRequestResponse();
-    final HttpRequestBase method = createMethod(request, defaultCollection);
-    ExecutorService pool =
-        ExecutorUtil.newMDCAwareFixedThreadPool(1, new SolrNamedThreadFactory("httpUriRequest"));
-    try {
-      MDC.put("HttpSolrClient.url", baseUrl);
-      mrr.future =
-          pool.submit(
-              () ->
-                  executeMethod(
-                      method, request.getUserPrincipal(), processor, isV2ApiRequest(request)));
-
-    } finally {
-      pool.shutdown();
-      MDC.remove("HttpSolrClient.url");
-    }
-    assert method != null;
-    mrr.httpUriRequest = method;
-    return mrr;
-  }
-
-  protected ModifiableSolrParams calculateQueryParams(
-      Set<String> queryParamNames, ModifiableSolrParams wparams) {
-    ModifiableSolrParams queryModParams = new ModifiableSolrParams();
-    if (queryParamNames != null) {
-      for (String param : queryParamNames) {
-        String[] value = wparams.getParams(param);
-        if (value != null) {
-          for (String v : value) {
-            queryModParams.add(param, v);
-          }
-          wparams.remove(param);
-        }
-      }
-    }
-    return queryModParams;
-  }
-
-  protected HttpRequestBase createMethod(SolrRequest<?> request, String collection)
-      throws IOException, SolrServerException {
-    SolrParams params = request.getParams();
-    RequestWriter.ContentWriter contentWriter = requestWriter.getContentWriter(request);
-    Collection<ContentStream> streams =
-        contentWriter == null ? requestWriter.getContentStreams(request) : null;
-
-    final String requestUrlBeforeParams = ClientUtils.buildRequestUrl(request, baseUrl, collection);
-
-    ResponseParser parser = request.getResponseParser();
-    if (parser == null) {
-      parser = this.parser;
-    }
-
-    Header[] contextHeaders = buildRequestSpecificHeaders(request);
-
-    // The parser 'wt=' param is used instead of the original params
-    ModifiableSolrParams wparams = new ModifiableSolrParams(params);
-    if (parser != null) {
-      wparams.set(CommonParams.WT, parser.getWriterType());
-    }
-    if (invariantParams != null) {
-      wparams.add(invariantParams);
-    }
-
-    if (SolrRequest.METHOD.GET == request.getMethod()) {
-      if (streams != null || contentWriter != null) {
-        throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "GET can't send streams!");
-      }
-
-      HttpGet result = new HttpGet(requestUrlBeforeParams + wparams.toQueryString());
-
-      populateHeaders(result, contextHeaders);
-      return result;
-    }
-
-    if (SolrRequest.METHOD.DELETE == request.getMethod()) {
-      return new HttpDelete(requestUrlBeforeParams + wparams.toQueryString());
-    }
-
-    if (SolrRequest.METHOD.POST == request.getMethod()
-        || SolrRequest.METHOD.PUT == request.getMethod()) {
-
-      boolean hasNullStreamName = false;
-      if (streams != null) {
-        for (ContentStream cs : streams) {
-          if (cs.getName() == null) {
-            hasNullStreamName = true;
-            break;
-          }
-        }
-      }
-      boolean isMultipart =
-          ((this.useMultiPartPost && SolrRequest.METHOD.POST == request.getMethod())
-                  || (streams != null && streams.size() > 1))
-              && !hasNullStreamName;
-
-      List<NameValuePair> postOrPutParams = new ArrayList<>();
-
-      if (contentWriter != null) {
-        String fullQueryUrl = requestUrlBeforeParams + wparams.toQueryString();
-        HttpEntityEnclosingRequestBase postOrPut =
-            SolrRequest.METHOD.POST == request.getMethod()
-                ? new HttpPost(fullQueryUrl)
-                : new HttpPut(fullQueryUrl);
-        postOrPut.addHeader("Content-Type", contentWriter.getContentType());
-        postOrPut.setEntity(
-            new BasicHttpEntity() {
-              @Override
-              public boolean isStreaming() {
-                return true;
-              }
-
-              @Override
-              public void writeTo(OutputStream outstream) throws IOException {
-                contentWriter.write(outstream);
-              }
-            });
-
-        populateHeaders(postOrPut, contextHeaders);
-
-        return postOrPut;
-
-      } else if (streams == null || isMultipart) {
-        // send server list and request list as query string params
-        ModifiableSolrParams queryParams = calculateQueryParams(this.urlParamNames, wparams);
-        queryParams.add(calculateQueryParams(request.getQueryParams(), wparams));
-        String fullQueryUrl = requestUrlBeforeParams + queryParams.toQueryString();
-        HttpEntityEnclosingRequestBase postOrPut =
-            fillContentStream(
-                request, streams, wparams, isMultipart, postOrPutParams, fullQueryUrl);
-        return postOrPut;
-      }
-      // It is has one stream, it is the post body, put the params in the URL
-      else {
-        String fullQueryUrl = requestUrlBeforeParams + wparams.toQueryString();
-        HttpEntityEnclosingRequestBase postOrPut =
-            SolrRequest.METHOD.POST == request.getMethod()
-                ? new HttpPost(fullQueryUrl)
-                : new HttpPut(fullQueryUrl);
-        fillSingleContentStream(streams, postOrPut);
-
-        return postOrPut;
-      }
-    }
-
-    throw new SolrServerException("Unsupported method: " + request.getMethod());
-  }
-
-  private void fillSingleContentStream(
-      Collection<ContentStream> streams, HttpEntityEnclosingRequestBase postOrPut)
-      throws IOException {
-    // Single stream as body
-    // Using a loop just to get the first one
-    final ContentStream[] contentStream = new ContentStream[1];
-    for (ContentStream content : streams) {
-      contentStream[0] = content;
-      break;
-    }
-    Long size = contentStream[0].getSize();
-    postOrPut.setEntity(
-        new InputStreamEntity(contentStream[0].getStream(), size == null ? -1 : size) {
-          @Override
-          public Header getContentType() {
-            return new BasicHeader("Content-Type", contentStream[0].getContentType());
-          }
-
-          @Override
-          public boolean isRepeatable() {
-            return false;
-          }
-        });
-  }
-
-  private HttpEntityEnclosingRequestBase fillContentStream(
-      SolrRequest<?> request,
-      Collection<ContentStream> streams,
-      ModifiableSolrParams wparams,
-      boolean isMultipart,
-      List<NameValuePair> postOrPutParams,
-      String fullQueryUrl)
-      throws IOException {
-    HttpEntityEnclosingRequestBase postOrPut =
-        SolrRequest.METHOD.POST == request.getMethod()
-            ? new HttpPost(fullQueryUrl)
-            : new HttpPut(fullQueryUrl);
-
-    if (!isMultipart) {
-      postOrPut.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
-    }
-
-    List<FormBodyPart> parts = new ArrayList<>();
-    Iterator<String> iter = wparams.getParameterNamesIterator();
-    while (iter.hasNext()) {
-      String p = iter.next();
-      String[] vals = wparams.getParams(p);
-      if (vals != null) {
-        for (String v : vals) {
-          if (isMultipart) {
-            parts.add(new FormBodyPart(p, new StringBody(v, StandardCharsets.UTF_8)));
-          } else {
-            postOrPutParams.add(new BasicNameValuePair(p, v));
-          }
-        }
-      }
-    }
-
-    // TODO: remove deprecated - first simple attempt failed, see {@link MultipartEntityBuilder}
-    if (isMultipart && streams != null) {
-      for (ContentStream content : streams) {
-        String contentType = content.getContentType();
-        if (contentType == null) {
-          contentType = "multipart/form-data"; // default
-        }
-        String name = content.getName();
-        if (name == null) {
-          name = "";
-        }
-        parts.add(
-            new FormBodyPart(
-                name,
-                new InputStreamBody(
-                    content.getStream(), ContentType.parse(contentType), content.getName())));
-      }
-    }
-
-    if (parts.size() > 0) {
-      MultipartEntity entity = new MultipartEntity(HttpMultipartMode.STRICT);
-      for (FormBodyPart p : parts) {
-        entity.addPart(p);
-      }
-      postOrPut.setEntity(entity);
-    } else {
-      // not using multipart
-      postOrPut.setEntity(new UrlEncodedFormEntity(postOrPutParams, StandardCharsets.UTF_8));
-    }
-    return postOrPut;
-  }
-
-  // Utils.getObjectByPath(err, false,"metadata/error-class")
-  private static final List<String> errPath = Arrays.asList("metadata", "error-class");
-
-  @SuppressWarnings({"unchecked", "rawtypes"})
-  protected NamedList<Object> executeMethod(
-      HttpRequestBase method,
-      Principal userPrincipal,
-      final ResponseParser processor,
-      final boolean isV2Api)
-      throws SolrServerException {
-    method.addHeader("User-Agent", USER_AGENT);
-
-    RequestConfig.Builder requestConfigBuilder = HttpClientUtil.createDefaultRequestConfigBuilder();
-    requestConfigBuilder.setSocketTimeout(soTimeout);
-    requestConfigBuilder.setConnectTimeout(connectionTimeout);
-
-    if (followRedirects != null) {
-      requestConfigBuilder.setRedirectsEnabled(followRedirects);
-    }
-
-    method.setConfig(requestConfigBuilder.build());
-
-    HttpEntity entity = null;
-    boolean shouldClose = true;
-    try {
-      // Execute the method.
-      HttpClientContext httpClientRequestContext =
-          HttpClientUtil.createNewHttpClientRequestContext();
-      if (userPrincipal != null) {
-        // Normally the context contains a static userToken to enable reuse resources. However, if a
-        // personal Principal object exists, we use that instead, also as a means to transfer
-        // authentication information to Auth plugins that wish to intercept the request later
-        httpClientRequestContext.setUserToken(userPrincipal);
-      }
-      final HttpResponse response = httpClient.execute(method, httpClientRequestContext);
-
-      int httpStatus = response.getStatusLine().getStatusCode();
-
-      // Read the contents
-      entity = response.getEntity();
-      String mimeType = null;
-      Charset charset = null;
-      String charsetName = null;
-
-      ContentType contentType = ContentType.get(entity);
-      if (contentType != null) {
-        mimeType = contentType.getMimeType().trim().toLowerCase(Locale.ROOT);
-        charset = contentType.getCharset();
-
-        if (charset != null) {
-          charsetName = charset.name();
-        }
-      }
-
-      // handle some http level checks before trying to parse the response
-      switch (httpStatus) {
-        case HttpStatus.SC_OK:
-        case HttpStatus.SC_BAD_REQUEST:
-        case HttpStatus.SC_CONFLICT: // 409
-          break;
-        case HttpStatus.SC_MOVED_PERMANENTLY:
-        case HttpStatus.SC_MOVED_TEMPORARILY:
-          if (!followRedirects) {
-            throw new SolrServerException(
-                "Server at " + getBaseURL() + " sent back a redirect (" + httpStatus + ").");
-          }
-          break;
-        default:
-          if (processor == null || contentType == null) {
-            throw new RemoteSolrException(
-                baseUrl,
-                httpStatus,
-                "non ok status: "
-                    + httpStatus
-                    + ", message:"
-                    + response.getStatusLine().getReasonPhrase(),
-                null);
-          }
-      }
-
-      InputStream respBody =
-          (entity == null ? new ByteArrayInputStream(new byte[0]) : entity.getContent());
-      if (processor == null || processor instanceof InputStreamResponseParser) {
-        // no processor specified, return raw stream
-        final var rsp =
-            InputStreamResponseParser.createInputStreamNamedList(
-                response.getStatusLine().getStatusCode(), respBody);
-        rsp.add("closeableResponse", response);
-        // Only case where stream should not be closed
-        shouldClose = false;
-        return rsp;
-      }
-
-      final Collection<String> processorSupportedContentTypes = processor.getContentTypes();
-      if (!processorSupportedContentTypes.isEmpty()) {
-        final Collection<String> processorMimeTypes =
-            processorSupportedContentTypes.stream()
-                .map(ct -> ContentType.parse(ct).getMimeType().trim().toLowerCase(Locale.ROOT))
-                .collect(Collectors.toSet());
-        if (!processorMimeTypes.contains(mimeType)) {
-          if (isUnmatchedErrorCode(mimeType, httpStatus)) {
-            throw new RemoteSolrException(
-                baseUrl,
-                httpStatus,
-                "non ok status: "
-                    + httpStatus
-                    + ", message:"
-                    + response.getStatusLine().getReasonPhrase(),
-                null);
-          }
-
-          // unexpected mime type
-          final String combinedMimeTypes = String.join(", ", processorMimeTypes);
-          String prefix =
-              "Expected mime type in [" + combinedMimeTypes + "] but got " + mimeType + ". ";
-          Charset exceptionCharset = charset != null ? charset : FALLBACK_CHARSET;
-          try {
-            ByteArrayOutputStream body = new ByteArrayOutputStream();
-            respBody.transferTo(body);
-            throw new RemoteSolrException(
-                baseUrl, httpStatus, prefix + body.toString(exceptionCharset), null);
-          } catch (IOException e) {
-            throw new RemoteSolrException(
-                baseUrl,
-                httpStatus,
-                "Could not parse response with encoding " + exceptionCharset,
-                e);
-          }
-        }
-      }
-
-      NamedList<Object> rsp = null;
-      try {
-        rsp = processor.processResponse(respBody, charsetName);
-      } catch (Exception e) {
-        throw new RemoteSolrException(baseUrl, httpStatus, e.getMessage(), e);
-      }
-      Object error = rsp == null ? null : rsp.get("error");
-      if (error != null && isV2Api) {
-        throw new RemoteSolrException(baseUrl, httpStatus, error, true);
-      }
-      if (httpStatus != 200 && !isV2Api) {
-        if (error == null) {
-          StringBuilder msg = new StringBuilder();
-          msg.append(response.getStatusLine().getReasonPhrase())
-              .append("\n\n")
-              .append("request: ")
-              .append(method.getURI());
-          String reason = URLDecoder.decode(msg.toString(), FALLBACK_CHARSET);
-          throw new RemoteSolrException(baseUrl, httpStatus, reason, null);
-        } else {
-          throw new RemoteSolrException(baseUrl, httpStatus, error);
-        }
-      }
-      return rsp;
-    } catch (ConnectException e) {
-      throw new SolrServerException("Server refused connection at: " + getBaseURL(), e);
-    } catch (SocketTimeoutException e) {
-      throw new SolrServerException(
-          "Timeout occurred while waiting response from server at: " + getBaseURL(), e);
-    } catch (IOException e) {
-      throw new SolrServerException(
-          "IOException occurred when talking to server at: " + getBaseURL(), e);
-    } finally {
-      if (shouldClose) {
-        HttpClientUtil.consumeFully(entity);
-      }
-    }
-  }
-
-  // When raising an error using HTTP sendError, mime types can be mismatched. This is specifically
-  // true when SolrServlet uses the sendError mechanism since the expected MIME type of
-  // response is not HTML but HTTP sendError generates an HTML output, which can lead to mismatch
-  private boolean isUnmatchedErrorCode(String mimeType, int httpStatus) {
-    if (mimeType == null) {
-      return false;
-    }
-
-    if (mimeType.equalsIgnoreCase("text/html")
-        && UNMATCHED_ACCEPTED_ERROR_CODES.contains(httpStatus)) {
-      return true;
-    }
-
-    return false;
-  }
-
-  private Header[] buildRequestSpecificHeaders(final SolrRequest<?> request) {
-    Header[] contextHeaders = new Header[2];
-
-    // TODO: validate request context here: https://issues.apache.org/jira/browse/SOLR-14720
-    contextHeaders[0] =
-        new BasicHeader(CommonParams.SOLR_REQUEST_CONTEXT_PARAM, getContext().toString());
-
-    contextHeaders[1] =
-        new BasicHeader(CommonParams.SOLR_REQUEST_TYPE_PARAM, request.getRequestType().toString());
-
-    return contextHeaders;
-  }
-
-  private void populateHeaders(HttpMessage message, Header[] contextHeaders) {
-    message.addHeader(contextHeaders[0]);
-    message.addHeader(contextHeaders[1]);
-  }
-
-  // -------------------------------------------------------------------
-  // -------------------------------------------------------------------
-
-  /**
-   * Retrieve the default list of parameters are added to every request regardless.
-   *
-   * @see #invariantParams
-   */
-  public ModifiableSolrParams getInvariantParams() {
-    return invariantParams;
-  }
-
-  /** Typically looks like {@code http://localhost:8983/solr} (no core or collection) */
-  public String getBaseURL() {
-    return baseUrl;
-  }
-
-  public ResponseParser getParser() {
-    return parser;
-  }
-
-  /** Return the HttpClient this instance uses. */
-  public HttpClient getHttpClient() {
-    return httpClient;
-  }
-
-  /** Close the {@link HttpClientConnectionManager} from the internal client. */
-  @Override
-  public void close() throws IOException {
-    if (httpClient != null && internalClient) {
-      HttpClientUtil.close(httpClient);
-    }
-  }
-
-  public boolean isUseMultiPartPost() {
-    return useMultiPartPost;
-  }
-
-  /**
-   * Constructs {@link HttpSolrClient} instances from provided configuration.
-   *
-   * @deprecated Please use {@link HttpJettySolrClient}
-   */
-  @Deprecated(since = "9.0")
-  public static class Builder extends SolrClientBuilder<Builder> {
-    protected String baseSolrUrl;
-    protected boolean compression;
-    protected ModifiableSolrParams invariantParams = new ModifiableSolrParams();
-
-    public Builder() {
-      this.responseParser = new JavaBinResponseParser();
-    }
-
-    /**
-     * Specify the base-url for the created client to use when sending requests to Solr.
-     *
-     * <p>The provided URL must point to the root Solr path ("/solr"), for example:
-     *
-     * <pre>
-     *   SolrClient client = new HttpSolrClient.Builder()
-     *       .withBaseSolrUrl("http://my-solr-server:8983/solr")
-     *       .withDefaultCollection("core1")
-     *       .build();
-     *   QueryResponse resp = client.query(new SolrQuery("*:*"));
-     * </pre>
-     */
-    public Builder withBaseSolrUrl(String baseSolrUrl) {
-      this.baseSolrUrl = baseSolrUrl;
-      return this;
-    }
-
-    /**
-     * Initialize a Builder object, based on the provided Solr URL.
-     *
-     * <p>The provided URL must point to the root Solr path ("/solr"), for example:
-     *
-     * <pre>
-     *   SolrClient client = new HttpSolrClient.Builder("http://my-solr-server:8983/solr")
-     *       .withDefaultCollection("core1")
-     *       .build();
-     *   QueryResponse resp = client.query(new SolrQuery("*:*"));
-     * </pre>
-     *
-     * <p>By default, compression is not enabled on created HttpSolrClient objects. By default,
-     * redirects are not followed in created HttpSolrClient objects. By default, {@link
-     * JavaBinRequestWriter} is used for composing requests. By default, {@link
-     * JavaBinResponseParser} is used for parsing responses.
-     *
-     * @param baseSolrUrl a URL to the root Solr path (i.e. "/solr") that will be targeted by any
-     *     created clients.
-     */
-    public Builder(String baseSolrUrl) {
-      this.baseSolrUrl = baseSolrUrl;
-      this.responseParser = new JavaBinResponseParser();
-    }
-
-    /** Chooses whether created clients use compression by default. */
-    public Builder allowCompression(boolean compression) {
-      this.compression = compression;
-      return this;
-    }
-
-    /**
-     * Adds to the set of params that the created client will add on all requests
-     *
-     * @param params a set of parameters to add to the invariant-params list. These params must be
-     *     unique and may not duplicate a param already in the invariant list.
-     */
-    public Builder withInvariantParams(ModifiableSolrParams params) {
-      Objects.requireNonNull(params, "params must be non null!");
-
-      for (String name : params.getParameterNames()) {
-        if (this.invariantParams.get(name) != null) {
-          throw new IllegalStateException("parameter " + name + " is redefined.");
-        }
-      }
-
-      this.invariantParams.add(params);
-      return this;
-    }
-
-    /** Create a {@link HttpSolrClient} based on provided configuration. */
-    public HttpSolrClient build() {
-      if (baseSolrUrl == null) {
-        throw new IllegalArgumentException(
-            "Cannot create HttpSolrClient without a valid baseSolrUrl!");
-      }
-
-      return new HttpSolrClient(this);
-    }
-
-    @Override
-    public Builder getThis() {
-      return this;
-    }
-  }
-}
diff --git a/solr/test-framework/src/java/org/apache/solr/client/solrj/apache/LBHttpSolrClient.java b/solr/test-framework/src/java/org/apache/solr/client/solrj/apache/LBHttpSolrClient.java
deleted file mode 100644
index 7a9670722e3..00000000000
--- a/solr/test-framework/src/java/org/apache/solr/client/solrj/apache/LBHttpSolrClient.java
+++ /dev/null
@@ -1,341 +0,0 @@
-/*
- * 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.apache;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.TimeUnit;
-import org.apache.http.client.HttpClient;
-import org.apache.solr.client.solrj.SolrClient;
-import org.apache.solr.client.solrj.impl.CloudSolrClient;
-import org.apache.solr.client.solrj.impl.LBSolrClient;
-import org.apache.solr.client.solrj.response.JavaBinResponseParser;
-import org.apache.solr.common.params.ModifiableSolrParams;
-
-/**
- * LBHttpSolrClient or "LoadBalanced HttpSolrClient" is a load balancing wrapper around {@link
- * HttpSolrClient}. This is useful when you have multiple Solr servers (also called endpoints) and
- * requests need to be Load Balanced among them.
- *
- * <p>Do <b>NOT</b> use this class for indexing in leader/follower scenarios since documents must be
- * sent to the correct leader; no inter-node routing is done.
- *
- * <p>In SolrCloud (leader/replica) scenarios, it is usually better to use {@link CloudSolrClient},
- * but this class may be used for updates because the server will forward them to the appropriate
- * leader.
- *
- * <p>It offers automatic failover when a server goes down, and it detects when the server comes
- * back up.
- *
- * <p>Load balancing is done using a simple round-robin on the list of endpoints. Endpoint URLs are
- * expected to point to the Solr "root" path (i.e. "/solr").
- *
- * <blockquote>
- *
- * <pre>
- * SolrClient lbHttpSolrClient = new LBHttpSolrClient.Builder()
- *     .withBaseEndpoints("http://host1:8080/solr", "http://host2:8080/solr", "http://host3:8080/solr")
- *     .build();
- * </pre>
- *
- * </blockquote>
- *
- * Users who wish to balance traffic across a specific set of replicas or cores may specify each
- * endpoint as a root-URL and core-name pair. For example:
- *
- * <blockquote>
- *
- * <pre>
- * SolrClient lbHttpSolrClient = new LBHttpSolrClient.Builder()
- *     .withCollectionEndpoint("http://host1:8080/solr", "coreA")
- *     .withCollectionEndpoint("http://host2:8080/solr", "coreB")
- *     .withCollectionEndpoint("http://host3:8080/solr", "coreC")
- *     .build();
- * // Or, if you wish to provide all endpoints together:
- * lbHttpSolrClient = new LBHttpSolrClient.Builder()
- *     .withCollectionEndpoints(
- *         new LBSolrClient.Endpoint("http://host1:8080/solr", "coreA"),
- *         new LBSolrClient.Endpoint("http://host2:8080/solr", "coreB"),
- *         new LBSolrClient.Endpoint("http://host3:8080/solr", "coreC"))
- *     .build();
- * </pre>
- *
- * </blockquote>
- *
- * <p>If a request to an endpoint fails by an IOException due to a connection timeout or read
- * timeout then the host is taken off the list of live endpoints and moved to a 'dead endpoint list'
- * and the request is resent to the next live endpoint. This process is continued till it tries all
- * the live endpoints. If at least one endpoint is alive, the request succeeds, and if not it fails.
- *
- * <p>Dead endpoints are periodically healthchecked on a fixed interval controlled by {@link
- * LBHttpSolrClient.Builder#setAliveCheckInterval(int)}. The default is set to one minute.
- *
- * <p><b>When to use this?</b><br>
- * This can be used as a software load balancer when you do not wish to set up an external load
- * balancer. Alternatives to this code are to use a dedicated hardware load balancer or using Apache
- * httpd with mod_proxy_balancer as a load balancer. See <a
- * href="http://en.wikipedia.org/wiki/Load_balancing_(computing)">Load balancing on Wikipedia</a>
- *
- * @since solr 1.4
- * @deprecated Not supporting Apache HttpClient based implementations anymore
- */
-@Deprecated(since = "9.0")
-public class LBHttpSolrClient extends LBSolrClient {
-
-  private final HttpClient httpClient;
-  private final boolean clientIsInternal;
-  private final ConcurrentHashMap<String, HttpSolrClient> urlToClient = new ConcurrentHashMap<>();
-  private final HttpSolrClient.Builder httpSolrClientBuilder;
-  private volatile Set<String> urlParamNames = new HashSet<>();
-
-  final int connectionTimeoutMillis;
-  final int soTimeoutMillis;
-
-  /** The provided httpClient should use a multi-threaded connection manager */
-  protected LBHttpSolrClient(Builder builder) {
-    super(builder.solrEndpoints);
-    this.clientIsInternal = builder.httpClient == null;
-    this.httpSolrClientBuilder = builder.httpSolrClientBuilder;
-    this.httpClient =
-        builder.httpClient == null ? constructClient(builder.solrEndpoints) : builder.httpClient;
-    this.defaultCollection = builder.defaultCollection;
-    if (httpSolrClientBuilder != null && this.defaultCollection != null) {
-      httpSolrClientBuilder.defaultCollection = this.defaultCollection;
-    }
-    this.connectionTimeoutMillis = builder.connectionTimeoutMillis;
-    this.soTimeoutMillis = builder.socketTimeoutMillis;
-    this.parser = builder.responseParser;
-    this.aliveCheckIntervalMillis = builder.aliveCheckInterval;
-    for (Endpoint endpoint : builder.solrEndpoints) {
-      urlToClient.put(endpoint.toString(), makeSolrClient(endpoint));
-    }
-  }
-
-  private HttpClient constructClient(List<Endpoint> solrEndpoints) {
-    ModifiableSolrParams params = new ModifiableSolrParams();
-    if (solrEndpoints != null && solrEndpoints.size() > 1) {
-      // we prefer retrying another server
-      params.set(HttpClientUtil.PROP_USE_RETRY, false);
-    } else {
-      params.set(HttpClientUtil.PROP_USE_RETRY, true);
-    }
-    return HttpClientUtil.createClient(params);
-  }
-
-  protected HttpSolrClient makeSolrClient(Endpoint server) {
-    HttpSolrClient client;
-    if (httpSolrClientBuilder != null) {
-      synchronized (this) {
-        httpSolrClientBuilder
-            .withBaseSolrUrl(server.getBaseUrl())
-            .withDefaultCollection(server.getCore())
-            .withHttpClient(httpClient)
-            .withConnectionTimeout(connectionTimeoutMillis, TimeUnit.MILLISECONDS)
-            .withSocketTimeout(soTimeoutMillis, TimeUnit.MILLISECONDS);
-        if (defaultCollection != null) {
-          httpSolrClientBuilder.withDefaultCollection(defaultCollection);
-        }
-        if (requestWriter != null) {
-          httpSolrClientBuilder.withRequestWriter(requestWriter);
-        }
-        if (urlParamNames != null) {
-          httpSolrClientBuilder.withTheseParamNamesInTheUrl(urlParamNames);
-        }
-        client = httpSolrClientBuilder.build();
-      }
-    } else {
-      final var clientBuilder =
-          new HttpSolrClient.Builder(server.getBaseUrl())
-              .withDefaultCollection(server.getCore())
-              .withHttpClient(httpClient)
-              .withResponseParser(parser)
-              .withConnectionTimeout(connectionTimeoutMillis, TimeUnit.MILLISECONDS)
-              .withSocketTimeout(soTimeoutMillis, TimeUnit.MILLISECONDS);
-      if (defaultCollection != null) {
-        clientBuilder.withDefaultCollection(defaultCollection);
-      }
-      if (requestWriter != null) {
-        clientBuilder.withRequestWriter(requestWriter);
-      }
-      if (urlParamNames != null) {
-        clientBuilder.withTheseParamNamesInTheUrl(urlParamNames);
-      }
-      client = clientBuilder.build();
-    }
-
-    return client;
-  }
-
-  @Override
-  protected SolrClient getClient(Endpoint endpoint) {
-    SolrClient client = urlToClient.get(endpoint.toString());
-    if (client == null) {
-      return makeSolrClient(endpoint);
-    } else {
-      return client;
-    }
-  }
-
-  @Override
-  public synchronized String removeSolrServer(Endpoint server) {
-    urlToClient.remove(server.toString());
-    return super.removeSolrServer(server);
-  }
-
-  @Override
-  public void close() {
-    super.close();
-    if (clientIsInternal) {
-      HttpClientUtil.close(httpClient);
-    }
-  }
-
-  /** Return the HttpClient this instance uses. */
-  public HttpClient getHttpClient() {
-    return httpClient;
-  }
-
-  /** Constructs {@link LBHttpSolrClient} instances from provided configuration. */
-  public static class Builder extends SolrClientBuilder<Builder> {
-
-    public static final int CHECK_INTERVAL = 60 * 1000; // 1 minute between checks
-    protected final List<Endpoint> solrEndpoints;
-    protected HttpSolrClient.Builder httpSolrClientBuilder;
-    private int aliveCheckInterval = CHECK_INTERVAL;
-
-    public Builder() {
-      this.solrEndpoints = new ArrayList<>();
-      this.responseParser = new JavaBinResponseParser();
-    }
-
-    public HttpSolrClient.Builder getHttpSolrClientBuilder() {
-      return httpSolrClientBuilder;
-    }
-
-    /**
-     * Provide a "base" Solr URL to be used when configuring {@link LBHttpSolrClient} instances.
-     *
-     * <p>Method may be called multiple times. All provided values will be used. However, all
-     * endpoints must be of the same type: providing a mix of"base" endpoints via this method and
-     * core/collection endpoints via {@link #withCollectionEndpoint(String, String)} is prohibited.
-     *
-     * <p>Users who use this method to provide base Solr URLs may specify a "default collection" for
-     * their requests using {@link #withDefaultCollection(String)} if they wish to avoid needing to
-     * specify a collection or core on relevant requests.
-     *
-     * @param baseSolrUrl the base URL for a Solr node, in the form "http[s]://hostname:port/solr"
-     */
-    public Builder withBaseEndpoint(String baseSolrUrl) {
-      solrEndpoints.add(new Endpoint(baseSolrUrl));
-      return this;
-    }
-
-    /**
-     * Provide multiple "base" Solr URLs to be used when configuring {@link LBHttpSolrClient}
-     * instances.
-     *
-     * <p>Method may be called multiple times. All provided values will be used. However, all
-     * endpoints must be of the same type: providing a mix of"base" endpoints via this method and
-     * core/collection endpoints via {@link #withCollectionEndpoint(String, String)} is prohibited.
-     *
-     * <p>Users who use this method to provide base Solr URLs may specify a "default collection" for
-     * their requests using {@link #withDefaultCollection(String)} if they wish to avoid needing to
-     * specify a collection or core on relevant requests.
-     *
-     * @param baseSolrUrls Solr base URLs, in the form "http[s]://hostname:port/solr"
-     */
-    public Builder withBaseEndpoints(String... baseSolrUrls) {
-      for (String baseSolrUrl : baseSolrUrls) {
-        solrEndpoints.add(new Endpoint(baseSolrUrl));
-      }
-      return this;
-    }
-
-    /**
-     * Provide a core/collection Solr endpoint to be used when configuring {@link LBHttpSolrClient}
-     * instances.
-     *
-     * <p>Method may be called multiple times. All provided values will be used. However, all
-     * endpoints must be of the same type: providing a mix of "core" endpoints via this method and
-     * base endpoints via {@link #withBaseEndpoint(String)} is prohibited.
-     *
-     * @param baseSolrUrl the base URL for a Solr node, in the form "http[s]://hostname:port/solr"
-     * @param core the Solr core or collection to target
-     */
-    public Builder withCollectionEndpoint(String baseSolrUrl, String core) {
-      solrEndpoints.add(new Endpoint(baseSolrUrl, core));
-      return this;
-    }
-
-    /**
-     * Provide multiple core/collection endpoints to be used when configuring {@link
-     * LBHttpSolrClient} instances.
-     *
-     * <p>Method may be called multiple times. All provided values will be used. However, all
-     * endpoints must be of the same type: providing a mix of "core" endpoints via this method and
-     * base endpoints via {@link #withBaseEndpoint(String)} is prohibited.
-     *
-     * @param endpoints endpoint instances pointing to distinct cores/collections
-     */
-    public Builder withCollectionEndpoints(Endpoint... endpoints) {
-      if (endpoints != null) {
-        for (Endpoint e : endpoints) {
-          solrEndpoints.add(e);
-        }
-      }
-
-      return this;
-    }
-
-    /**
-     * LBHttpSolrServer keeps pinging the dead servers at fixed interval to find if it is alive. Use
-     * this to set that interval
-     *
-     * @param aliveCheckInterval time in milliseconds
-     */
-    public Builder setAliveCheckInterval(int aliveCheckInterval) {
-      if (aliveCheckInterval <= 0) {
-        throw new IllegalArgumentException(
-            "Alive check interval must be " + "positive, specified value = " + aliveCheckInterval);
-      }
-      this.aliveCheckInterval = aliveCheckInterval;
-      return this;
-    }
-
-    /**
-     * Provides a {@link HttpSolrClient.Builder} to be used for building the internally used
-     * clients.
-     */
-    public Builder withHttpSolrClientBuilder(HttpSolrClient.Builder builder) {
-      this.httpSolrClientBuilder = builder;
-      return this;
-    }
-
-    /** Create a {@link LBHttpSolrClient} based on provided configuration. */
-    public LBHttpSolrClient build() {
-      return new LBHttpSolrClient(this);
-    }
-
-    @Override
-    public Builder getThis() {
-      return this;
-    }
-  }
-}
diff --git a/solr/test-framework/src/java/org/apache/solr/client/solrj/apache/PreemptiveAuth.java b/solr/test-framework/src/java/org/apache/solr/client/solrj/apache/PreemptiveAuth.java
deleted file mode 100644
index 26d02e20b07..00000000000
--- a/solr/test-framework/src/java/org/apache/solr/client/solrj/apache/PreemptiveAuth.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * 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.apache;
-
-import java.io.IOException;
-import org.apache.http.HttpException;
-import org.apache.http.HttpRequest;
-import org.apache.http.HttpRequestInterceptor;
-import org.apache.http.auth.AuthScheme;
-import org.apache.http.auth.AuthScope;
-import org.apache.http.auth.AuthState;
-import org.apache.http.auth.Credentials;
-import org.apache.http.client.CredentialsProvider;
-import org.apache.http.client.protocol.ClientContext;
-import org.apache.http.protocol.HttpContext;
-
-/**
- * This HTTP request interceptor adds HTTP authentication credentials to every outgoing request.
- * This implementation is required since Solr client is not capable of performing non preemptive
- * authentication. By adding the Http authentication credentials to every request, this interceptor
- * enables "preemptive" authentication.
- *
- * @deprecated Please look into using Solr's new Http2 clients
- */
-@Deprecated(since = "9.0")
-public class PreemptiveAuth implements HttpRequestInterceptor {
-  private AuthScheme authScheme = null;
-
-  public PreemptiveAuth(AuthScheme authScheme) {
-    this.authScheme = authScheme;
-  }
-
-  @Override
-  public void process(final HttpRequest request, final HttpContext context)
-      throws HttpException, IOException {
-
-    AuthState authState = (AuthState) context.getAttribute(ClientContext.TARGET_AUTH_STATE);
-    // If no auth scheme available yet, try to initialize it preemptively
-    if (authState.getAuthScheme() == null) {
-      CredentialsProvider credsProvider =
-          (CredentialsProvider) context.getAttribute(ClientContext.CREDS_PROVIDER);
-      Credentials creds = credsProvider.getCredentials(AuthScope.ANY);
-      authState.update(authScheme, creds);
-    }
-  }
-}
diff --git a/solr/test-framework/src/java/org/apache/solr/client/solrj/apache/SolrClientBuilder.java b/solr/test-framework/src/java/org/apache/solr/client/solrj/apache/SolrClientBuilder.java
deleted file mode 100644
index c12bafd88e3..00000000000
--- a/solr/test-framework/src/java/org/apache/solr/client/solrj/apache/SolrClientBuilder.java
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * 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.apache;
-
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-import org.apache.http.client.HttpClient;
-import org.apache.http.client.config.RequestConfig;
-import org.apache.http.client.methods.Configurable;
-import org.apache.solr.client.solrj.apache.HttpSolrClient.Builder;
-import org.apache.solr.client.solrj.request.RequestWriter;
-import org.apache.solr.client.solrj.response.ResponseParser;
-
-/**
- * @deprecated Please look into using Solr's new Http2 clients
- */
-@Deprecated(since = "9.0")
-public abstract class SolrClientBuilder<B extends SolrClientBuilder<B>> {
-
-  protected int timeToLiveSeconds = 60;
-  protected HttpClient httpClient;
-  protected ResponseParser responseParser;
-  protected RequestWriter requestWriter;
-  protected boolean useMultiPartPost;
-  protected int connectionTimeoutMillis = 15000; // 15 seconds
-  private boolean connectionTimeoutMillisUpdate = false;
-  protected int socketTimeoutMillis = 120000; // 120 seconds
-  private boolean socketTimeoutMillisUpdate = false;
-  protected boolean followRedirects = false;
-  protected String defaultCollection;
-  protected Set<String> urlParamNames;
-
-  /** The solution for the unchecked cast warning. */
-  public abstract B getThis();
-
-  /** Provides a {@link HttpClient} for the builder to use when creating clients. */
-  public B withHttpClient(HttpClient httpClient) {
-    this.httpClient = httpClient;
-
-    if (this.httpClient instanceof Configurable) {
-      RequestConfig conf = ((Configurable) httpClient).getConfig();
-      // only update values that were not already manually changed
-      if (!connectionTimeoutMillisUpdate && conf.getConnectTimeout() > 0) {
-        this.connectionTimeoutMillis = conf.getConnectTimeout();
-      }
-      if (!socketTimeoutMillisUpdate && conf.getSocketTimeout() > 0) {
-        this.socketTimeoutMillis = conf.getSocketTimeout();
-      }
-    }
-    return getThis();
-  }
-
-  /** Provides a {@link ResponseParser} for created clients to use when handling requests. */
-  public B withResponseParser(ResponseParser responseParser) {
-    this.responseParser = responseParser;
-    return getThis();
-  }
-
-  /** Provides a {@link RequestWriter} for created clients to use when handing requests. */
-  public B withRequestWriter(RequestWriter requestWriter) {
-    this.requestWriter = requestWriter;
-    return getThis();
-  }
-
-  /** Enables or disables splitting POST requests into pieces. */
-  public B allowMultiPartPost(Boolean useMultiPartPost) {
-    this.useMultiPartPost = useMultiPartPost;
-    return getThis();
-  }
-
-  /**
-   * Provides a set of keys which the created client will send as a part of the query string.
-   *
-   * @param queryParams set of param keys to only send via the query string Note that the param will
-   *     be sent as a query string if the key is part of this Set or the SolrRequest's query params.
-   */
-  public B withTheseParamNamesInTheUrl(Set<String> queryParams) {
-    this.urlParamNames = queryParams;
-    return getThis();
-  }
-
-  public B withFollowRedirects(boolean followRedirects) {
-    this.followRedirects = followRedirects;
-    return getThis();
-  }
-
-  /** Sets a default for core or collection based requests. */
-  public B withDefaultCollection(String defaultCoreOrCollection) {
-    this.defaultCollection = defaultCoreOrCollection;
-    return getThis();
-  }
-
-  /**
-   * Tells {@link Builder} that created clients should obey the following timeout when connecting to
-   * Solr servers.
-   *
-   * <p>For valid values see {@link org.apache.http.client.config.RequestConfig#getConnectTimeout()}
-   *
-   * @deprecated Please use {@link #withConnectionTimeout(int, TimeUnit)}
-   */
-  @Deprecated(since = "9.2")
-  public B withConnectionTimeout(int connectionTimeoutMillis) {
-    withConnectionTimeout(connectionTimeoutMillis, TimeUnit.MILLISECONDS);
-    return getThis();
-  }
-
-  /**
-   * Tells {@link Builder} that created clients should obey the following timeout when connecting to
-   * Solr servers.
-   *
-   * <p>For valid values see {@link org.apache.http.client.config.RequestConfig#getConnectTimeout()}
-   */
-  public B withConnectionTimeout(int connectionTimeout, TimeUnit unit) {
-    if (connectionTimeout < 0) {
-      throw new IllegalArgumentException("connectionTimeout must be a non-negative integer.");
-    }
-    this.connectionTimeoutMillis =
-        Math.toIntExact(TimeUnit.MILLISECONDS.convert(connectionTimeout, unit));
-    connectionTimeoutMillisUpdate = true;
-    return getThis();
-  }
-
-  public int getConnectionTimeoutMillis() {
-    return this.connectionTimeoutMillis;
-  }
-
-  /**
-   * Tells {@link Builder} that created clients should set the following read timeout on all
-   * sockets.
-   *
-   * <p>For valid values see {@link org.apache.http.client.config.RequestConfig#getSocketTimeout()}
-   *
-   * <p>* @deprecated Please use {@link #withSocketTimeout(int, TimeUnit)}
-   */
-  @Deprecated(since = "9.2")
-  public B withSocketTimeout(int socketTimeoutMillis) {
-    withSocketTimeout(socketTimeoutMillis, TimeUnit.MILLISECONDS);
-    return getThis();
-  }
-
-  /**
-   * Tells {@link Builder} that created clients should set the following read timeout on all
-   * sockets.
-   *
-   * <p>For valid values see {@link org.apache.http.client.config.RequestConfig#getSocketTimeout()}
-   */
-  public B withSocketTimeout(int socketTimeout, TimeUnit unit) {
-    if (socketTimeout < 0) {
-      throw new IllegalArgumentException("socketTimeout must be a non-negative integer.");
-    }
-    this.socketTimeoutMillis = Math.toIntExact(TimeUnit.MILLISECONDS.convert(socketTimeout, unit));
-    socketTimeoutMillisUpdate = true;
-    return getThis();
-  }
-
-  public int getSocketTimeoutMillis() {
-    return this.socketTimeoutMillis;
-  }
-}
diff --git a/solr/test-framework/src/java/org/apache/solr/client/solrj/apache/SolrHttpClientBuilder.java b/solr/test-framework/src/java/org/apache/solr/client/solrj/apache/SolrHttpClientBuilder.java
deleted file mode 100644
index 6139c8d19b8..00000000000
--- a/solr/test-framework/src/java/org/apache/solr/client/solrj/apache/SolrHttpClientBuilder.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * 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.apache;
-
-import org.apache.http.auth.AuthSchemeProvider;
-import org.apache.http.client.CredentialsProvider;
-import org.apache.http.config.Lookup;
-import org.apache.http.cookie.CookieSpecProvider;
-
-/**
- * Builder class for configuring internal HttpClients. This relies on the internal HttpClient
- * implementation and is subject to change.
- *
- * @lucene.experimental
- * @deprecated Please look into using Solr's new Http2 clients
- */
-@Deprecated(since = "9.0")
-public class SolrHttpClientBuilder {
-  public static SolrHttpClientBuilder create() {
-    return new SolrHttpClientBuilder();
-  }
-
-  public interface CredentialsProviderProvider {
-    CredentialsProvider getCredentialsProvider();
-  }
-
-  public interface AuthSchemeRegistryProvider {
-    Lookup<AuthSchemeProvider> getAuthSchemeRegistry();
-  }
-
-  public interface CookieSpecRegistryProvider {
-    Lookup<CookieSpecProvider> getCookieSpecRegistry();
-  }
-
-  private CookieSpecRegistryProvider cookieSpecRegistryProvider;
-  private AuthSchemeRegistryProvider authSchemeRegistryProvider;
-  private CredentialsProviderProvider credentialsProviderProvider;
-
-  protected SolrHttpClientBuilder() {
-    super();
-  }
-
-  public final SolrHttpClientBuilder setCookieSpecRegistryProvider(
-      final CookieSpecRegistryProvider cookieSpecRegistryProvider) {
-    this.cookieSpecRegistryProvider = cookieSpecRegistryProvider;
-    return this;
-  }
-
-  public final SolrHttpClientBuilder setDefaultCredentialsProvider(
-      final CredentialsProviderProvider credentialsProviderProvider) {
-    this.credentialsProviderProvider = credentialsProviderProvider;
-    return this;
-  }
-
-  public final SolrHttpClientBuilder setAuthSchemeRegistryProvider(
-      final AuthSchemeRegistryProvider authSchemeRegistryProvider) {
-    this.authSchemeRegistryProvider = authSchemeRegistryProvider;
-    return this;
-  }
-
-  public AuthSchemeRegistryProvider getAuthSchemeRegistryProvider() {
-    return authSchemeRegistryProvider;
-  }
-
-  public CookieSpecRegistryProvider getCookieSpecRegistryProvider() {
-    return cookieSpecRegistryProvider;
-  }
-
-  public CredentialsProviderProvider getCredentialsProviderProvider() {
-    return credentialsProviderProvider;
-  }
-}
diff --git a/solr/test-framework/src/java/org/apache/solr/client/solrj/apache/SolrHttpClientContextBuilder.java b/solr/test-framework/src/java/org/apache/solr/client/solrj/apache/SolrHttpClientContextBuilder.java
deleted file mode 100644
index 5f50d309cc3..00000000000
--- a/solr/test-framework/src/java/org/apache/solr/client/solrj/apache/SolrHttpClientContextBuilder.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * 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.apache;
-
-import org.apache.http.auth.AuthSchemeProvider;
-import org.apache.http.client.CredentialsProvider;
-import org.apache.http.client.protocol.HttpClientContext;
-import org.apache.http.config.Lookup;
-import org.apache.http.cookie.CookieSpecProvider;
-
-/**
- * @deprecated Please look into using Solr's new Http2 clients
- */
-@Deprecated(since = "9.0")
-public class SolrHttpClientContextBuilder {
-  public static SolrHttpClientContextBuilder create() {
-    return new SolrHttpClientContextBuilder();
-  }
-
-  public abstract static class CredentialsProviderProvider {
-    public abstract CredentialsProvider getCredentialsProvider();
-  }
-
-  public abstract static class AuthSchemeRegistryProvider {
-    public abstract Lookup<AuthSchemeProvider> getAuthSchemeRegistry();
-  }
-
-  public abstract static class CookieSpecRegistryProvider {
-    public abstract Lookup<CookieSpecProvider> getCookieSpecRegistry();
-  }
-
-  private CookieSpecRegistryProvider cookieSpecRegistryProvider;
-  private AuthSchemeRegistryProvider authSchemeRegistryProvider;
-  private CredentialsProviderProvider credentialsProviderProvider;
-
-  public SolrHttpClientContextBuilder() {
-    super();
-  }
-
-  public final SolrHttpClientContextBuilder setCookieSpecRegistryProvider(
-      final CookieSpecRegistryProvider cookieSpecRegistryProvider) {
-    this.cookieSpecRegistryProvider = cookieSpecRegistryProvider;
-    return this;
-  }
-
-  public final SolrHttpClientContextBuilder setDefaultCredentialsProvider(
-      final CredentialsProviderProvider credentialsProviderProvider) {
-    this.credentialsProviderProvider = credentialsProviderProvider;
-    return this;
-  }
-
-  public final SolrHttpClientContextBuilder setAuthSchemeRegistryProvider(
-      final AuthSchemeRegistryProvider authSchemeRegistryProvider) {
-    this.authSchemeRegistryProvider = authSchemeRegistryProvider;
-    return this;
-  }
-
-  public AuthSchemeRegistryProvider getAuthSchemeRegistryProvider() {
-    return authSchemeRegistryProvider;
-  }
-
-  public CookieSpecRegistryProvider getCookieSpecRegistryProvider() {
-    return cookieSpecRegistryProvider;
-  }
-
-  public CredentialsProviderProvider getCredentialsProviderProvider() {
-    return credentialsProviderProvider;
-  }
-
-  public HttpClientContext createContext(Object userToken) {
-    HttpClientContext context = new HttpClientContext();
-    if (getCredentialsProviderProvider() != null) {
-      context.setCredentialsProvider(getCredentialsProviderProvider().getCredentialsProvider());
-    }
-    if (getAuthSchemeRegistryProvider() != null) {
-      context.setAuthSchemeRegistry(getAuthSchemeRegistryProvider().getAuthSchemeRegistry());
-    }
-
-    if (getCookieSpecRegistryProvider() != null) {
-      context.setCookieSpecRegistry(getCookieSpecRegistryProvider().getCookieSpecRegistry());
-    }
-
-    context.setUserToken(userToken);
-
-    return context;
-  }
-}
diff --git a/solr/test-framework/src/java/org/apache/solr/client/solrj/apache/SolrHttpRequestRetryHandler.java b/solr/test-framework/src/java/org/apache/solr/client/solrj/apache/SolrHttpRequestRetryHandler.java
deleted file mode 100644
index b3bb70a014c..00000000000
--- a/solr/test-framework/src/java/org/apache/solr/client/solrj/apache/SolrHttpRequestRetryHandler.java
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- * 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.apache;
-
-import java.io.IOException;
-import java.io.InterruptedIOException;
-import java.lang.invoke.MethodHandles;
-import java.net.ConnectException;
-import java.net.SocketException;
-import java.net.UnknownHostException;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Set;
-import javax.net.ssl.SSLException;
-import org.apache.http.HttpRequest;
-import org.apache.http.client.HttpRequestRetryHandler;
-import org.apache.http.client.methods.HttpUriRequest;
-import org.apache.http.client.protocol.HttpClientContext;
-import org.apache.http.impl.client.RequestWrapper;
-import org.apache.http.protocol.HttpContext;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * @deprecated Please look into using Solr's new Http2 clients
- */
-@Deprecated(since = "9.0")
-public class SolrHttpRequestRetryHandler implements HttpRequestRetryHandler {
-
-  private static final String GET = "GET";
-
-  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
-
-  public static final SolrHttpRequestRetryHandler INSTANCE = new SolrHttpRequestRetryHandler();
-
-  /** the number of times a method will be retried */
-  private final int retryCount;
-
-  private final Set<Class<? extends IOException>> nonRetryableClasses;
-
-  /**
-   * Create the request retry handler using the specified IOException classes
-   *
-   * @param retryCount how many times to retry; 0 means no retries true if it's OK to retry requests
-   *     that have been sent
-   * @param clazzes the IOException types that should not be retried
-   */
-  protected SolrHttpRequestRetryHandler(
-      final int retryCount, final Collection<Class<? extends IOException>> clazzes) {
-    super();
-    this.retryCount = retryCount;
-    this.nonRetryableClasses = new HashSet<>();
-    for (final Class<? extends IOException> clazz : clazzes) {
-      this.nonRetryableClasses.add(clazz);
-    }
-  }
-
-  /**
-   * Create the request retry handler using the following list of non-retryable IOException classes:
-   * <br>
-   *
-   * <ul>
-   *   <li>InterruptedIOException
-   *   <li>UnknownHostException
-   *   <li>ConnectException
-   *   <li>SSLException
-   * </ul>
-   *
-   * @param retryCount how many times to retry; 0 means no retries true if it's OK to retry
-   *     non-idempotent requests that have been sent
-   */
-  public SolrHttpRequestRetryHandler(final int retryCount) {
-    this(
-        retryCount,
-        Arrays.asList(
-            InterruptedIOException.class,
-            UnknownHostException.class,
-            ConnectException.class,
-            SSLException.class));
-  }
-
-  /**
-   * Create the request retry handler with a retry count of 3, requestSentRetryEnabled false and
-   * using the following list of non-retryable IOException classes: <br>
-   *
-   * <ul>
-   *   <li>InterruptedIOException
-   *   <li>UnknownHostException
-   *   <li>ConnectException
-   *   <li>SSLException
-   * </ul>
-   */
-  public SolrHttpRequestRetryHandler() {
-    this(3);
-  }
-
-  @Override
-  public boolean retryRequest(
-      final IOException exception, final int executionCount, final HttpContext context) {
-    log.debug("Retry http request {} out of {}", executionCount, this.retryCount);
-    if (executionCount > this.retryCount) {
-      log.debug("Do not retry, over max retry count");
-      return false;
-    }
-
-    if (!isRetryable(exception)) {
-      if (log.isDebugEnabled()) {
-        log.debug("Do not retry, non retryable class {}", exception.getClass().getName());
-      }
-      return false;
-    }
-
-    final HttpClientContext clientContext = HttpClientContext.adapt(context);
-    final HttpRequest request = clientContext.getRequest();
-
-    if (requestIsAborted(request)) {
-      log.debug("Do not retry, request was aborted");
-      return false;
-    }
-
-    if (handleAsIdempotent(clientContext)) {
-      log.debug("Retry, request should be idempotent");
-      return true;
-    }
-
-    log.debug("Do not retry, no allow rules matched");
-    return false;
-  }
-
-  private boolean isRetryable(IOException exception) {
-    // Workaround for "recv failed" issue on hard-aborted sockets on Windows
-    // (and other operating systems, possibly).
-    // https://issues.apache.org/jira/browse/SOLR-13778
-    if (exception instanceof SSLException
-        && Arrays.stream(exception.getSuppressed()).anyMatch((t) -> t instanceof SocketException)) {
-      return true;
-    }
-
-    // Fast check for exact class followed by slow-check with instanceof.
-    if (nonRetryableClasses.contains(exception.getClass())
-        || nonRetryableClasses.stream()
-            .anyMatch(rejectException -> rejectException.isInstance(exception))) {
-      return false;
-    }
-
-    return true;
-  }
-
-  public int getRetryCount() {
-    return retryCount;
-  }
-
-  protected boolean handleAsIdempotent(final HttpClientContext context) {
-    String method = context.getRequest().getRequestLine().getMethod();
-    // do not retry admin requests, even if they are GET requests as they are not idempotent
-    if (context.getRequest().getRequestLine().getUri().startsWith("/admin/")) {
-      log.debug("Do not retry, this is an admin request");
-      return false;
-    }
-    return method.equals(GET);
-  }
-
-  protected boolean requestIsAborted(final HttpRequest request) {
-    HttpRequest req = request;
-    if (request instanceof RequestWrapper) { // does not forward request to original
-      req = ((RequestWrapper) request).getOriginal();
-    }
-    return (req instanceof HttpUriRequest && ((HttpUriRequest) req).isAborted());
-  }
-}
diff --git a/solr/test-framework/src/java/org/apache/solr/client/solrj/apache/SolrPortAwareCookieSpecFactory.java b/solr/test-framework/src/java/org/apache/solr/client/solrj/apache/SolrPortAwareCookieSpecFactory.java
deleted file mode 100644
index aadcf2ad9ac..00000000000
--- a/solr/test-framework/src/java/org/apache/solr/client/solrj/apache/SolrPortAwareCookieSpecFactory.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * 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.apache;
-
-import java.util.Collection;
-import org.apache.http.cookie.ClientCookie;
-import org.apache.http.cookie.Cookie;
-import org.apache.http.cookie.CookieOrigin;
-import org.apache.http.cookie.CookieSpec;
-import org.apache.http.cookie.CookieSpecFactory;
-import org.apache.http.cookie.CookieSpecProvider;
-import org.apache.http.cookie.MalformedCookieException;
-import org.apache.http.cookie.params.CookieSpecPNames;
-import org.apache.http.impl.cookie.NetscapeDomainHandler;
-import org.apache.http.impl.cookie.NetscapeDraftSpec;
-import org.apache.http.params.HttpParams;
-import org.apache.http.protocol.HttpContext;
-
-@SuppressWarnings("deprecation")
-@Deprecated
-public class SolrPortAwareCookieSpecFactory implements CookieSpecFactory, CookieSpecProvider {
-  public static final String POLICY_NAME = "solr-portaware";
-  private final CookieSpec cookieSpec;
-
-  public SolrPortAwareCookieSpecFactory(final String[] datepatterns) {
-    super();
-    this.cookieSpec = new PortAwareCookieSpec(datepatterns);
-  }
-
-  public SolrPortAwareCookieSpecFactory() {
-    this(null);
-  }
-
-  @Override
-  public CookieSpec newInstance(final HttpParams params) {
-    if (params != null) {
-      String[] patterns = null;
-      final Collection<?> param =
-          (Collection<?>) params.getParameter(CookieSpecPNames.DATE_PATTERNS);
-      if (param != null) {
-        patterns = new String[param.size()];
-        patterns = param.toArray(patterns);
-      }
-      return new PortAwareCookieSpec(patterns);
-    } else {
-      return new PortAwareCookieSpec(null);
-    }
-  }
-
-  @Override
-  public CookieSpec create(final HttpContext context) {
-    return this.cookieSpec;
-  }
-
-  public static class PortAwareCookieSpec extends NetscapeDraftSpec {
-    public PortAwareCookieSpec(String patterns[]) {
-      super(patterns);
-      super.registerAttribHandler(ClientCookie.DOMAIN_ATTR, new PortAwareDomainHandler());
-    }
-
-    public PortAwareCookieSpec() {
-      this(null);
-    }
-  }
-
-  /**
-   * A domain handler to validate and match cookies based on the domain and origin. The domain is
-   * tested against host and port both, and if it doesn't match, it delegates the handling to the
-   * base class' matching/validation logic.
-   */
-  public static class PortAwareDomainHandler extends NetscapeDomainHandler {
-
-    @Override
-    public void validate(final Cookie cookie, final CookieOrigin origin)
-        throws MalformedCookieException {
-      if (origin != null && origin.getHost() != null && cookie != null) {
-        String hostPort = origin.getHost() + ":" + origin.getPort();
-        String domain = cookie.getDomain();
-
-        if (hostPort.equals(domain)) {
-          return;
-        }
-      }
-      super.validate(cookie, origin);
-    }
-
-    @Override
-    public boolean match(final Cookie cookie, final CookieOrigin origin) {
-      if (origin != null && origin.getHost() != null && cookie != null) {
-        String hostPort = origin.getHost() + ":" + origin.getPort();
-        String domain = cookie.getDomain();
-        if (hostPort.equals(domain)) {
-          return true;
-        }
-      }
-      return super.match(cookie, origin);
-    }
-  }
-}
diff --git a/solr/test-framework/src/java/org/apache/solr/client/solrj/apache/package-info.java b/solr/test-framework/src/java/org/apache/solr/client/solrj/apache/package-info.java
deleted file mode 100644
index 31cd26e17ea..00000000000
--- a/solr/test-framework/src/java/org/apache/solr/client/solrj/apache/package-info.java
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * 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.
- */
-
-/** Apache HttpClient based {@link org.apache.solr.client.solrj.SolrClient} implementations */
-@Deprecated
-package org.apache.solr.client.solrj.apache;
diff --git a/solr/test-framework/src/java/org/apache/solr/cloud/AbstractFullDistribZkTestBase.java b/solr/test-framework/src/java/org/apache/solr/cloud/AbstractFullDistribZkTestBase.java
index e2062527680..ef9159a38d5 100644
--- a/solr/test-framework/src/java/org/apache/solr/cloud/AbstractFullDistribZkTestBase.java
+++ b/solr/test-framework/src/java/org/apache/solr/cloud/AbstractFullDistribZkTestBase.java
@@ -16,6 +16,7 @@
  */
 package org.apache.solr.cloud;
 
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.concurrent.TimeUnit.SECONDS;
 import static org.apache.solr.common.cloud.ZkStateReader.HTTPS;
 import static org.apache.solr.common.cloud.ZkStateReader.URL_SCHEME;
@@ -57,8 +58,9 @@ import org.apache.solr.client.solrj.SolrRequest;
 import org.apache.solr.client.solrj.SolrRequest.METHOD;
 import org.apache.solr.client.solrj.SolrRequest.SolrRequestType;
 import org.apache.solr.client.solrj.SolrServerException;
-import org.apache.solr.client.solrj.apache.HttpSolrClient;
 import org.apache.solr.client.solrj.impl.CloudSolrClient;
+import org.apache.solr.client.solrj.impl.HttpSolrClient;
+import org.apache.solr.client.solrj.jetty.HttpJettySolrClient;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
 import org.apache.solr.client.solrj.request.CoreAdminRequest;
 import org.apache.solr.client.solrj.request.GenericSolrRequest;
@@ -161,7 +163,8 @@ public abstract class AbstractFullDistribZkTestBase extends BaseDistributedSearc
 
   protected volatile CloudSolrClient controlClientCloud; // cloud version of the control client
   protected volatile CloudSolrClient cloudClient;
-  protected final List<SolrClient> coreClients = Collections.synchronizedList(new ArrayList<>());
+  protected final List<HttpSolrClient> coreClients =
+      Collections.synchronizedList(new ArrayList<>());
 
   protected final List<CloudJettyRunner> cloudJettys =
       Collections.synchronizedList(new ArrayList<>());
@@ -210,7 +213,7 @@ public abstract class AbstractFullDistribZkTestBase extends BaseDistributedSearc
   }
 
   public static class CloudSolrServerClient {
-    SolrClient solrClient;
+    HttpSolrClient solrClient;
     String nodeName;
     int port;
 
@@ -366,7 +369,7 @@ public abstract class AbstractFullDistribZkTestBase extends BaseDistributedSearc
   }
 
   protected CloudSolrClient createCloudClient(String defaultCollection) {
-    return getCloudSolrClient(
+    return createNewCloudSolrClient(
         zkServer.getZkAddress(), defaultCollection, random().nextBoolean(), 30000, 120000);
   }
 
@@ -394,7 +397,7 @@ public abstract class AbstractFullDistribZkTestBase extends BaseDistributedSearc
     }
 
     controlClient =
-        new HttpSolrClient.Builder(controlJetty.getBaseUrl().toString())
+        HttpSolrClient.builder(controlJetty.getBaseUrl().toString())
             .withDefaultCollection("control_collection")
             .build();
     if (sliceCount <= 0) {
@@ -448,7 +451,7 @@ public abstract class AbstractFullDistribZkTestBase extends BaseDistributedSearc
 
   protected List<JettySolrRunner> createJettys(int numJettys) throws Exception {
     List<JettySolrRunner> jettys = Collections.synchronizedList(new ArrayList<>());
-    List<SolrClient> clients = Collections.synchronizedList(new ArrayList<>());
+    List<HttpSolrClient> clients = Collections.synchronizedList(new ArrayList<>());
     List<CollectionAdminRequest<CollectionAdminResponse>> createReplicaRequests =
         Collections.synchronizedList(new ArrayList<>());
     List<CollectionAdminRequest<CollectionAdminResponse>> createPullReplicaRequests =
@@ -532,7 +535,7 @@ public abstract class AbstractFullDistribZkTestBase extends BaseDistributedSearc
                           .setType(Replica.Type.TLOG));
 
                   coreClients.add(createNewSolrClient(coreName, j.getLocalPort()));
-                  SolrClient client = createNewSolrClient(j.getLocalPort());
+                  var client = createNewSolrClient(j.getLocalPort());
                   clients.add(client);
 
                 } catch (Exception e) {
@@ -571,8 +574,7 @@ public abstract class AbstractFullDistribZkTestBase extends BaseDistributedSearc
                           .setNode(j.getNodeName())
                           .setType(Replica.Type.NRT));
                   coreClients.add(createNewSolrClient(coreName, j.getLocalPort()));
-                  SolrClient client = createNewSolrClient(j.getLocalPort());
-                  clients.add(client);
+                  clients.add(createNewSolrClient(j.getLocalPort()));
                 } catch (Exception e) {
                   log.error("error creating jetty", e);
                   throw new RuntimeException(e);
@@ -607,8 +609,7 @@ public abstract class AbstractFullDistribZkTestBase extends BaseDistributedSearc
                         .setNode(j.getNodeName())
                         .setType(Replica.Type.PULL));
                 coreClients.add(createNewSolrClient(coreName, j.getLocalPort()));
-                SolrClient client = createNewSolrClient(j.getLocalPort());
-                clients.add(client);
+                clients.add(createNewSolrClient(j.getLocalPort()));
               } catch (Exception e) {
                 log.error("error creating jetty", e);
                 throw new RuntimeException(e);
@@ -938,13 +939,13 @@ public abstract class AbstractFullDistribZkTestBase extends BaseDistributedSearc
     return relativizedCurDir.resolve(solrHomeRelativeToRoot).toAbsolutePath();
   }
 
-  protected void updateMappingsFromZk(List<JettySolrRunner> jettys, List<SolrClient> clients)
+  protected void updateMappingsFromZk(List<JettySolrRunner> jettys, List<HttpSolrClient> clients)
       throws Exception {
     updateMappingsFromZk(jettys, clients, false);
   }
 
   protected void updateMappingsFromZk(
-      List<JettySolrRunner> jettys, List<SolrClient> clients, boolean allowOverSharding)
+      List<JettySolrRunner> jettys, List<HttpSolrClient> clients, boolean allowOverSharding)
       throws Exception {
     ZkStateReader zkStateReader = ZkStateReader.from(cloudClient);
     zkStateReader.forceUpdateCollection(DEFAULT_COLLECTION);
@@ -955,13 +956,13 @@ public abstract class AbstractFullDistribZkTestBase extends BaseDistributedSearc
     DocCollection coll = clusterState.getCollection(DEFAULT_COLLECTION);
 
     List<CloudSolrServerClient> theClients = new ArrayList<>();
-    for (SolrClient client : clients) {
+    for (var client : clients) {
       // find info for this client in zk
       nextClient:
       // we find out state by simply matching ports...
       for (Slice slice : coll.getSlices()) {
         for (Replica replica : slice.getReplicas()) {
-          int port = new URI(((HttpSolrClient) client).getBaseURL()).getPort();
+          int port = new URI(client.getBaseURL()).getPort();
 
           if (replica.getBaseUrl().contains(":" + port)) {
             CloudSolrServerClient csc = new CloudSolrServerClient();
@@ -2043,8 +2044,7 @@ public abstract class AbstractFullDistribZkTestBase extends BaseDistributedSearc
       for (Map.Entry<String, Slice> slice : slices.entrySet()) {
         Map<String, Replica> theShards = slice.getValue().getReplicasMap();
         for (Map.Entry<String, Replica> shard : theShards.entrySet()) {
-          String shardName =
-              new URI(((HttpSolrClient) client.solrClient).getBaseURL()).getPort() + "_solr_";
+          String shardName = new URI(client.solrClient.getBaseURL()).getPort() + "_solr_";
           if (verbose && shard.getKey().endsWith(shardName)) {
             System.err.println("shard:" + slice.getKey());
             System.err.println(shard.getValue());
@@ -2359,7 +2359,7 @@ public abstract class AbstractFullDistribZkTestBase extends BaseDistributedSearc
     CollectionAdminResponse res = new CollectionAdminResponse();
     if (client == null) {
       final String baseUrl = getBaseUrl(jettys.get(clientIndex));
-      try (SolrClient adminClient = createNewSolrClient("", baseUrl)) {
+      try (SolrClient adminClient = createNewSolrClient(baseUrl, null)) {
         res.setResponse(adminClient.request(request));
       }
     } else {
@@ -2441,44 +2441,42 @@ public abstract class AbstractFullDistribZkTestBase extends BaseDistributedSearc
    * not wish to have any randomized behavior should use the {@link
    * org.apache.solr.client.solrj.impl.CloudSolrClient.Builder} class directly
    */
-  public static CloudSolrClient getCloudSolrClient(
+  public static CloudSolrClient createNewCloudSolrClient(
       String zkHost,
       String defaultCollection,
       boolean shardLeadersOnly,
       int connectionTimeoutMillis,
-      int socketTimeoutMillis) {
-    RandomizingCloudSolrClientBuilder builder =
-        new RandomizingCloudSolrClientBuilder(List.of(zkHost), Optional.empty());
+      int idleTimeoutMillis) {
+    var builder = new RandomizingCloudSolrClientBuilder(List.of(zkHost), Optional.empty());
+    builder.withDefaultCollection(defaultCollection);
     if (shardLeadersOnly) {
       builder.sendUpdatesOnlyToShardLeaders();
     } else {
-      builder.sendUpdatesToAllReplicasInShard();
+      builder.sendUpdatesToAnyReplica();
     }
-    if (defaultCollection != null) {
-      builder.withDefaultCollection(defaultCollection);
-    }
-    return builder
-        .withConnectionTimeout(connectionTimeoutMillis)
-        .withSocketTimeout(socketTimeoutMillis)
-        .build();
+    builder.withHttpClientBuilder(
+        new HttpJettySolrClient.Builder()
+            .withConnectionTimeout(connectionTimeoutMillis, MILLISECONDS)
+            .withIdleTimeout(idleTimeoutMillis, MILLISECONDS));
+    return builder.build();
   }
 
   @Override
-  protected SolrClient createNewSolrClient(int port) {
+  protected HttpSolrClient createNewSolrClient(int port) {
     return createNewSolrClient(DEFAULT_COLLECTION, port);
   }
 
-  protected SolrClient createNewSolrClient(String coreName, int port) {
+  protected HttpSolrClient createNewSolrClient(String coreName, int port) {
     String baseUrl = buildUrl(port);
-    return new HttpSolrClient.Builder(baseUrl)
+    return HttpSolrClient.builder(baseUrl)
         .withDefaultCollection(coreName)
         .withConnectionTimeout(DEFAULT_CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS)
-        .withSocketTimeout(60000, TimeUnit.MILLISECONDS)
+        .withIdleTimeout(60000, TimeUnit.MILLISECONDS)
         .build();
   }
 
-  protected SolrClient createNewSolrClient(String collection, String baseUrl) {
-    return getHttpSolrClient(baseUrl, collection);
+  protected SolrClient createNewSolrClient(String baseUrl, String collection) {
+    return new HttpJettySolrClient.Builder(baseUrl).withDefaultCollection(collection).build();
   }
 
   protected String getBaseUrl(JettySolrRunner jetty) {
@@ -2552,7 +2550,7 @@ public abstract class AbstractFullDistribZkTestBase extends BaseDistributedSearc
     synchronized (this) {
       if (commonCloudSolrClient == null) {
         commonCloudSolrClient =
-            getCloudSolrClient(
+            createNewCloudSolrClient(
                 zkServer.getZkAddress(), DEFAULT_COLLECTION, random().nextBoolean(), 5000, 120000);
         commonCloudSolrClient.connect();
         if (log.isInfoEnabled()) {
@@ -2571,7 +2569,7 @@ public abstract class AbstractFullDistribZkTestBase extends BaseDistributedSearc
         collectionName,
         k -> {
           CloudSolrClient solrClient =
-              getCloudSolrClient(
+              createNewCloudSolrClient(
                   zkServer.getZkAddress(), collectionName, random().nextBoolean(), 5000, 120000);
 
           solrClient.connect();
@@ -3000,7 +2998,7 @@ public abstract class AbstractFullDistribZkTestBase extends BaseDistributedSearc
 
   protected long getIndexVersion(Replica replica) throws IOException {
     try (SolrClient client =
-        new HttpSolrClient.Builder(replica.getBaseUrl())
+        HttpSolrClient.builder(replica.getBaseUrl())
             .withDefaultCollection(replica.getCoreName())
             .build()) {
       ModifiableSolrParams params = new ModifiableSolrParams();
@@ -3050,7 +3048,7 @@ public abstract class AbstractFullDistribZkTestBase extends BaseDistributedSearc
 
   protected void logReplicationDetails(Replica replica, StringBuilder builder) throws IOException {
     try (SolrClient client =
-        new HttpSolrClient.Builder(replica.getBaseUrl())
+        HttpSolrClient.builder(replica.getBaseUrl())
             .withDefaultCollection(replica.getCoreName())
             .build()) {
       ModifiableSolrParams params = new ModifiableSolrParams();
diff --git a/solr/test-framework/src/java/org/apache/solr/cloud/FullThrottleStoppableIndexingThread.java b/solr/test-framework/src/java/org/apache/solr/cloud/FullThrottleStoppableIndexingThread.java
index 8ca5646f03e..30c8f67d406 100644
--- a/solr/test-framework/src/java/org/apache/solr/cloud/FullThrottleStoppableIndexingThread.java
+++ b/solr/test-framework/src/java/org/apache/solr/cloud/FullThrottleStoppableIndexingThread.java
@@ -23,12 +23,13 @@ import java.util.List;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
-import org.apache.http.client.HttpClient;
 import org.apache.lucene.tests.util.LuceneTestCase;
 import org.apache.solr.client.solrj.SolrClient;
-import org.apache.solr.client.solrj.apache.ConcurrentUpdateSolrClient;
-import org.apache.solr.client.solrj.apache.HttpSolrClient;
 import org.apache.solr.client.solrj.impl.CloudSolrClient;
+import org.apache.solr.client.solrj.impl.ConcurrentUpdateBaseSolrClient;
+import org.apache.solr.client.solrj.impl.HttpSolrClient;
+import org.apache.solr.client.solrj.jetty.ConcurrentUpdateJettySolrClient;
+import org.apache.solr.client.solrj.jetty.HttpJettySolrClient;
 import org.apache.solr.common.SolrInputDocument;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -38,19 +39,18 @@ class FullThrottleStoppableIndexingThread extends StoppableIndexingThread {
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
   /** */
-  private final HttpClient httpClient;
+  private final HttpJettySolrClient httpClient;
 
   private volatile boolean stop = false;
   int clientIndex = 0;
-  private ConcurrentUpdateSolrClient cusc;
-  private List<SolrClient> clients;
+  private ConcurrentUpdateBaseSolrClient cusc;
+  private List<HttpSolrClient> clients;
   private AtomicInteger fails = new AtomicInteger();
 
   public FullThrottleStoppableIndexingThread(
-      HttpClient httpClient,
       SolrClient controlClient,
       CloudSolrClient cloudClient,
-      List<SolrClient> clients,
+      List<HttpSolrClient> clients,
       String id,
       boolean doDeletes,
       int clientSoTimeout) {
@@ -58,17 +58,18 @@ class FullThrottleStoppableIndexingThread extends StoppableIndexingThread {
     setName("FullThrottleStoppableIndexingThread");
     setDaemon(true);
     this.clients = clients;
-    this.httpClient = httpClient;
+    this.httpClient =
+        new HttpJettySolrClient.Builder()
+            .withConnectionTimeout(10, TimeUnit.SECONDS)
+            .withIdleTimeout(clientSoTimeout, TimeUnit.MILLISECONDS)
+            .build();
 
     cusc =
         new ErrorLoggingConcurrentUpdateSolrClient.Builder(
-                ((HttpSolrClient) clients.get(0)).getBaseURL())
-            .withDefaultCollection(clients.get(0).getDefaultCollection())
-            .withHttpClient(httpClient)
+                clients.getFirst().getBaseURL(), httpClient)
+            .withDefaultCollection(clients.getFirst().getDefaultCollection())
             .withQueueSize(8)
             .withThreadCount(2)
-            .withConnectionTimeout(10000, TimeUnit.MILLISECONDS)
-            .withSocketTimeout(clientSoTimeout, TimeUnit.MILLISECONDS)
             .build();
   }
 
@@ -127,9 +128,8 @@ class FullThrottleStoppableIndexingThread extends StoppableIndexingThread {
       cusc.shutdownNow();
       cusc =
           new ErrorLoggingConcurrentUpdateSolrClient.Builder(
-                  ((HttpSolrClient) clients.get(clientIndex)).getBaseURL())
+                  clients.get(clientIndex).getBaseURL(), httpClient)
               .withDefaultCollection(clients.get(clientIndex).getDefaultCollection())
-              .withHttpClient(httpClient)
               .withQueueSize(30)
               .withThreadCount(3)
               .build();
@@ -163,7 +163,7 @@ class FullThrottleStoppableIndexingThread extends StoppableIndexingThread {
     throw new UnsupportedOperationException();
   }
 
-  static class ErrorLoggingConcurrentUpdateSolrClient extends ConcurrentUpdateSolrClient {
+  static class ErrorLoggingConcurrentUpdateSolrClient extends ConcurrentUpdateJettySolrClient {
     public ErrorLoggingConcurrentUpdateSolrClient(Builder builder) {
       super(builder);
     }
@@ -173,10 +173,10 @@ class FullThrottleStoppableIndexingThread extends StoppableIndexingThread {
       log.warn("cusc error", ex);
     }
 
-    static class Builder extends ConcurrentUpdateSolrClient.Builder {
+    static class Builder extends ConcurrentUpdateJettySolrClient.Builder {
 
-      public Builder(String baseSolrUrl) {
-        super(baseSolrUrl);
+      public Builder(String baseSolrUrl, HttpJettySolrClient jettySolrClient) {
+        super(baseSolrUrl, jettySolrClient);
       }
 
       @Override
diff --git a/solr/test-framework/src/java/org/apache/solr/cloud/MiniSolrCloudCluster.java b/solr/test-framework/src/java/org/apache/solr/cloud/MiniSolrCloudCluster.java
index 121a212270d..b1cbb83362d 100644
--- a/solr/test-framework/src/java/org/apache/solr/cloud/MiniSolrCloudCluster.java
+++ b/solr/test-framework/src/java/org/apache/solr/cloud/MiniSolrCloudCluster.java
@@ -54,8 +54,10 @@ import org.apache.lucene.tests.util.LuceneTestCase;
 import org.apache.solr.SolrBackend;
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.client.solrj.SolrServerException;
-import org.apache.solr.client.solrj.apache.CloudLegacySolrClient;
 import org.apache.solr.client.solrj.impl.CloudSolrClient;
+import org.apache.solr.client.solrj.impl.ZkClientClusterStateProvider;
+import org.apache.solr.client.solrj.jetty.CloudJettySolrClient;
+import org.apache.solr.client.solrj.jetty.HttpJettySolrClient;
 import org.apache.solr.client.solrj.jetty.SSLConfig;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
 import org.apache.solr.common.SolrException;
@@ -664,12 +666,7 @@ public class MiniSolrCloudCluster implements SolrBackend {
     return solrClientByCollection.computeIfAbsent(
         collectionName,
         k -> {
-          CloudSolrClient solrClient =
-              new CloudLegacySolrClient.Builder(List.of(zkServer.getZkAddress()), Optional.empty())
-                  .withDefaultCollection(collectionName)
-                  .withSocketTimeout(90000)
-                  .withConnectionTimeout(15000)
-                  .build();
+          CloudSolrClient solrClient = newSolrClient(collectionName);
 
           solrClient.connect();
           if (log.isInfoEnabled()) {
@@ -685,11 +682,13 @@ public class MiniSolrCloudCluster implements SolrBackend {
 
   @Override // SolrBackend
   public CloudSolrClient newSolrClient(String collection) {
-    return new CloudLegacySolrClient.Builder(
-            List.of(getZkServer().getZkAddress()), Optional.empty())
-        .withSocketTimeout(90000, TimeUnit.MILLISECONDS)
-        .withConnectionTimeout(15000, TimeUnit.MILLISECONDS)
+    return new CloudJettySolrClient.Builder(
+            new ZkClientClusterStateProvider(getZkServer().getZkAddress()))
         .withDefaultCollection(collection)
+        .withHttpClientBuilder(
+            new HttpJettySolrClient.Builder()
+                .withConnectionTimeout(15, TimeUnit.SECONDS)
+                .withIdleTimeout(90, TimeUnit.SECONDS))
         .build(); // we choose 90 because we run in some harsh envs
   }
 
diff --git a/solr/test-framework/src/java/org/apache/solr/cloud/api/collections/AbstractCloudBackupRestoreTestCase.java b/solr/test-framework/src/java/org/apache/solr/cloud/api/collections/AbstractCloudBackupRestoreTestCase.java
index 8acdf9ad983..3124d5c1451 100644
--- a/solr/test-framework/src/java/org/apache/solr/cloud/api/collections/AbstractCloudBackupRestoreTestCase.java
+++ b/solr/test-framework/src/java/org/apache/solr/cloud/api/collections/AbstractCloudBackupRestoreTestCase.java
@@ -31,12 +31,11 @@ import java.util.TreeMap;
 import java.util.UUID;
 import org.apache.lucene.tests.util.TestUtil;
 import org.apache.solr.client.solrj.SolrServerException;
-import org.apache.solr.client.solrj.apache.CloudLegacySolrClient;
-import org.apache.solr.client.solrj.apache.HttpSolrClient;
 import org.apache.solr.client.solrj.impl.CloudSolrClient;
+import org.apache.solr.client.solrj.jetty.CloudJettySolrClient;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest.ClusterProp;
-import org.apache.solr.client.solrj.request.SolrQuery;
+import org.apache.solr.client.solrj.request.QueryRequest;
 import org.apache.solr.client.solrj.response.RequestStatusState;
 import org.apache.solr.cloud.AbstractFullDistribZkTestBase;
 import org.apache.solr.cloud.SolrCloudTestCase;
@@ -468,21 +467,17 @@ public abstract class AbstractCloudBackupRestoreTestCase extends SolrCloudTestCa
   public static Map<String, Integer> getShardToDocCountMap(
       CloudSolrClient client, DocCollection docCollection) throws SolrServerException, IOException {
     Map<String, Integer> shardToDocCount = new TreeMap<>();
+    var jettySolrClient = ((CloudJettySolrClient) client).getHttpClient();
     for (Slice slice : docCollection.getActiveSlices()) {
-      String shardName = slice.getName();
-      try (var leaderClient =
-          new HttpSolrClient.Builder(slice.getLeader().getBaseUrl())
-              .withDefaultCollection(slice.getLeader().getCoreName())
-              .withHttpClient(((CloudLegacySolrClient) client).getHttpClient())
-              .build()) {
-        long docsInShard =
-            leaderClient
-                .query(new SolrQuery("*:*").setParam("distrib", "false"))
-                .getResults()
-                .getNumFound();
-        shardToDocCount.put(shardName, (int) docsInShard);
-      }
+      long docsInShard =
+          new QueryRequest("/select", params("q", "*:*", "distrib", "false"))
+              .processWithBaseUrl(
+                  jettySolrClient, slice.getLeader().getBaseUrl(), slice.getLeader().getCoreName())
+              .getResults()
+              .getNumFound();
+      shardToDocCount.put(slice.getName(), (int) docsInShard);
     }
+
     return shardToDocCount;
   }
 }
diff --git a/solr/test-framework/src/java/org/apache/solr/embedded/JettySolrRunner.java b/solr/test-framework/src/java/org/apache/solr/embedded/JettySolrRunner.java
index 30e1a45f54c..653a001ff1d 100644
--- a/solr/test-framework/src/java/org/apache/solr/embedded/JettySolrRunner.java
+++ b/solr/test-framework/src/java/org/apache/solr/embedded/JettySolrRunner.java
@@ -42,9 +42,7 @@ import java.util.Set;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 import org.apache.solr.SolrBackend;
-import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrServerException;
-import org.apache.solr.client.solrj.apache.HttpSolrClient;
 import org.apache.solr.client.solrj.embedded.EmbeddedSolrServer;
 import org.apache.solr.client.solrj.jetty.HttpJettySolrClient;
 import org.apache.solr.client.solrj.jetty.SSLConfig;
@@ -752,14 +750,15 @@ public class JettySolrRunner implements SolrBackend {
     }
   }
 
-  public SolrClient newClient() {
-    return new HttpSolrClient.Builder(getBaseUrl().toString()).build();
+  @Deprecated
+  public HttpJettySolrClient newClient() {
+    return new HttpJettySolrClient.Builder(getBaseUrl().toString()).build();
   }
 
-  public SolrClient newClient(int connectionTimeoutMillis, int socketTimeoutMillis) {
-    return new HttpSolrClient.Builder(getBaseUrl().toString())
+  public HttpJettySolrClient newClient(int connectionTimeoutMillis, int socketTimeoutMillis) {
+    return new HttpJettySolrClient.Builder(getBaseUrl().toString())
         .withConnectionTimeout(connectionTimeoutMillis, TimeUnit.MILLISECONDS)
-        .withSocketTimeout(socketTimeoutMillis, TimeUnit.MILLISECONDS)
+        .withIdleTimeout(socketTimeoutMillis, TimeUnit.MILLISECONDS)
         .build();
   }
 
@@ -813,7 +812,7 @@ public class JettySolrRunner implements SolrBackend {
   // ---- SolrBackend implementation ----
 
   @Override
-  public SolrClient newSolrClient(String collection) {
+  public HttpJettySolrClient newSolrClient(String collection) {
     return new HttpJettySolrClient.Builder(getBaseUrl().toString())
         .withDefaultCollection(collection)
         .build();
diff --git a/solr/test-framework/src/java/org/apache/solr/util/SSLTestConfig.java b/solr/test-framework/src/java/org/apache/solr/util/SSLTestConfig.java
index 70f26d6405e..9a62ecb2afb 100644
--- a/solr/test-framework/src/java/org/apache/solr/util/SSLTestConfig.java
+++ b/solr/test-framework/src/java/org/apache/solr/util/SSLTestConfig.java
@@ -24,19 +24,19 @@ import java.security.SecureRandom;
 import java.security.SecureRandomParameters;
 import java.security.SecureRandomSpi;
 import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
 import java.util.Random;
 import java.util.concurrent.ThreadLocalRandom;
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.KeyManagerFactory;
 import javax.net.ssl.SSLContext;
-import org.apache.http.config.Registry;
-import org.apache.http.config.RegistryBuilder;
-import org.apache.http.conn.socket.ConnectionSocketFactory;
-import org.apache.http.conn.socket.PlainConnectionSocketFactory;
-import org.apache.http.conn.ssl.NoopHostnameVerifier;
-import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
-import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
-import org.apache.http.ssl.SSLContextBuilder;
-import org.apache.http.ssl.SSLContexts;
-import org.apache.solr.client.solrj.apache.HttpClientUtil.SocketFactoryRegistryProvider;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509TrustManager;
 import org.apache.solr.client.solrj.impl.SolrHttpConstants;
 import org.apache.solr.client.solrj.jetty.SSLConfig;
 import org.eclipse.jetty.util.resource.Resource;
@@ -45,9 +45,9 @@ import org.eclipse.jetty.util.security.CertificateUtils;
 import org.eclipse.jetty.util.ssl.SslContextFactory;
 
 /**
- * An SSLConfig that provides {@link SSLConfig} and {@link SocketFactoryRegistryProvider} for both
- * clients and servers that supports reading key/trust store information directly from resource
- * files provided with the Solr test-framework classes
+ * An SSLConfig that provides {@link SSLConfig} for both clients and servers that supports reading
+ * key/trust store information directly from resource files provided with the Solr test-framework
+ * classes
  */
 public class SSLTestConfig {
   private static final String TEST_KEYSTORE_BOGUSHOST_RESOURCE =
@@ -101,7 +101,6 @@ public class SSLTestConfig {
    *     Certificate (and which testing Cert should be used)
    * @see SolrHttpConstants#SYS_PROP_CHECK_PEER_NAME
    */
-  @SuppressWarnings("removal")
   public SSLTestConfig(boolean useSsl, boolean clientAuth, boolean checkPeerName) {
     this.useSsl = useSsl;
     this.clientAuth = clientAuth;
@@ -130,23 +129,6 @@ public class SSLTestConfig {
     return clientAuth;
   }
 
-  /**
-   * Creates a {@link SocketFactoryRegistryProvider} for HTTP <b>clients</b> to use when
-   * communicating with servers which have been configured based on the settings of this object.
-   * When {@link #isSSLMode} is true, this <code>SocketFactoryRegistryProvider</code> will
-   * <i>only</i> support HTTPS (no HTTP scheme) using the appropriate certs. When {@link #isSSLMode}
-   * is false, <i>only</i> HTTP (no HTTPS scheme) will be supported.
-   */
-  public SocketFactoryRegistryProvider buildClientSocketFactoryRegistryProvider() {
-    if (isSSLMode()) {
-      SSLConnectionSocketFactory sslConnectionFactory = buildClientSSLConnectionSocketFactory();
-      assert null != sslConnectionFactory;
-      return new SSLSocketFactoryRegistryProvider(sslConnectionFactory);
-    } else {
-      return HTTP_ONLY_SCHEMA_PROVIDER;
-    }
-  }
-
   /**
    * Builds a new SSLContext for HTTP <b>clients</b> to use when communicating with servers which
    * have been configured based on the settings of this object.
@@ -163,22 +145,25 @@ public class SSLTestConfig {
 
     assert isSSLMode();
 
-    SSLContextBuilder builder = SSLContexts.custom();
-    builder.setSecureRandom(NotSecurePseudoRandom.INSTANCE);
-
     // NOTE: KeyStore & TrustStore are swapped because they are from configured from server
     // perspective...
     // we are a client - our keystore contains the keys the server trusts, and vice versa
-    builder
-        .loadTrustMaterial(buildKeyStore(keyStore, TEST_PASSWORD), new TrustSelfSignedStrategy())
-        .build();
+    var tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+    tmf.init(buildKeyStore(keyStore, TEST_PASSWORD));
 
+    KeyManager[] keyManagers = null;
     if (isClientAuthMode()) {
-      builder.loadKeyMaterial(
-          buildKeyStore(trustStore, TEST_PASSWORD), TEST_PASSWORD.toCharArray());
+      var kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+      kmf.init(buildKeyStore(trustStore, TEST_PASSWORD), TEST_PASSWORD.toCharArray());
+      keyManagers = kmf.getKeyManagers();
     }
 
-    return builder.build();
+    SSLContext sslContext = SSLContext.getInstance("TLS");
+    sslContext.init(
+        keyManagers,
+        trustSelfSignedStrategy(tmf.getTrustManagers()),
+        NotSecurePseudoRandom.INSTANCE);
+    return sslContext;
   }
 
   public SSLConfig buildClientSSLConfig() {
@@ -222,19 +207,19 @@ public class SSLTestConfig {
       public SslContextFactory.Server createContextFactory() {
         SslContextFactory.Server factory = new SslContextFactory.Server();
         try {
-          SSLContextBuilder builder = SSLContexts.custom();
-          builder.setSecureRandom(NotSecurePseudoRandom.INSTANCE);
-
-          builder.loadKeyMaterial(
-              buildKeyStore(keyStore, TEST_PASSWORD), TEST_PASSWORD.toCharArray());
+          var kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+          kmf.init(buildKeyStore(keyStore, TEST_PASSWORD), TEST_PASSWORD.toCharArray());
 
+          TrustManager[] trustManagers = null;
           if (isClientAuthMode()) {
-            builder
-                .loadTrustMaterial(
-                    buildKeyStore(trustStore, TEST_PASSWORD), new TrustSelfSignedStrategy())
-                .build();
+            var tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+            tmf.init(buildKeyStore(trustStore, TEST_PASSWORD));
+            trustManagers = trustSelfSignedStrategy(tmf.getTrustManagers());
           }
-          factory.setSslContext(builder.build());
+
+          SSLContext sslContext = SSLContext.getInstance("TLS");
+          sslContext.init(kmf.getKeyManagers(), trustManagers, NotSecurePseudoRandom.INSTANCE);
+          factory.setSslContext(sslContext);
         } catch (Exception e) {
           throw new RuntimeException("ssl context init failure: " + e.getMessage(), e);
         }
@@ -244,6 +229,51 @@ public class SSLTestConfig {
     };
   }
 
+  /**
+   * Returns trust managers that accept self-signed certs, delegating all others to {@code
+   * delegates}.
+   */
+  private static TrustManager[] trustSelfSignedStrategy(TrustManager[] delegates) {
+    return new TrustManager[] {
+      new X509TrustManager() {
+        @Override
+        public void checkClientTrusted(X509Certificate[] chain, String authType)
+            throws CertificateException {
+          if (chain.length == 1) return;
+          for (TrustManager tm : delegates) {
+            if (tm instanceof X509TrustManager) {
+              ((X509TrustManager) tm).checkClientTrusted(chain, authType);
+              return;
+            }
+          }
+        }
+
+        @Override
+        public void checkServerTrusted(X509Certificate[] chain, String authType)
+            throws CertificateException {
+          if (chain.length == 1) return;
+          for (TrustManager tm : delegates) {
+            if (tm instanceof X509TrustManager) {
+              ((X509TrustManager) tm).checkServerTrusted(chain, authType);
+              return;
+            }
+          }
+        }
+
+        @Override
+        public X509Certificate[] getAcceptedIssuers() {
+          List<X509Certificate> issuers = new ArrayList<>();
+          for (TrustManager tm : delegates) {
+            if (tm instanceof X509TrustManager) {
+              issuers.addAll(Arrays.asList(((X509TrustManager) tm).getAcceptedIssuers()));
+            }
+          }
+          return issuers.toArray(new X509Certificate[0]);
+        }
+      }
+    };
+  }
+
   public KeyStore defaultKeyStore() {
     if (keyStore == null) {
       return null;
@@ -269,64 +299,6 @@ public class SSLTestConfig {
     }
   }
 
-  /**
-   * Constructs a new SSLConnectionSocketFactory for HTTP <b>clients</b> to use when communicating
-   * with servers which have been configured based on the settings of this object. Will return null
-   * unless {@link #isSSLMode} is true.
-   */
-  public SSLConnectionSocketFactory buildClientSSLConnectionSocketFactory() {
-    if (!isSSLMode()) {
-      return null;
-    }
-    SSLConnectionSocketFactory sslConnectionFactory;
-    try {
-      SSLContext sslContext = buildClientSSLContext();
-      if (checkPeerName == false) {
-        sslConnectionFactory =
-            new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
-      } else {
-        sslConnectionFactory = new SSLConnectionSocketFactory(sslContext);
-      }
-    } catch (KeyManagementException
-        | UnrecoverableKeyException
-        | NoSuchAlgorithmException
-        | KeyStoreException e) {
-      throw new IllegalStateException(
-          "Unable to setup https scheme for HTTPClient to test SSL.", e);
-    }
-    return sslConnectionFactory;
-  }
-
-  /**
-   * A SocketFactoryRegistryProvider that only knows about SSL using a specified
-   * SSLConnectionSocketFactory
-   */
-  private static class SSLSocketFactoryRegistryProvider extends SocketFactoryRegistryProvider {
-    private final SSLConnectionSocketFactory sslConnectionFactory;
-
-    public SSLSocketFactoryRegistryProvider(SSLConnectionSocketFactory sslConnectionFactory) {
-      this.sslConnectionFactory = sslConnectionFactory;
-    }
-
-    @Override
-    public Registry<ConnectionSocketFactory> getSocketFactoryRegistry() {
-      return RegistryBuilder.<ConnectionSocketFactory>create()
-          .register("https", sslConnectionFactory)
-          .build();
-    }
-  }
-
-  /** A SocketFactoryRegistryProvider that only knows about HTTP */
-  private static final SocketFactoryRegistryProvider HTTP_ONLY_SCHEMA_PROVIDER =
-      new SocketFactoryRegistryProvider() {
-        @Override
-        public Registry<ConnectionSocketFactory> getSocketFactoryRegistry() {
-          return RegistryBuilder.<ConnectionSocketFactory>create()
-              .register("http", PlainConnectionSocketFactory.getSocketFactory())
-              .build();
-        }
-      };
-
   /**
    * A mocked up instance of SecureRandom that just uses {@link Random} under the covers. This is to
    * prevent blocking issues that arise in platform default SecureRandom instances due to too many
diff --git a/solr/test-framework/src/java/org/apache/solr/util/SolrJettyTestRule.java b/solr/test-framework/src/java/org/apache/solr/util/SolrJettyTestRule.java
index 467c6bb2b1b..f540f823bbb 100644
--- a/solr/test-framework/src/java/org/apache/solr/util/SolrJettyTestRule.java
+++ b/solr/test-framework/src/java/org/apache/solr/util/SolrJettyTestRule.java
@@ -16,12 +16,14 @@
  */
 package org.apache.solr.util;
 
+import static org.apache.solr.SolrTestCaseJ4.DEFAULT_TEST_COLLECTION_NAME;
+
 import java.lang.invoke.MethodHandles;
 import java.nio.file.Path;
 import java.util.Properties;
 import java.util.concurrent.ConcurrentHashMap;
 import org.apache.solr.client.solrj.SolrClient;
-import org.apache.solr.client.solrj.apache.HttpSolrClient;
+import org.apache.solr.client.solrj.jetty.HttpJettySolrClient;
 import org.apache.solr.common.util.IOUtils;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.embedded.JettyConfig;
@@ -113,11 +115,21 @@ public class SolrJettyTestRule extends SolrClientTestRule {
   }
 
   protected SolrClient newSolrClient(String collection) {
-    return new HttpSolrClient.Builder(getBaseUrl())
+    return newSolrClientBuilder()
         .withDefaultCollection(collection) // Properly handles when collection is 'null'
         .build();
   }
 
+  /**
+   * Creates a client builder with the URL, shared Jetty HttpClient, and default collection
+   * "collection1" (can be changed).
+   */
+  public HttpJettySolrClient.Builder newSolrClientBuilder() {
+    return new HttpJettySolrClient.Builder(getBaseUrl())
+        .withHttpClient(jetty.getSolrClient())
+        .withDefaultCollection(DEFAULT_TEST_COLLECTION_NAME);
+  }
+
   /** URL to Solr. */
   public String getBaseUrl() {
     return getJetty().getBaseUrl().toString();


(solr) 03/03: SOLR-17208: Parallelize backup and restore file transfers (#4023)

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

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

commit e2edd26b12c9e97d32c5b3383b7da5f9579d2665
Author: Samuel Verstraete <19...@users.noreply.github.com>
AuthorDate: Wed Jun 10 20:12:20 2026 +0200

    SOLR-17208: Parallelize backup and restore file transfers (#4023)
    
    Co-authored-by: samuel <sa...@ontoforce.com>
    Co-authored-by: David Smiley <ds...@apache.org>
    (cherry picked from commit 74974b1be7e5b4a58dbe93468a359aca9621a647)
---
 .../unreleased/SOLR-1092-parallelizebackups.yml    |  10 ++
 .../solr/handler/IncrementalShardBackup.java       | 133 ++++++++++++++++-----
 .../java/org/apache/solr/handler/RestoreCore.java  | 115 ++++++++++++++----
 .../apache/solr/gcs/GCSIncrementalBackupTest.java  |   3 +
 .../apache/solr/s3/S3IncrementalBackupTest.java    |   3 +
 .../deployment-guide/pages/backup-restore.adoc     |  32 +++++
 .../org/apache/solr/common/util/ObjectCache.java   |   7 +-
 7 files changed, 245 insertions(+), 58 deletions(-)

diff --git a/changelog/unreleased/SOLR-1092-parallelizebackups.yml b/changelog/unreleased/SOLR-1092-parallelizebackups.yml
new file mode 100644
index 00000000000..84853f8bc39
--- /dev/null
+++ b/changelog/unreleased/SOLR-1092-parallelizebackups.yml
@@ -0,0 +1,10 @@
+title: Parallelize Backup and Restore File Operations
+type: changed
+authors:
+  - name: Samuel Verstraete
+    nick: elangelo
+  - name: David Smiley
+    nick: dsmiley
+links:
+  - name: SOLR-1092
+    url: https://issues.apache.org/jira/browse/SOLR-1092
diff --git a/solr/core/src/java/org/apache/solr/handler/IncrementalShardBackup.java b/solr/core/src/java/org/apache/solr/handler/IncrementalShardBackup.java
index 0e07ac0ca28..71ea48eac82 100644
--- a/solr/core/src/java/org/apache/solr/handler/IncrementalShardBackup.java
+++ b/solr/core/src/java/org/apache/solr/handler/IncrementalShardBackup.java
@@ -23,15 +23,25 @@ import java.io.IOException;
 import java.lang.invoke.MethodHandles;
 import java.net.URI;
 import java.time.Instant;
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.List;
 import java.util.Optional;
 import java.util.UUID;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
 import org.apache.commons.math3.util.Precision;
 import org.apache.lucene.index.IndexCommit;
 import org.apache.lucene.store.Directory;
 import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.cloud.CloudDescriptor;
 import org.apache.solr.common.SolrException;
+import org.apache.solr.common.util.EnvUtils;
+import org.apache.solr.common.util.ExecutorUtil;
+import org.apache.solr.common.util.SolrNamedThreadFactory;
 import org.apache.solr.core.DirectoryFactory;
 import org.apache.solr.core.IndexDeletionPolicyWrapper;
 import org.apache.solr.core.SolrCore;
@@ -52,6 +62,15 @@ import org.slf4j.LoggerFactory;
  */
 public class IncrementalShardBackup {
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+  /**
+   * Maximum number of files to upload in parallel during backup. Can be configured via the system
+   * property {@code solr.backup.maxparalleluploads} or environment variable {@code
+   * SOLR_BACKUP_MAXPARALLELUPLOADS}.
+   */
+  private static final int MAX_PARALLEL_UPLOADS =
+      EnvUtils.getPropertyAsInteger("solr.backup.maxparalleluploads", 1);
+
   private SolrCore solrCore;
 
   private BackupFilePaths incBackupFiles;
@@ -154,8 +173,8 @@ public class IncrementalShardBackup {
                 solrCore.getSolrConfig().indexConfig.lockType);
     try {
       BackupStats stats = incrementalCopy(files, dir);
-      details.indexFileCount = stats.fileCount;
-      details.uploadedIndexFileCount = stats.uploadedFileCount;
+      details.indexFileCount = stats.fileCount.get();
+      details.uploadedIndexFileCount = stats.uploadedFileCount.get();
       details.indexSizeMB = stats.getIndexSizeMB();
       details.uploadedIndexFileMB = stats.getTotalUploadedMB();
     } finally {
@@ -191,25 +210,77 @@ public class IncrementalShardBackup {
     URI indexDir = incBackupFiles.getIndexDir();
     BackupStats backupStats = new BackupStats();
 
+    var executor =
+        solrCore
+            .getCoreContainer()
+            .getObjectCache()
+            .computeIfAbsent(
+                "BackupUploadExecutor",
+                ExecutorService.class,
+                s ->
+                    ExecutorUtil.newMDCAwareCachedThreadPool(
+                        MAX_PARALLEL_UPLOADS,
+                        Integer.MAX_VALUE,
+                        new SolrNamedThreadFactory("BackupUploadExecutor")));
+
+    List<Future<?>> uploadFutures = new ArrayList<>();
     for (String fileName : indexFiles) {
-      Optional<ShardBackupMetadata.BackedFile> opBackedFile = oldBackupPoint.getFile(fileName);
-      Checksum originalFileCS = backupRepo.checksum(dir, fileName);
-
-      if (opBackedFile.isPresent()) {
-        ShardBackupMetadata.BackedFile backedFile = opBackedFile.get();
-        Checksum existedFileCS = backedFile.fileChecksum;
-        if (existedFileCS.equals(originalFileCS)) {
-          currentBackupPoint.addBackedFile(opBackedFile.get());
-          backupStats.skippedUploadingFile(existedFileCS);
-          continue;
-        }
-      }
-
-      String backedFileName = UUID.randomUUID().toString();
-      backupRepo.copyIndexFileFrom(dir, fileName, indexDir, backedFileName);
+      // Capture variable for lambda
+      final String fileNameFinal = fileName;
+
+      Runnable uploadTask =
+          () -> {
+            try {
+              // Calculate checksum and check if file already exists in previous backup
+              Optional<ShardBackupMetadata.BackedFile> opBackedFile =
+                  oldBackupPoint.getFile(fileNameFinal);
+              Checksum originalFileCS = backupRepo.checksum(dir, fileNameFinal);
+
+              if (opBackedFile.isPresent()) {
+                ShardBackupMetadata.BackedFile backedFile = opBackedFile.get();
+                Checksum existedFileCS = backedFile.fileChecksum;
+                if (existedFileCS.equals(originalFileCS)) {
+                  synchronized (currentBackupPoint) {
+                    currentBackupPoint.addBackedFile(opBackedFile.get());
+                  }
+                  backupStats.skippedUploadingFile(existedFileCS);
+                  return;
+                }
+              }
+
+              // File doesn't exist or has changed - upload it
+              String backedFileName = UUID.randomUUID().toString();
+              backupRepo.copyIndexFileFrom(dir, fileNameFinal, indexDir, backedFileName);
+
+              synchronized (currentBackupPoint) {
+                currentBackupPoint.addBackedFile(backedFileName, fileNameFinal, originalFileCS);
+              }
+              backupStats.uploadedFile(originalFileCS);
+            } catch (IOException e) {
+              throw new RuntimeException("Failed to process file: " + fileNameFinal, e);
+            }
+          };
+
+      uploadFutures.add(executor.submit(uploadTask));
+    }
 
-      currentBackupPoint.addBackedFile(backedFileName, fileName, originalFileCS);
-      backupStats.uploadedFile(originalFileCS);
+    try {
+      for (Future<?> future : uploadFutures) {
+        future.get();
+      }
+    } catch (ExecutionException e) {
+      uploadFutures.forEach(f -> f.cancel(true));
+      Throwable cause = e.getCause();
+      switch (cause) {
+        case Error err -> throw err;
+        case IOException ioe -> throw ioe;
+        case RuntimeException re -> throw re;
+        default -> throw new SolrException(
+            SolrException.ErrorCode.UNKNOWN, "Error during parallel backup upload", cause);
+      }
+    } catch (InterruptedException e) {
+      uploadFutures.forEach(f -> f.cancel(true));
+      throw new SolrException(SolrException.ErrorCode.UNKNOWN, "Backup interrupted", e);
     }
 
     currentBackupPoint.store(backupRepo, incBackupFiles.getShardBackupMetadataDir(), shardBackupId);
@@ -217,29 +288,29 @@ public class IncrementalShardBackup {
   }
 
   private static class BackupStats {
-    private int fileCount;
-    private int uploadedFileCount;
-    private long indexSize;
-    private long totalUploadedBytes;
+    private final AtomicInteger fileCount = new AtomicInteger();
+    private final AtomicInteger uploadedFileCount = new AtomicInteger();
+    private final AtomicLong indexSize = new AtomicLong();
+    private final AtomicLong totalUploadedBytes = new AtomicLong();
 
     public void uploadedFile(Checksum file) {
-      fileCount++;
-      uploadedFileCount++;
-      indexSize += file.size;
-      totalUploadedBytes += file.size;
+      fileCount.incrementAndGet();
+      uploadedFileCount.incrementAndGet();
+      indexSize.addAndGet(file.size);
+      totalUploadedBytes.addAndGet(file.size);
     }
 
     public void skippedUploadingFile(Checksum existedFile) {
-      fileCount++;
-      indexSize += existedFile.size;
+      fileCount.incrementAndGet();
+      indexSize.addAndGet(existedFile.size);
     }
 
     public double getIndexSizeMB() {
-      return Precision.round(indexSize / (1024.0 * 1024), 3);
+      return Precision.round(indexSize.get() / (1024.0 * 1024), 3);
     }
 
     public double getTotalUploadedMB() {
-      return Precision.round(totalUploadedBytes / (1024.0 * 1024), 3);
+      return Precision.round(totalUploadedBytes.get() / (1024.0 * 1024), 3);
     }
   }
 
diff --git a/solr/core/src/java/org/apache/solr/handler/RestoreCore.java b/solr/core/src/java/org/apache/solr/handler/RestoreCore.java
index 7f4abc18ffd..244a50e7827 100644
--- a/solr/core/src/java/org/apache/solr/handler/RestoreCore.java
+++ b/solr/core/src/java/org/apache/solr/handler/RestoreCore.java
@@ -21,19 +21,26 @@ import java.lang.invoke.MethodHandles;
 import java.lang.reflect.Array;
 import java.net.URI;
 import java.text.SimpleDateFormat;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Date;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Locale;
 import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
 import org.apache.lucene.codecs.CodecUtil;
 import org.apache.lucene.store.Directory;
 import org.apache.lucene.store.IOContext;
 import org.apache.lucene.store.IndexInput;
 import org.apache.solr.common.SolrException;
+import org.apache.solr.common.util.EnvUtils;
+import org.apache.solr.common.util.ExecutorUtil;
+import org.apache.solr.common.util.SolrNamedThreadFactory;
 import org.apache.solr.core.DirectoryFactory;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.core.backup.BackupFilePaths;
@@ -48,6 +55,14 @@ public class RestoreCore implements Callable<Boolean> {
 
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
+  /**
+   * Maximum number of files to download in parallel during restore. Can be configured via the
+   * system property {@code solr.backup.maxparalleldownloads} or environment variable {@code
+   * SOLR_BACKUP_MAXPARALLELDOWNLOADS}.
+   */
+  private static final int MAX_PARALLEL_DOWNLOADS =
+      EnvUtils.getPropertyAsInteger("solr.backup.maxparalleldownloads", 1);
+
   private final SolrCore core;
   private RestoreRepository repository;
 
@@ -107,35 +122,85 @@ public class RestoreCore implements Callable<Boolean> {
                   DirectoryFactory.DirContext.DEFAULT,
                   core.getSolrConfig().indexConfig.lockType);
       Set<String> indexDirFiles = new HashSet<>(Arrays.asList(indexDir.listAll()));
+
+      // Capture directories as final for lambda access
+      final Directory finalIndexDir = indexDir;
+      final Directory finalRestoreIndexDir = restoreIndexDir;
+
+      List<Future<?>> downloadFutures = new ArrayList<>();
+
+      var executor =
+          core.getCoreContainer()
+              .getObjectCache()
+              .computeIfAbsent(
+                  "RestoreDownloadExecutor",
+                  ExecutorService.class,
+                  s ->
+                      ExecutorUtil.newMDCAwareCachedThreadPool(
+                          MAX_PARALLEL_DOWNLOADS,
+                          Integer.MAX_VALUE,
+                          new SolrNamedThreadFactory("RestoreDownloadExecutor")));
+
       // Move all files from backupDir to restoreIndexDir
       for (String filename : repository.listAllFiles()) {
         checkInterrupted();
-        try {
-          if (indexDirFiles.contains(filename)) {
-            Checksum cs = repository.checksum(filename);
-            IndexFetcher.CompareResult compareResult;
-            if (cs == null) {
-              compareResult = new IndexFetcher.CompareResult();
-              compareResult.equal = false;
-            } else {
-              compareResult = IndexFetcher.compareFile(indexDir, filename, cs.size, cs.checksum);
-            }
-            if (!compareResult.equal
-                || (IndexFetcher.filesToAlwaysDownloadIfNoChecksums(
-                    filename, cs.size, compareResult))) {
-              repository.repoCopy(filename, restoreIndexDir);
-            } else {
-              // prefer local copy
-              repository.localCopy(indexDir, filename, restoreIndexDir);
-            }
-          } else {
-            repository.repoCopy(filename, restoreIndexDir);
-          }
-        } catch (Exception e) {
-          log.warn("Exception while restoring the backup index ", e);
-          throw new SolrException(
-              SolrException.ErrorCode.UNKNOWN, "Exception while restoring the backup index", e);
+
+        // Capture variables for lambda
+        final String filenameFinal = filename;
+        final boolean fileExistsLocally = indexDirFiles.contains(filename);
+
+        Runnable downloadTask =
+            () -> {
+              try {
+                if (fileExistsLocally) {
+                  Checksum cs = repository.checksum(filenameFinal);
+                  IndexFetcher.CompareResult compareResult;
+                  if (cs == null) {
+                    compareResult = new IndexFetcher.CompareResult();
+                    compareResult.equal = false;
+                  } else {
+                    compareResult =
+                        IndexFetcher.compareFile(
+                            finalIndexDir, filenameFinal, cs.size, cs.checksum);
+                  }
+                  if (!compareResult.equal
+                      || (IndexFetcher.filesToAlwaysDownloadIfNoChecksums(
+                          filenameFinal, cs.size, compareResult))) {
+                    repository.repoCopy(filenameFinal, finalRestoreIndexDir);
+                  } else {
+                    // prefer local copy
+                    repository.localCopy(finalIndexDir, filenameFinal, finalRestoreIndexDir);
+                  }
+                } else {
+                  repository.repoCopy(filenameFinal, finalRestoreIndexDir);
+                }
+              } catch (Exception e) {
+                log.warn("Exception while restoring the backup index ", e);
+                throw new RuntimeException(
+                    "Exception while restoring the backup index for file: " + filenameFinal, e);
+              }
+            };
+
+        downloadFutures.add(executor.submit(downloadTask));
+      }
+
+      try {
+        for (Future<?> future : downloadFutures) {
+          future.get();
+        }
+      } catch (ExecutionException e) {
+        downloadFutures.forEach(f -> f.cancel(true));
+        Throwable cause = e.getCause();
+        switch (cause) {
+          case Error err -> throw err;
+          case IOException ioe -> throw ioe;
+          case RuntimeException re -> throw re;
+          default -> throw new SolrException(
+              SolrException.ErrorCode.UNKNOWN, "Error during parallel restore download", cause);
         }
+      } catch (InterruptedException e) {
+        downloadFutures.forEach(f -> f.cancel(true));
+        throw new SolrException(SolrException.ErrorCode.UNKNOWN, "Restore interrupted", e);
       }
       log.debug("Switching directories");
       core.modifyIndexProps(restoreIndexName);
diff --git a/solr/modules/gcs-repository/src/test/org/apache/solr/gcs/GCSIncrementalBackupTest.java b/solr/modules/gcs-repository/src/test/org/apache/solr/gcs/GCSIncrementalBackupTest.java
index d955da11e1e..62b9b8e7717 100644
--- a/solr/modules/gcs-repository/src/test/org/apache/solr/gcs/GCSIncrementalBackupTest.java
+++ b/solr/modules/gcs-repository/src/test/org/apache/solr/gcs/GCSIncrementalBackupTest.java
@@ -76,6 +76,9 @@ public class GCSIncrementalBackupTest extends AbstractIncrementalBackupTest {
 
   @BeforeClass
   public static void setupClass() throws Exception {
+    // Enable parallel backup/restore for cloud storage tests
+    System.setProperty("solr.backup.maxparalleluploads", "2");
+    System.setProperty("solr.backup.maxparalleldownloads", "2");
 
     configureCluster(NUM_NODES) // nodes
         .addConfig("conf1", getFile("conf/solrconfig.xml").getParent())
diff --git a/solr/modules/s3-repository/src/test/org/apache/solr/s3/S3IncrementalBackupTest.java b/solr/modules/s3-repository/src/test/org/apache/solr/s3/S3IncrementalBackupTest.java
index c35dbc17ab0..8cebe4a80a5 100644
--- a/solr/modules/s3-repository/src/test/org/apache/solr/s3/S3IncrementalBackupTest.java
+++ b/solr/modules/s3-repository/src/test/org/apache/solr/s3/S3IncrementalBackupTest.java
@@ -97,6 +97,9 @@ public class S3IncrementalBackupTest extends AbstractIncrementalBackupTest {
   public static void setupClass() throws Exception {
     System.setProperty("aws.accessKeyId", "foo");
     System.setProperty("aws.secretAccessKey", "bar");
+    // Enable parallel backup/restore for cloud storage tests
+    System.setProperty("solr.backup.maxparalleluploads", "2");
+    System.setProperty("solr.backup.maxparalleldownloads", "2");
     String retryMode;
     switch (random().nextInt(3)) {
       case 0:
diff --git a/solr/solr-ref-guide/modules/deployment-guide/pages/backup-restore.adoc b/solr/solr-ref-guide/modules/deployment-guide/pages/backup-restore.adoc
index 12297153e9c..7e9cdd35bf2 100644
--- a/solr/solr-ref-guide/modules/deployment-guide/pages/backup-restore.adoc
+++ b/solr/solr-ref-guide/modules/deployment-guide/pages/backup-restore.adoc
@@ -396,6 +396,38 @@ Any children under the `<repository>` tag are passed as additional configuration
 
 Information on each of the repository implementations provided with Solr is provided below.
 
+=== Parallel File Transfers
+
+Backup and restore operations can transfer multiple index files in parallel to improve throughput, especially when using cloud storage repositories like S3 or GCS where latency is higher.
+The parallelism is controlled via system properties or environment variables:
+
+`solr.backup.maxparalleluploads`::
++
+[%autowidth,frame=none]
+|===
+|Optional |Default: `1`
+|===
++
+Maximum number of index files to upload in parallel during backup operations.
+Can also be set via the `SOLR_BACKUP_MAXPARALLELUPLOADS` environment variable.
+
+`solr.backup.maxparalleldownloads`::
++
+[%autowidth,frame=none]
+|===
+|Optional |Default: `1`
+|===
++
+Maximum number of index files to download in parallel during restore operations.
+Can also be set via the `SOLR_BACKUP_MAXPARALLELDOWNLOADS` environment variable.
+
+TIP: These are two independent thread pools — one for backup uploads and one for restore downloads.
+Increasing either value can significantly improve throughput when using cloud storage (S3, GCS), but too high a value will increase IOPS and bandwidth pressure on your cluster.
+Start small and increase based on observed throughput and available resources.
+The configured limit applies across all concurrent backup or restore operations on the node.
+
+=== Checksum Verification
+
 By default, all the repository implementations verify the integrity of the index files before they are copied to the destination. However, it is possible to disable this integrity check by setting the optional configuration property `verifyChecksum`.
 
 `verifyChecksum`::
diff --git a/solr/solrj/src/java/org/apache/solr/common/util/ObjectCache.java b/solr/solrj/src/java/org/apache/solr/common/util/ObjectCache.java
index 2bbb6529c0f..096998b76c8 100644
--- a/solr/solrj/src/java/org/apache/solr/common/util/ObjectCache.java
+++ b/solr/solrj/src/java/org/apache/solr/common/util/ObjectCache.java
@@ -19,6 +19,7 @@ package org.apache.solr.common.util;
 import java.io.Closeable;
 import java.io.IOException;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Function;
 import org.apache.solr.common.SolrCloseable;
@@ -91,8 +92,10 @@ public class ObjectCache extends MapBackedCache<String, Object> implements SolrC
       // owns this ObjectCache, which is useful for plugins to register objects
       // which should be closed before being garbage-collected.
       for (Object value : map.values()) {
-        if (value instanceof Closeable) {
-          ((Closeable) value).close();
+        if (value instanceof Closeable closeable) {
+          closeable.close();
+        } else if (value instanceof ExecutorService executor) {
+          ExecutorUtil.shutdownAndAwaitTermination(executor);
         }
       }
       map.clear();


(solr) 01/03: New SolrJ CollectionScopedSolrClient (#4418)

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

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

commit 84ebc0a2777ec2de83088de8f1ae48cc3883afb5
Author: David Smiley <ds...@apache.org>
AuthorDate: Sun May 17 11:27:51 2026 -0400

    New SolrJ CollectionScopedSolrClient (#4418)
    
    (cherry picked from commit 0c8dcb806d6b4c1b590d04cb8e3b5cea3f5edba7)
---
 .../unreleased/CollectionScopedSolrClient.yml      |  7 +++
 .../solrj/impl/CollectionScopedSolrClient.java     | 61 ++++++++++++++++++++++
 2 files changed, 68 insertions(+)

diff --git a/changelog/unreleased/CollectionScopedSolrClient.yml b/changelog/unreleased/CollectionScopedSolrClient.yml
new file mode 100644
index 00000000000..9bd6b677d82
--- /dev/null
+++ b/changelog/unreleased/CollectionScopedSolrClient.yml
@@ -0,0 +1,7 @@
+title: New SolrJ CollectionScopedSolrClient
+type: added
+authors:
+  - name: David Smiley
+links:
+  - name: PR#4418
+    url: https://github.com/apache/solr/pull/4418
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/CollectionScopedSolrClient.java b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/CollectionScopedSolrClient.java
new file mode 100644
index 00000000000..5b0b4ee86ce
--- /dev/null
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/CollectionScopedSolrClient.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.client.solrj.impl;
+
+import java.io.IOException;
+import java.util.Objects;
+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.common.util.NamedList;
+
+/**
+ * This SolrClient delegates to a backing client with a specified collection. Simple attempts to
+ * refer to another collection will fail. DISCLAIMER: this isn't a security mechanism.
+ */
+public class CollectionScopedSolrClient extends SolrClient {
+
+  private final SolrClient delegate;
+
+  public CollectionScopedSolrClient(SolrClient delegate, String defaultCollection) {
+    this.delegate = Objects.requireNonNull(delegate);
+    this.defaultCollection = Objects.requireNonNull(defaultCollection);
+  }
+
+  @Override
+  public NamedList<Object> request(SolrRequest<?> request, String collection)
+      throws SolrServerException, IOException {
+    if (collection == null) {
+      collection = defaultCollection;
+    } else if (!collection.equals(defaultCollection)) {
+      throw new IllegalArgumentException(
+          "Restricted to default collection " + defaultCollection + " but was given " + collection);
+    }
+    return delegate.request(request, collection);
+  }
+
+  @Override
+  public String toString() {
+    return delegate + "/" + defaultCollection;
+  }
+
+  @Override
+  public void close() throws IOException {
+    delegate.close();
+  }
+}