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


The following commit(s) were added to refs/heads/branch_10x by this push:
     new 255638e7474 SOLR-18351 SolrJ: ContentStream more deprecation and 
replacements (#5235)
255638e7474 is described below

commit 255638e7474ee2316c544b0e21350f96b9c213e5
Author: David Smiley <[email protected]>
AuthorDate: Thu Oct 29 15:25:26 2026 -0400

    SOLR-18351 SolrJ: ContentStream more deprecation and replacements (#5235)
    
    SolrJ clients now write request content via RequestWriter.ContentWriter; 
ContentStreams from
      the long-deprecated SolrRequest.getContentStreams and 
RequestWriter.getContentStreams are still
      honored. Deprecated ContentStreamUpdateRequest in favor of the new 
ContentWriterUpdateRequest,
      ConfigSetAdminRequest.Upload.setUploadStream in favor of 
setUploadContent, and
      ClientUtils.toContentStreams. Upload.setUploadFile no longer gunzips .gz 
files or guesses a
      missing content type, so pass the content type explicitly.
    
    Co-authored-by: Serhiy Bzhezytskyy <[email protected]>
    Co-authored-by: Claude Sonnet 5.5 <[email protected]>
    Co-authored-by: Nick Shanin <[email protected]>
---
 .../SOLR-18351-10x-contentstream-deprecations.yml  |  15 ++
 solr/core/src/java/org/apache/solr/api/ApiBag.java |  42 ++-
 .../core/src/java/org/apache/solr/cli/SolrCLI.java |   9 +-
 .../client/solrj/embedded/EmbeddedSolrServer.java  |  39 ++-
 .../org/apache/solr/handler/SolrConfigHandler.java |   4 +-
 .../solr/handler/admin/SecurityConfHandler.java    |   3 +-
 .../TestEmbeddedContentStreamCompatibility.java    | 133 ++++++++++
 .../transform/TestSubQueryTransformerDistrib.java  |   7 +-
 .../apache/solr/update/MaxSizeAutoCommitTest.java  |   4 +-
 .../processor/TolerantUpdateProcessorTest.java     |   8 +-
 .../src/test/org/apache/solr/util/TestUtils.java   |   3 +-
 .../solr/crossdc/common/MirroredSolrRequest.java   |  39 ++-
 .../common/MirroredSolrRequestSerializer.java      |   4 +-
 .../handler/MirroringConfigSetsHandlerTest.java    |   4 +-
 .../examples/SolrCellRefGuideExamplesTest.java     |   6 +-
 .../indexing-guide/pages/indexing-with-tika.adoc   |   4 +-
 .../query-guide/examples/JsonRequestApiTest.java   |   4 +-
 .../client/solrj/jetty/HttpJettySolrClient.java    | 141 +++++-----
 .../solrj/jetty/HttpJettySolrClientTest.java       | 101 ++++++++
 .../TestLegacyContentStreamCompatibility.java      | 283 +++++++++++++++++++++
 .../org/apache/solr/client/solrj/SolrRequest.java  |  16 +-
 .../solr/client/solrj/impl/HttpJdkSolrClient.java  |  22 +-
 .../solr/client/solrj/impl/HttpSolrClient.java     |  18 +-
 .../solrj/request/ConfigSetAdminRequest.java       |  64 +++--
 .../solrj/request/ContentStreamUpdateRequest.java  |  25 +-
 .../solrj/request/ContentWriterUpdateRequest.java  |  83 ++++++
 .../client/solrj/request/JavaBinRequestWriter.java |   4 +-
 .../solr/client/solrj/request/RequestWriter.java   |  87 ++++++-
 .../client/solrj/request/XMLRequestWriter.java     |   1 +
 .../apache/solr/client/solrj/util/ClientUtils.java |   8 +-
 .../apache/solr/common/util/CommandOperation.java  |  42 ---
 .../solrj/impl/CloudSolrClientCacheTest.java       |   7 -
 .../solrj/request/TestConfigSetAdminRequest.java   |  10 +-
 ...DirectJsonQueryRequestFacetingEmbeddedTest.java |   4 +-
 ...ectJsonQueryRequestFacetingIntegrationTest.java |   4 +-
 .../JsonQueryRequestFacetingIntegrationTest.java   |   4 +-
 .../json/JsonQueryRequestIntegrationTest.java      |   4 +-
 .../src/java/org/apache/solr/util/TestHarness.java |   7 +-
 38 files changed, 995 insertions(+), 268 deletions(-)

diff --git a/changelog/unreleased/SOLR-18351-10x-contentstream-deprecations.yml 
b/changelog/unreleased/SOLR-18351-10x-contentstream-deprecations.yml
new file mode 100644
index 00000000000..c2325d463c7
--- /dev/null
+++ b/changelog/unreleased/SOLR-18351-10x-contentstream-deprecations.yml
@@ -0,0 +1,15 @@
+title: >
+  SolrJ clients now write request content via RequestWriter.ContentWriter; 
ContentStreams from
+  the long-deprecated SolrRequest.getContentStreams and 
RequestWriter.getContentStreams are still
+  honored. Deprecated ContentStreamUpdateRequest in favor of the new 
ContentWriterUpdateRequest,
+  ConfigSetAdminRequest.Upload.setUploadStream in favor of setUploadContent, 
and
+  ClientUtils.toContentStreams. Upload.setUploadFile no longer gunzips .gz 
files or guesses a
+  missing content type, so pass the content type explicitly.
+type: deprecated
+authors:
+  - name: Serhiy Bzhezytskyy
+  - name: Nick Shanin
+  - name: David Smiley
+links:
+  - name: SOLR-18351
+    url: https://issues.apache.org/jira/browse/SOLR-18351
diff --git a/solr/core/src/java/org/apache/solr/api/ApiBag.java 
b/solr/core/src/java/org/apache/solr/api/ApiBag.java
index 935881369d2..45e02e2a2c9 100644
--- a/solr/core/src/java/org/apache/solr/api/ApiBag.java
+++ b/solr/core/src/java/org/apache/solr/api/ApiBag.java
@@ -412,11 +412,51 @@ public class ApiBag {
     }
   }
 
+  public static List<CommandOperation> readCommands(
+      Iterable<ContentStream> streams, @SuppressWarnings({"rawtypes"}) 
NamedList resp)
+      throws IOException {
+    return readCommands(streams, resp, Set.of());
+  }
+
+  /**
+   * Read commands from request streams
+   *
+   * @param streams the streams
+   * @param resp solr query response
+   * @param singletonCommands , commands that cannot be repeated
+   * @return parsed list of commands
+   * @throws IOException if there is an error while parsing the stream
+   */
+  @SuppressWarnings({"deprecation", "unchecked"})
+  public static List<CommandOperation> readCommands(
+      Iterable<ContentStream> streams,
+      @SuppressWarnings({"rawtypes"}) NamedList resp,
+      Set<String> singletonCommands)
+      throws IOException {
+    if (streams == null) {
+      throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "missing 
content stream");
+    }
+    ArrayList<CommandOperation> ops = new ArrayList<>();
+    for (ContentStream stream : streams) {
+      if ("application/javabin".equals(stream.getContentType())) {
+        ops.addAll(CommandOperation.parse(stream.getStream(), 
singletonCommands));
+      } else {
+        ops.addAll(CommandOperation.parse(stream.getReader(), 
singletonCommands));
+      }
+    }
+    List<Map<String, Object>> errList = CommandOperation.captureErrors(ops);
+    if (!errList.isEmpty()) {
+      resp.add(CommandOperation.ERR_MSGS, errList);
+      return null;
+    }
+    return ops;
+  }
+
   public static List<CommandOperation> getCommandOperations(
       ContentStream stream, Map<String, JsonSchemaValidator> validators, 
boolean validate) {
     List<CommandOperation> parsedCommands = null;
     try {
-      parsedCommands = CommandOperation.readCommands(Set.of(stream), new 
NamedList<>());
+      parsedCommands = readCommands(Set.of(stream), new NamedList<>());
     } catch (IOException e) {
       throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Unable to 
parse commands", e);
     }
diff --git a/solr/core/src/java/org/apache/solr/cli/SolrCLI.java 
b/solr/core/src/java/org/apache/solr/cli/SolrCLI.java
index cf9d9d4b0e7..555b30bf7c8 100755
--- a/solr/core/src/java/org/apache/solr/cli/SolrCLI.java
+++ b/solr/core/src/java/org/apache/solr/cli/SolrCLI.java
@@ -44,8 +44,7 @@ import org.apache.commons.cli.help.TableDefinition;
 import org.apache.commons.cli.help.TextHelpAppendable;
 import org.apache.solr.client.api.util.SolrVersion;
 import org.apache.solr.client.solrj.SolrClient;
-import org.apache.solr.client.solrj.request.ContentStreamUpdateRequest;
-import org.apache.solr.common.util.ContentStreamBase;
+import org.apache.solr.client.solrj.request.ContentWriterUpdateRequest;
 import org.apache.solr.common.util.EnvUtils;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.SuppressForbidden;
@@ -604,10 +603,8 @@ public class SolrCLI implements CLIO, 
java.util.concurrent.Callable<Integer> {
 
   public static NamedList<Object> postJsonToSolr(
       SolrClient solrClient, String updatePath, String jsonBody) throws 
Exception {
-    ContentStreamBase.StringStream contentStream = new 
ContentStreamBase.StringStream(jsonBody);
-    contentStream.setContentType(JSON_CONTENT_TYPE);
-    ContentStreamUpdateRequest req = new 
ContentStreamUpdateRequest(updatePath);
-    req.addContentStream(contentStream);
+    ContentWriterUpdateRequest req = new 
ContentWriterUpdateRequest(updatePath);
+    req.addContentWithType(jsonBody, JSON_CONTENT_TYPE);
     return solrClient.request(req);
   }
 
diff --git 
a/solr/core/src/java/org/apache/solr/client/solrj/embedded/EmbeddedSolrServer.java
 
b/solr/core/src/java/org/apache/solr/client/solrj/embedded/EmbeddedSolrServer.java
index e43bbfb5cdc..ddf16103e6e 100644
--- 
a/solr/core/src/java/org/apache/solr/client/solrj/embedded/EmbeddedSolrServer.java
+++ 
b/solr/core/src/java/org/apache/solr/client/solrj/embedded/EmbeddedSolrServer.java
@@ -309,14 +309,27 @@ public class EmbeddedSolrServer extends SolrClient {
   }
 
   /** A list of streams, non-null. */
+  @SuppressWarnings("deprecation")
   private List<ContentStream> getContentStreams(SolrRequest<?> request) throws 
IOException {
     if (request.getMethod() == SolrRequest.METHOD.GET) return List.of();
     if (request instanceof ContentStreamUpdateRequest csur) {
-      final Collection<ContentStream> cs = csur.getContentStreams();
-      if (cs != null) return new ArrayList<>(cs);
+      final Collection<ContentStream> streams = csur.getContentStreams();
+      if (streams != null) return new ArrayList<>(streams);
     }
 
     final RequestWriter.ContentWriter contentWriter = 
request.getContentWriter(null);
+    if (contentWriter == null) {
+      final Collection<ContentStream> streams = request.getContentStreams();
+      if (streams != null) return new ArrayList<>(streams);
+    }
+
+    if (contentWriter instanceof RequestWriter.MultipartContentWriter 
multipartWriter) {
+      List<ContentStream> parts = new ArrayList<>();
+      for (RequestWriter.NamedPart part : multipartWriter.getParts()) {
+        parts.add(bufferedContentStream(part.name, part.writer));
+      }
+      return parts;
+    }
 
     String cType;
     final Utils.BAOS baos = new Utils.BAOS();
@@ -349,6 +362,28 @@ public class EmbeddedSolrServer extends SolrClient {
     return List.of();
   }
 
+  private static ContentStream bufferedContentStream(
+      String name, RequestWriter.ContentWriter writer) throws IOException {
+    final Utils.BAOS baos = new Utils.BAOS();
+    writer.write(baos);
+    final byte[] buf = baos.toByteArray();
+    return new ContentStreamBase() {
+      {
+        setName(name);
+      }
+
+      @Override
+      public InputStream getStream() throws IOException {
+        return new ByteArrayInputStream(buf);
+      }
+
+      @Override
+      public String getContentType() {
+        return writer.getContentType();
+      }
+    };
+  }
+
   private JavaBinCodec createJavaBinCodec(
       final StreamingResponseCallback callback,
       final JavaBinResponseWriter.Resolver resolver,
diff --git a/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java 
b/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java
index 9e43499fbe3..d44e7fe9c53 100644
--- a/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/SolrConfigHandler.java
@@ -49,6 +49,7 @@ import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
 import org.apache.solr.api.AnnotatedApi;
 import org.apache.solr.api.Api;
+import org.apache.solr.api.ApiBag;
 import org.apache.solr.api.JerseyResource;
 import org.apache.solr.client.solrj.SolrRequest;
 import org.apache.solr.client.solrj.SolrResponse;
@@ -245,8 +246,7 @@ public class SolrConfigHandler extends RequestHandlerBase
     }
 
     private void handlePOST() throws IOException {
-      List<CommandOperation> ops =
-          CommandOperation.readCommands(req.getContentStreams(), 
resp.getValues());
+      List<CommandOperation> ops = 
ApiBag.readCommands(req.getContentStreams(), resp.getValues());
       if (ops == null) return;
       try {
         for (; ; ) {
diff --git 
a/solr/core/src/java/org/apache/solr/handler/admin/SecurityConfHandler.java 
b/solr/core/src/java/org/apache/solr/handler/admin/SecurityConfHandler.java
index c330b6f08b6..353e096d294 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/SecurityConfHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/SecurityConfHandler.java
@@ -116,8 +116,7 @@ public abstract class SecurityConfHandler extends 
RequestHandlerBase
     if (req.getContentStreams() == null) {
       throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "No 
contentStream");
     }
-    List<CommandOperation> ops =
-        CommandOperation.readCommands(req.getContentStreams(), 
rsp.getValues());
+    List<CommandOperation> ops = ApiBag.readCommands(req.getContentStreams(), 
rsp.getValues());
     if (ops == null) {
       throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "No 
commands");
     }
diff --git 
a/solr/core/src/test/org/apache/solr/client/solrj/embedded/TestEmbeddedContentStreamCompatibility.java
 
b/solr/core/src/test/org/apache/solr/client/solrj/embedded/TestEmbeddedContentStreamCompatibility.java
new file mode 100644
index 00000000000..1002edb43d8
--- /dev/null
+++ 
b/solr/core/src/test/org/apache/solr/client/solrj/embedded/TestEmbeddedContentStreamCompatibility.java
@@ -0,0 +1,133 @@
+/*
+ * 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.embedded;
+
+import java.util.List;
+import org.apache.solr.SolrTestCase;
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.client.solrj.SolrClient;
+import org.apache.solr.client.solrj.SolrRequest;
+import org.apache.solr.client.solrj.request.ContentStreamUpdateRequest;
+import org.apache.solr.client.solrj.request.GenericSolrRequest;
+import org.apache.solr.client.solrj.request.RequestWriter;
+import org.apache.solr.client.solrj.request.SolrQuery;
+import org.apache.solr.common.SolrDocumentList;
+import org.apache.solr.common.util.ContentStream;
+import org.apache.solr.common.util.ContentStreamBase;
+import org.apache.solr.util.EmbeddedSolrServerTestRule;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+/** Checks that legacy embedded updates retain every stream, including unnamed 
strings. */
+@SuppressWarnings("deprecation")
+public class TestEmbeddedContentStreamCompatibility extends SolrTestCase {
+
+  @ClassRule
+  public static final EmbeddedSolrServerTestRule solrTestRule = new 
EmbeddedSolrServerTestRule();
+
+  @BeforeClass
+  public static void beforeClass() throws Exception {
+    solrTestRule.startSolr(SolrTestCaseJ4.TEST_HOME());
+    SolrTestCaseJ4.newRandomConfig();
+    
solrTestRule.newCollection().withConfigSet(SolrTestCaseJ4.TEST_COLL1_CONF()).create();
+  }
+
+  @Test
+  public void testUnnamedContentStreamsRetainAllDocuments() throws Exception {
+    assertAllDocuments(false);
+  }
+
+  @Test
+  public void testNamedContentStreamsRetainAllDocuments() throws Exception {
+    assertAllDocuments(true);
+  }
+
+  @Test
+  public void testGenericLegacyStreamsRetainAllDocuments() throws Exception {
+    GenericSolrRequest request =
+        new GenericSolrRequest(
+            SolrRequest.METHOD.POST, "/update", 
SolrRequest.SolrRequestType.UPDATE) {
+          @Override
+          public List<ContentStream> getContentStreams() {
+            return List.of(
+                new ContentStreamBase.StringStream(document("1"), 
"application/xml"),
+                new ContentStreamBase.StringStream(document("2"), 
"application/xml"));
+          }
+        };
+    assertAllDocuments(request);
+  }
+
+  @Test
+  public void testMultipartContentWriterTakesPrecedenceOverLegacyStreams() 
throws Exception {
+    GenericSolrRequest request =
+        new GenericSolrRequest(
+            SolrRequest.METHOD.POST, "/update", 
SolrRequest.SolrRequestType.UPDATE) {
+          @Override
+          public List<ContentStream> getContentStreams() {
+            throw new AssertionError("An explicit writer must take precedence 
over legacy streams");
+          }
+        };
+    request.setContentWriter(
+        (RequestWriter.MultipartContentWriter)
+            () ->
+                List.of(
+                    new RequestWriter.NamedPart(
+                        "first.xml",
+                        new RequestWriter.StringPayloadContentWriter(
+                            document("1"), "application/xml")),
+                    new RequestWriter.NamedPart(
+                        "second.xml",
+                        new RequestWriter.StringPayloadContentWriter(
+                            document("2"), "application/xml"))));
+    assertAllDocuments(request);
+  }
+
+  private void assertAllDocuments(boolean named) throws Exception {
+    ContentStreamUpdateRequest request = new 
ContentStreamUpdateRequest("/update");
+    for (String id : List.of("1", "2")) {
+      ContentStreamBase stream =
+          new ContentStreamBase.StringStream(document(id), "application/xml");
+      if (named) {
+        stream.setName(id + ".xml");
+      } else {
+        assertNull(stream.getName());
+      }
+      request.addContentStream(stream);
+    }
+    assertAllDocuments(request);
+  }
+
+  private void assertAllDocuments(SolrRequest<?> request) throws Exception {
+    SolrClient client = solrTestRule.getSolrClient();
+    client.deleteByQuery("*:*");
+    client.request(request);
+    client.commit();
+    SolrQuery query = new SolrQuery("*:*");
+    query.setFields("id");
+    query.setSort("id", SolrQuery.ORDER.asc);
+    SolrDocumentList documents = client.query(query).getResults();
+    assertEquals("Both content streams must be indexed", 2L, 
documents.getNumFound());
+    assertEquals(
+        List.of("1", "2"),
+        documents.stream().map(document -> 
document.getFieldValue("id")).toList());
+  }
+
+  private static String document(String id) {
+    return "<add><doc><field name=\"id\">" + id + "</field></doc></add>";
+  }
+}
diff --git 
a/solr/core/src/test/org/apache/solr/response/transform/TestSubQueryTransformerDistrib.java
 
b/solr/core/src/test/org/apache/solr/response/transform/TestSubQueryTransformerDistrib.java
index 108196031f7..572b081651a 100644
--- 
a/solr/core/src/test/org/apache/solr/response/transform/TestSubQueryTransformerDistrib.java
+++ 
b/solr/core/src/test/org/apache/solr/response/transform/TestSubQueryTransformerDistrib.java
@@ -39,7 +39,7 @@ import org.apache.solr.client.solrj.SolrResponse;
 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.ContentStreamUpdateRequest;
+import org.apache.solr.client.solrj.request.ContentWriterUpdateRequest;
 import org.apache.solr.client.solrj.request.QueryRequest;
 import org.apache.solr.client.solrj.response.QueryResponse;
 import org.apache.solr.cloud.AbstractFullDistribZkTestBase;
@@ -48,7 +48,6 @@ import org.apache.solr.common.SolrDocument;
 import org.apache.solr.common.SolrDocumentList;
 import org.apache.solr.common.cloud.ZkStateReader;
 import org.apache.solr.common.params.ModifiableSolrParams;
-import org.apache.solr.common.util.ContentStreamBase;
 import org.apache.solr.common.util.Utils;
 import org.apache.solr.security.BasicAuthPlugin;
 import org.apache.solr.security.RuleBasedAuthorizationPlugin;
@@ -413,8 +412,8 @@ public class TestSubQueryTransformerDistrib extends 
SolrCloudTestCase {
         }
         upd.append("</update>");
 
-        ContentStreamUpdateRequest req = withBasicAuth(new 
ContentStreamUpdateRequest("/update"));
-        req.addContentStream(new 
ContentStreamBase.StringStream(upd.toString(), "text/xml"));
+        ContentWriterUpdateRequest req = withBasicAuth(new 
ContentWriterUpdateRequest("/update"));
+        req.addContentWithType(upd.toString(), "text/xml");
         cluster.getSolrClient().request(req, collection);
         upd.setLength("<update>".length());
       }
diff --git 
a/solr/core/src/test/org/apache/solr/update/MaxSizeAutoCommitTest.java 
b/solr/core/src/test/org/apache/solr/update/MaxSizeAutoCommitTest.java
index eb4f6d90e13..3736b61e999 100644
--- a/solr/core/src/test/org/apache/solr/update/MaxSizeAutoCommitTest.java
+++ b/solr/core/src/test/org/apache/solr/update/MaxSizeAutoCommitTest.java
@@ -26,9 +26,9 @@ import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Function;
 import org.apache.solr.SolrTestCaseJ4;
-import org.apache.solr.client.solrj.util.ClientUtils;
 import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.common.util.ContentStream;
+import org.apache.solr.common.util.ContentStreamBase;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.core.SolrEventListener;
 import org.apache.solr.handler.UpdateRequestHandler;
@@ -252,7 +252,7 @@ public class MaxSizeAutoCommitTest extends SolrTestCaseJ4 {
   private List<ContentStream> toContentStreams(List<String> strs) {
     ArrayList<ContentStream> streams = new ArrayList<>();
     for (String str : strs) {
-      streams.addAll(ClientUtils.toContentStreams(str, "text/xml"));
+      streams.add(new ContentStreamBase.StringStream(str, "text/xml"));
     }
     return streams;
   }
diff --git 
a/solr/core/src/test/org/apache/solr/update/processor/TolerantUpdateProcessorTest.java
 
b/solr/core/src/test/org/apache/solr/update/processor/TolerantUpdateProcessorTest.java
index 421f2885881..f87d1b2d260 100644
--- 
a/solr/core/src/test/org/apache/solr/update/processor/TolerantUpdateProcessorTest.java
+++ 
b/solr/core/src/test/org/apache/solr/update/processor/TolerantUpdateProcessorTest.java
@@ -27,14 +27,13 @@ import java.util.List;
 import java.util.Set;
 import javax.xml.xpath.XPathExpressionException;
 import org.apache.solr.client.solrj.embedded.EmbeddedSolrServer;
-import org.apache.solr.client.solrj.request.ContentStreamUpdateRequest;
+import org.apache.solr.client.solrj.request.ContentWriterUpdateRequest;
 import org.apache.solr.client.solrj.response.InputStreamResponseParser;
 import org.apache.solr.client.solrj.util.ClientUtils;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.SolrInputDocument;
 import org.apache.solr.common.params.ModifiableSolrParams;
 import org.apache.solr.common.params.SolrParams;
-import org.apache.solr.common.util.ContentStreamBase;
 import org.apache.solr.common.util.IOUtils;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.SimpleOrderedMap;
@@ -395,9 +394,8 @@ public class TolerantUpdateProcessorTest extends 
UpdateProcessorTestBase {
 
   public String update(String chain, String xml) {
     try {
-      // Use ContentStreamUpdateRequest to send raw XML through 
EmbeddedSolrServer
-      ContentStreamUpdateRequest xmlRequest = new 
ContentStreamUpdateRequest("/update");
-      xmlRequest.addContentStream(new ContentStreamBase.StringStream(xml, 
"text/xml"));
+      ContentWriterUpdateRequest xmlRequest = new 
ContentWriterUpdateRequest("/update");
+      xmlRequest.addContentWithType(xml, "text/xml");
 
       // Set the update chain parameter and request XML response
       xmlRequest.getParams().add("update.chain", chain);
diff --git a/solr/core/src/test/org/apache/solr/util/TestUtils.java 
b/solr/core/src/test/org/apache/solr/util/TestUtils.java
index d0da76dc419..38a188a4d63 100644
--- a/solr/core/src/test/org/apache/solr/util/TestUtils.java
+++ b/solr/core/src/test/org/apache/solr/util/TestUtils.java
@@ -33,6 +33,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.api.ApiBag;
 import org.apache.solr.common.MapWriter;
 import org.apache.solr.common.util.CommandOperation;
 import org.apache.solr.common.util.ContentStream;
@@ -138,7 +139,7 @@ public class TestUtils extends SolrTestCaseJ4 {
     ContentStream stream =
         new ContentStreamBase.ByteArrayStream(baos.toByteArray(), null, 
"application/javabin");
     List<CommandOperation> commands =
-        CommandOperation.readCommands(List.of(stream), new NamedList<>(), 
Set.of("single"));
+        ApiBag.readCommands(List.of(stream), new NamedList<>(), 
Set.of("single"));
 
     assertEquals(5, commands.size());
   }
diff --git 
a/solr/modules/cross-dc/src/java/org/apache/solr/crossdc/common/MirroredSolrRequest.java
 
b/solr/modules/cross-dc/src/java/org/apache/solr/crossdc/common/MirroredSolrRequest.java
index d30e64aae60..bcc789538d3 100644
--- 
a/solr/modules/cross-dc/src/java/org/apache/solr/crossdc/common/MirroredSolrRequest.java
+++ 
b/solr/modules/cross-dc/src/java/org/apache/solr/crossdc/common/MirroredSolrRequest.java
@@ -20,13 +20,16 @@ import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStream;
 import java.util.Collection;
 import java.util.Locale;
 import java.util.Objects;
+import java.util.stream.Collectors;
 import org.apache.solr.client.solrj.SolrRequest;
 import org.apache.solr.client.solrj.SolrResponse;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
 import org.apache.solr.client.solrj.request.ConfigSetAdminRequest;
+import org.apache.solr.client.solrj.request.RequestWriter;
 import org.apache.solr.client.solrj.request.UpdateRequest;
 import org.apache.solr.client.solrj.response.CollectionAdminResponse;
 import org.apache.solr.client.solrj.response.ConfigSetAdminResponse;
@@ -111,11 +114,43 @@ public class MirroredSolrRequest<T extends SolrResponse> {
       this.params = params;
     }
 
-    @Override
-    public Collection<ContentStream> getContentStreams() {
+    /** The raw streams, for serializers that need the individual 
name/type/bytes directly. */
+    public Collection<ContentStream> getRawContentStreams() {
       return contentStreams;
     }
 
+    @Override
+    public RequestWriter.ContentWriter getContentWriter(String expectedType) {
+      if (contentStreams == null || contentStreams.isEmpty()) return null;
+      if (contentStreams.size() == 1) {
+        return streamWriter(contentStreams.iterator().next());
+      }
+      return new RequestWriter.MultipartContentWriter() {
+        @Override
+        public java.util.List<RequestWriter.NamedPart> getParts() {
+          return contentStreams.stream()
+              .map(cs -> new RequestWriter.NamedPart(cs.getName(), 
streamWriter(cs)))
+              .collect(Collectors.toList());
+        }
+      };
+    }
+
+    private static RequestWriter.ContentWriter streamWriter(ContentStream 
stream) {
+      return new RequestWriter.ContentWriter() {
+        @Override
+        public void write(OutputStream os) throws IOException {
+          try (var inStream = stream.getStream()) {
+            inStream.transferTo(os);
+          }
+        }
+
+        @Override
+        public String getContentType() {
+          return stream.getContentType();
+        }
+      };
+    }
+
     @Override
     protected ConfigSetAdminResponse createResponse(NamedList<Object> 
namedList) {
       return new ConfigSetAdminResponse();
diff --git 
a/solr/modules/cross-dc/src/java/org/apache/solr/crossdc/common/MirroredSolrRequestSerializer.java
 
b/solr/modules/cross-dc/src/java/org/apache/solr/crossdc/common/MirroredSolrRequestSerializer.java
index 137d46f106e..d9ae0e961bb 100644
--- 
a/solr/modules/cross-dc/src/java/org/apache/solr/crossdc/common/MirroredSolrRequestSerializer.java
+++ 
b/solr/modules/cross-dc/src/java/org/apache/solr/crossdc/common/MirroredSolrRequestSerializer.java
@@ -225,9 +225,9 @@ public class MirroredSolrRequestSerializer
         }
       } else if (solrRequest instanceof 
MirroredSolrRequest.MirroredConfigSetRequest config) {
         map.put("method", config.getMethod().toString());
-        if (config.getContentStreams() != null) {
+        if (config.getRawContentStreams() != null) {
           List<Map<String, Object>> streamsList = new ArrayList<>();
-          for (ContentStream cs : config.getContentStreams()) {
+          for (ContentStream cs : config.getRawContentStreams()) {
             Map<String, Object> streamMap = new HashMap<>();
             streamMap.put("name", cs.getName());
             streamMap.put("contentType", cs.getContentType());
diff --git 
a/solr/modules/cross-dc/src/test/org/apache/solr/crossdc/handler/MirroringConfigSetsHandlerTest.java
 
b/solr/modules/cross-dc/src/test/org/apache/solr/crossdc/handler/MirroringConfigSetsHandlerTest.java
index b115f4cdcdb..84526c1dad5 100644
--- 
a/solr/modules/cross-dc/src/test/org/apache/solr/crossdc/handler/MirroringConfigSetsHandlerTest.java
+++ 
b/solr/modules/cross-dc/src/test/org/apache/solr/crossdc/handler/MirroringConfigSetsHandlerTest.java
@@ -153,7 +153,9 @@ public class MirroringConfigSetsHandlerTest extends 
SolrTestCaseJ4 {
       if (expectStreams) {
         List<ContentStream> sourceStreams = (List<ContentStream>) 
req.getContentStreams();
         assertNotNull("source streams missing", sourceStreams);
-        List<ContentStream> mirroredStreams = (List<ContentStream>) 
solrRequest.getContentStreams();
+        List<ContentStream> mirroredStreams =
+            (List<ContentStream>)
+                ((MirroredSolrRequest.MirroredConfigSetRequest) 
solrRequest).getRawContentStreams();
         assertNotNull("mirrored streams missing", mirroredStreams);
         assertEquals("number of streams", sourceStreams.size(), 
mirroredStreams.size());
         for (int i = 0; i < sourceStreams.size(); i++) {
diff --git 
a/solr/solr-ref-guide/modules/indexing-guide/examples/SolrCellRefGuideExamplesTest.java
 
b/solr/solr-ref-guide/modules/indexing-guide/examples/SolrCellRefGuideExamplesTest.java
index 14d6b40de29..33314a357da 100644
--- 
a/solr/solr-ref-guide/modules/indexing-guide/examples/SolrCellRefGuideExamplesTest.java
+++ 
b/solr/solr-ref-guide/modules/indexing-guide/examples/SolrCellRefGuideExamplesTest.java
@@ -23,7 +23,7 @@ import static 
org.apache.solr.client.ref_guide_examples.ExpectedOutputVerifier.p
 import java.nio.file.Path;
 import java.util.List;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
-import org.apache.solr.client.solrj.request.ContentStreamUpdateRequest;
+import org.apache.solr.client.solrj.request.ContentWriterUpdateRequest;
 import org.apache.solr.cloud.SolrCloudTestCase;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.util.NamedList;
@@ -71,8 +71,8 @@ public class SolrCellRefGuideExamplesTest extends 
SolrCloudTestCase {
         List.of("Error loading class 
'solr.extraction.ExtractingRequestHandler'"),
         () -> {
           // tag::solr-cell-pdf-extraction[]
-          ContentStreamUpdateRequest extractRequest =
-              new ContentStreamUpdateRequest("/update/extract");
+          ContentWriterUpdateRequest extractRequest =
+              new ContentWriterUpdateRequest("/update/extract");
           extractRequest.addFile(Path.of(PDF_DOC_PATH), "application/pdf");
           extractRequest.setParam("extractOnly", "true");
           NamedList<Object> extractResponse =
diff --git 
a/solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-tika.adoc 
b/solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-tika.adoc
index d17a899b7ab..28d7df9431a 100644
--- a/solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-tika.adoc
+++ b/solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-tika.adoc
@@ -677,8 +677,8 @@ 
include::example$SolrCellRefGuideExamplesTest.java[tag=solr-cell-pdf-extraction]
 This operation streams the file `my-file.pdf` into the Solr index for 
`my_collection`.
 
 The sample code above calls the extract command, but you can substitute other 
commands that are supported by Solr Cell.
-The key class to use is the `ContentStreamUpdateRequest`, which makes sure the 
ContentStreams are set properly.
+The key class to use is the `ContentWriterUpdateRequest`, which makes sure the 
request content is written properly.
 SolrJ takes care of the rest.
 
-Note that the `ContentStreamUpdateRequest` is not just specific to Solr Cell.
+Note that the `ContentWriterUpdateRequest` is not just specific to Solr Cell.
 You can send CSV to the CSV Update handler and to any other Request Handler 
that works with Content Streams for updates.
diff --git 
a/solr/solr-ref-guide/modules/query-guide/examples/JsonRequestApiTest.java 
b/solr/solr-ref-guide/modules/query-guide/examples/JsonRequestApiTest.java
index 16f60ea2dad..a5dcc194263 100644
--- a/solr/solr-ref-guide/modules/query-guide/examples/JsonRequestApiTest.java
+++ b/solr/solr-ref-guide/modules/query-guide/examples/JsonRequestApiTest.java
@@ -26,7 +26,7 @@ import java.util.Map;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.request.AbstractUpdateRequest;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
-import org.apache.solr.client.solrj.request.ContentStreamUpdateRequest;
+import org.apache.solr.client.solrj.request.ContentWriterUpdateRequest;
 import org.apache.solr.client.solrj.request.SolrQuery;
 import org.apache.solr.client.solrj.request.json.DomainMap;
 import org.apache.solr.client.solrj.request.json.JsonQueryRequest;
@@ -61,7 +61,7 @@ public class JsonRequestApiTest extends SolrCloudTestCase {
     CollectionAdminRequest.createCollection(COLLECTION_NAME, CONFIG_NAME, 1, 1)
         .process(cluster.getSolrClient());
 
-    ContentStreamUpdateRequest up = new ContentStreamUpdateRequest("/update");
+    ContentWriterUpdateRequest up = new ContentWriterUpdateRequest("/update");
     up.setParam("collection", COLLECTION_NAME);
     up.addFile(getFile("solrj/techproducts.xml"), "application/xml");
     up.setAction(AbstractUpdateRequest.ACTION.COMMIT, true, true);
diff --git 
a/solr/solrj-jetty/src/java/org/apache/solr/client/solrj/jetty/HttpJettySolrClient.java
 
b/solr/solrj-jetty/src/java/org/apache/solr/client/solrj/jetty/HttpJettySolrClient.java
index f4e5307bada..2f5cb96fcbf 100644
--- 
a/solr/solrj-jetty/src/java/org/apache/solr/client/solrj/jetty/HttpJettySolrClient.java
+++ 
b/solr/solrj-jetty/src/java/org/apache/solr/client/solrj/jetty/HttpJettySolrClient.java
@@ -23,7 +23,6 @@ import java.lang.invoke.MethodHandles;
 import java.lang.reflect.InvocationTargetException;
 import java.net.ConnectException;
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -55,7 +54,6 @@ import org.apache.solr.client.solrj.response.ResponseParser;
 import org.apache.solr.client.solrj.util.ClientUtils;
 import org.apache.solr.common.params.CommonParams;
 import org.apache.solr.common.params.ModifiableSolrParams;
-import org.apache.solr.common.util.ContentStream;
 import org.apache.solr.common.util.EnvUtils;
 import org.apache.solr.common.util.ExecutorUtil;
 import org.apache.solr.common.util.NamedList;
@@ -65,7 +63,6 @@ import org.eclipse.jetty.client.AuthenticationStore;
 import org.eclipse.jetty.client.HttpClient;
 import org.eclipse.jetty.client.HttpClientTransport;
 import org.eclipse.jetty.client.HttpProxy;
-import org.eclipse.jetty.client.InputStreamRequestContent;
 import org.eclipse.jetty.client.InputStreamResponseListener;
 import org.eclipse.jetty.client.MultiPartRequestContent;
 import org.eclipse.jetty.client.Origin.Address;
@@ -710,10 +707,14 @@ public class HttpJettySolrClient extends HttpSolrClient {
     }
   }
 
+  private record PartContent(
+      RequestWriter.ContentWriter writer, OutputStreamRequestContent content) 
{}
+
   private static class MakeRequestReturnValue {
     final Request request;
     final RequestWriter.ContentWriter contentWriter;
     final OutputStreamRequestContent requestContent;
+    final List<PartContent> partContents;
 
     MakeRequestReturnValue(
         Request request,
@@ -722,12 +723,21 @@ public class HttpJettySolrClient extends HttpSolrClient {
       this.request = request;
       this.contentWriter = contentWriter;
       this.requestContent = requestContent;
+      this.partContents = null;
+    }
+
+    MakeRequestReturnValue(Request request, List<PartContent> partContents) {
+      this.request = request;
+      this.contentWriter = null;
+      this.requestContent = null;
+      this.partContents = partContents;
     }
 
     MakeRequestReturnValue(Request request) {
       this.request = request;
       this.contentWriter = null;
       this.requestContent = null;
+      this.partContents = null;
     }
   }
 
@@ -751,37 +761,33 @@ public class HttpJettySolrClient extends HttpSolrClient {
 
     if (SolrRequest.METHOD.POST == solrRequest.getMethod()
         || SolrRequest.METHOD.PUT == solrRequest.getMethod()) {
-      RequestWriter.ContentWriter contentWriter = 
requestWriter.getContentWriter(solrRequest);
-      Collection<ContentStream> streams =
-          contentWriter == null ? requestWriter.getContentStreams(solrRequest) 
: null;
-
-      boolean isMultipart = isMultipart(streams);
+      RequestWriter.ContentWriter contentWriter =
+          requestWriter.getContentWriterOrStreams(solrRequest);
 
       HttpMethod method =
           SolrRequest.METHOD.POST == solrRequest.getMethod() ? HttpMethod.POST 
: HttpMethod.PUT;
 
-      if (contentWriter != null) {
+      if (contentWriter instanceof RequestWriter.MultipartContentWriter 
multipartWriter) {
+        // send server list and request list as query string params
+        ModifiableSolrParams queryParams = 
calculateQueryParams(this.urlParamNames, wparams);
+        queryParams.add(calculateQueryParams(solrRequest.getQueryParams(), 
wparams));
+        Request req = httpClient.newRequest(url + 
queryParams.toQueryString()).method(method);
+        var r = fillMultipartContent(req, multipartWriter, wparams);
+        decorateRequest(r.request, solrRequest, isAsync);
+        return r;
+
+      } else if (contentWriter != null) {
         var content = new 
OutputStreamRequestContent(contentWriter.getContentType());
         var r = httpClient.newRequest(url + 
wparams.toQueryString()).method(method).body(content);
         decorateRequest(r, solrRequest, isAsync);
         return new MakeRequestReturnValue(r, contentWriter, content);
 
-      } else if (streams == null || isMultipart) {
-        // send server list and request list as query string params
+      } else {
+        // application/x-www-form-urlencoded, params only
         ModifiableSolrParams queryParams = 
calculateQueryParams(this.urlParamNames, wparams);
         queryParams.add(calculateQueryParams(solrRequest.getQueryParams(), 
wparams));
         Request req = httpClient.newRequest(url + 
queryParams.toQueryString()).method(method);
-        var r = fillContentStream(req, streams, wparams, isMultipart);
-        decorateRequest(r, solrRequest, isAsync);
-        return new MakeRequestReturnValue(r);
-
-      } else {
-        // If it has one stream, it is the post body, put the params in the URL
-        ContentStream contentStream = streams.iterator().next();
-        var content =
-            new InputStreamRequestContent(
-                contentStream.getContentType(), contentStream.getStream());
-        var r = httpClient.newRequest(url + 
wparams.toQueryString()).method(method).body(content);
+        var r = fillParamsOnlyBody(req, wparams);
         decorateRequest(r, solrRequest, isAsync);
         return new MakeRequestReturnValue(r);
       }
@@ -798,63 +804,62 @@ public class HttpJettySolrClient extends HttpSolrClient {
       try (var output = mrrv.requestContent.getOutputStream()) {
         mrrv.contentWriter.write(output);
       }
+    } else if (mrrv.partContents != null) {
+      for (PartContent part : mrrv.partContents) {
+        try (var output = part.content().getOutputStream()) {
+          part.writer().write(output);
+        }
+      }
     }
     return mrrv.request;
   }
 
-  private Request fillContentStream(
+  private Request fillParamsOnlyBody(Request req, ModifiableSolrParams 
wparams) {
+    String queryString = wparams.toQueryString();
+    // remove the leading "?" if there is any
+    queryString = queryString.startsWith("?") ? queryString.substring(1) : 
queryString;
+    req.body(
+        new StringRequestContent(
+            "application/x-www-form-urlencoded", queryString, 
FALLBACK_CHARSET));
+    return req;
+  }
+
+  private MakeRequestReturnValue fillMultipartContent(
       Request req,
-      Collection<ContentStream> streams,
-      ModifiableSolrParams wparams,
-      boolean isMultipart)
+      RequestWriter.MultipartContentWriter multipartWriter,
+      ModifiableSolrParams wparams)
       throws IOException {
-    if (isMultipart) {
-      // multipart/form-data
-      try (MultiPartRequestContent content = new MultiPartRequestContent()) {
-        Iterator<String> iter = wparams.getParameterNamesIterator();
-        while (iter.hasNext()) {
-          String key = iter.next();
-          String[] vals = wparams.getParams(key);
-          if (vals != null) {
-            for (String val : vals) {
-              content.addPart(
-                  new MultiPart.ContentSourcePart(key, null, null, new 
StringRequestContent(val)));
-            }
-          }
-        }
-        if (streams != null) {
-          for (ContentStream contentStream : streams) {
-            String contentType = contentStream.getContentType();
-            if (contentType == null) {
-              contentType = "multipart/form-data"; // default
-            }
-            String name = contentStream.getName();
-            if (name == null) {
-              name = "";
-            }
-            HttpFields.Mutable fields = HttpFields.build(1);
-            fields.add(HttpHeader.CONTENT_TYPE, contentType);
+    MultiPartRequestContent content = new MultiPartRequestContent();
+    try {
+      Iterator<String> iter = wparams.getParameterNamesIterator();
+      while (iter.hasNext()) {
+        String key = iter.next();
+        String[] vals = wparams.getParams(key);
+        if (vals != null) {
+          for (String val : vals) {
             content.addPart(
-                new MultiPart.ContentSourcePart(
-                    name,
-                    contentStream.getName(),
-                    fields,
-                    new InputStreamRequestContent(contentStream.getStream())));
+                new MultiPart.ContentSourcePart(key, null, null, new 
StringRequestContent(val)));
           }
         }
-        req.body(content);
       }
-    } else {
-      // application/x-www-form-urlencoded
-      String queryString = wparams.toQueryString();
-      // remove the leading "?" if there is any
-      queryString = queryString.startsWith("?") ? queryString.substring(1) : 
queryString;
-      req.body(
-          new StringRequestContent(
-              "application/x-www-form-urlencoded", queryString, 
FALLBACK_CHARSET));
+      List<PartContent> partContents = new ArrayList<>();
+      for (RequestWriter.NamedPart part : multipartWriter.getParts()) {
+        String contentType = part.writer.getContentType();
+        if (contentType == null) {
+          contentType = "multipart/form-data"; // default
+        }
+        String name = part.name == null ? "" : part.name;
+        HttpFields.Mutable fields = HttpFields.build(1);
+        fields.add(HttpHeader.CONTENT_TYPE, contentType);
+        var partContent = new OutputStreamRequestContent(contentType);
+        content.addPart(new MultiPart.ContentSourcePart(name, part.name, 
fields, partContent));
+        partContents.add(new PartContent(part.writer, partContent));
+      }
+      req.body(content);
+      return new MakeRequestReturnValue(req, partContents);
+    } finally {
+      content.close();
     }
-
-    return req;
   }
 
   @Override
diff --git 
a/solr/solrj-jetty/src/test/org/apache/solr/client/solrj/jetty/HttpJettySolrClientTest.java
 
b/solr/solrj-jetty/src/test/org/apache/solr/client/solrj/jetty/HttpJettySolrClientTest.java
index a47e981be5b..f621307ce58 100644
--- 
a/solr/solrj-jetty/src/test/org/apache/solr/client/solrj/jetty/HttpJettySolrClientTest.java
+++ 
b/solr/solrj-jetty/src/test/org/apache/solr/client/solrj/jetty/HttpJettySolrClientTest.java
@@ -22,8 +22,11 @@ import static 
org.hamcrest.core.StringContains.containsStringIgnoringCase;
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStream;
 import java.nio.charset.StandardCharsets;
 import java.util.Base64;
+import java.util.Collection;
+import java.util.List;
 import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
@@ -40,8 +43,12 @@ import org.apache.solr.client.solrj.SolrRequest;
 import org.apache.solr.client.solrj.SolrServerException;
 import org.apache.solr.client.solrj.impl.HttpSolrClient;
 import org.apache.solr.client.solrj.impl.HttpSolrClientTestBase;
+import org.apache.solr.client.solrj.request.AbstractUpdateRequest;
+import org.apache.solr.client.solrj.request.ContentWriterUpdateRequest;
+import org.apache.solr.client.solrj.request.GenericSolrRequest;
 import org.apache.solr.client.solrj.request.JavaBinRequestWriter;
 import org.apache.solr.client.solrj.request.QueryRequest;
+import org.apache.solr.client.solrj.request.RequestWriter;
 import org.apache.solr.client.solrj.request.SolrPing;
 import org.apache.solr.client.solrj.request.SolrQuery;
 import org.apache.solr.client.solrj.request.XMLRequestWriter;
@@ -53,6 +60,8 @@ 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.ContentStreamBase;
 import org.apache.solr.common.util.ExecutorUtil;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.SolrNamedThreadFactory;
@@ -99,6 +108,98 @@ public class HttpJettySolrClientTest extends 
HttpSolrClientTestBase {
     }
   }
 
+  @Test
+  @SuppressWarnings("deprecation")
+  public void testRequestContentStreams() throws Exception {
+    var req =
+        new AbstractUpdateRequest(SolrRequest.METHOD.POST, "/update") {
+          @Override
+          public Collection<ContentStream> getContentStreams() {
+            return legacyStreams();
+          }
+        };
+    assertLegacyContentSent(new JavaBinRequestWriter(), req);
+  }
+
+  @Test
+  @SuppressWarnings("deprecation")
+  public void testRequestWriterContentStreams() throws Exception {
+    var writer =
+        new RequestWriter() {
+          @Override
+          public ContentWriter getContentWriter(SolrRequest<?> req) {
+            return null;
+          }
+
+          @Override
+          public Collection<ContentStream> getContentStreams(SolrRequest<?> 
req) {
+            return legacyStreams();
+          }
+
+          @Override
+          public void write(SolrRequest<?> request, OutputStream os) {}
+
+          @Override
+          public String getUpdateContentType() {
+            return "text/plain";
+          }
+        };
+    assertLegacyContentSent(writer, new 
GenericSolrRequest(SolrRequest.METHOD.POST, "/update"));
+  }
+
+  @SuppressWarnings("deprecation")
+  private static Collection<ContentStream> legacyStreams() {
+    var stream = new ContentStreamBase.StringStream("legacy content");
+    stream.setContentType("text/plain");
+    return List.of(stream);
+  }
+
+  private void assertLegacyContentSent(RequestWriter writer, SolrRequest<?> 
req) throws Exception {
+    String url = solrTestRule.getBaseUrl() + DEBUG_SERVLET_PATH;
+    try (var client =
+        new HttpJettySolrClient.Builder(url)
+            .withDefaultCollection(DEFAULT_COLLECTION)
+            .withRequestWriter(writer)
+            .build()) {
+      try {
+        client.request(req);
+      } catch (RemoteSolrException ignored) {
+      }
+      assertEquals("legacy content", new String(DebugServlet.requestBody, 
StandardCharsets.UTF_8));
+    } finally {
+      DebugServlet.clear();
+    }
+  }
+
+  @Test
+  public void testMultipartUpload() throws Exception {
+    ContentWriterUpdateRequest req = new ContentWriterUpdateRequest("/update");
+    req.addPart(
+        "firstPart", new RequestWriter.StringPayloadContentWriter("first 
content", "text/plain"));
+    req.addPart(
+        "secondPart", new RequestWriter.StringPayloadContentWriter("second 
content", "text/plain"));
+    req.setParam("someParam", "someValue");
+
+    String url = solrTestRule.getBaseUrl() + DEBUG_SERVLET_PATH;
+    try (var client =
+        new 
HttpJettySolrClient.Builder(url).withDefaultCollection(DEFAULT_COLLECTION).build())
 {
+      try {
+        client.request(req);
+      } catch (RemoteSolrException ignored) {
+      }
+
+      String contentType = DebugServlet.headers.get("content-type");
+      assertTrue(contentType, contentType != null && 
contentType.startsWith("multipart/form-data"));
+      String body = new String(DebugServlet.requestBody, 
StandardCharsets.UTF_8);
+      assertTrue(body, body.contains("firstPart"));
+      assertTrue(body, body.contains("first content"));
+      assertTrue(body, body.contains("secondPart"));
+      assertTrue(body, body.contains("second content"));
+    } finally {
+      DebugServlet.clear();
+    }
+  }
+
   @Test
   public void test0IdleTimeout() throws Exception {
     SolrQuery q = new SolrQuery("*:*");
diff --git 
a/solr/solrj-jetty/src/test/org/apache/solr/client/solrj/jetty/TestLegacyContentStreamCompatibility.java
 
b/solr/solrj-jetty/src/test/org/apache/solr/client/solrj/jetty/TestLegacyContentStreamCompatibility.java
new file mode 100644
index 00000000000..4afa8061f83
--- /dev/null
+++ 
b/solr/solrj-jetty/src/test/org/apache/solr/client/solrj/jetty/TestLegacyContentStreamCompatibility.java
@@ -0,0 +1,283 @@
+/*
+ * 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.jetty;
+
+import jakarta.servlet.http.HttpServlet;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Collection;
+import java.util.List;
+import java.util.Properties;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import org.apache.solr.SolrTestCase;
+import org.apache.solr.client.solrj.SolrClient;
+import org.apache.solr.client.solrj.SolrRequest;
+import org.apache.solr.client.solrj.impl.HttpJdkSolrClient;
+import org.apache.solr.client.solrj.request.GenericSolrRequest;
+import org.apache.solr.client.solrj.request.JavaBinRequestWriter;
+import org.apache.solr.client.solrj.request.RequestWriter;
+import org.apache.solr.client.solrj.request.XMLRequestWriter;
+import org.apache.solr.client.solrj.response.SimpleSolrResponse;
+import org.apache.solr.client.solrj.response.XMLResponseParser;
+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.ContentStreamBase;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.embedded.JettyConfig;
+import org.apache.solr.util.SolrJettyTestRule;
+import org.eclipse.jetty.ee10.servlet.ServletHolder;
+import org.junit.After;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+/** Checks legacy request bodies and custom stream writers through both HTTP 
clients. */
+@SuppressWarnings("deprecation")
+public class TestLegacyContentStreamCompatibility extends SolrTestCase {
+
+  private static final String REQUEST_BODY = "original-request-body";
+  private static final String WRITER_BODY = "custom-writer-body";
+  private static final String CONTENT_TYPE = "text/plain";
+  private static final String FORM_CONTENT_TYPE = 
"application/x-www-form-urlencoded";
+  private static final AtomicReference<CapturedRequest> capturedRequest = new 
AtomicReference<>();
+
+  @ClassRule public static final SolrJettyTestRule solrTestRule = new 
SolrJettyTestRule();
+
+  @BeforeClass
+  public static void beforeClass() throws Exception {
+    JettyConfig config =
+        JettyConfig.builder()
+            .withServlet(new ServletHolder(RawBodyServlet.class), 
"/legacy-content/*")
+            .build();
+    solrTestRule.startSolr(createTempDir(), new Properties(), config);
+  }
+
+  @After
+  public void clearServlet() {
+    capturedRequest.set(null);
+  }
+
+  @Test
+  public void testJettyLegacyRequestWithJavaBinWriter() throws Exception {
+    assertLegacyRequestBody(false, new JavaBinRequestWriter());
+  }
+
+  @Test
+  public void testJettyLegacyRequestWithXmlWriter() throws Exception {
+    assertLegacyRequestBody(false, new XMLRequestWriter());
+  }
+
+  @Test
+  public void testJdkLegacyRequestWithJavaBinWriter() throws Exception {
+    assertLegacyRequestBody(true, new JavaBinRequestWriter());
+  }
+
+  @Test
+  public void testJdkLegacyRequestWithXmlWriter() throws Exception {
+    assertLegacyRequestBody(true, new XMLRequestWriter());
+  }
+
+  @Test
+  public void testJettyJavaBinWriterStreamOverride() throws Exception {
+    assertWriterOverride(false, false);
+  }
+
+  @Test
+  public void testJettyXmlWriterStreamOverride() throws Exception {
+    assertWriterOverride(false, true);
+  }
+
+  @Test
+  public void testJdkJavaBinWriterStreamOverride() throws Exception {
+    assertWriterOverride(true, false);
+  }
+
+  @Test
+  public void testJdkXmlWriterStreamOverride() throws Exception {
+    assertWriterOverride(true, true);
+  }
+
+  @Test
+  public void testJettyContentWriterControl() throws Exception {
+    assertExplicitContentWriter(false, CONTENT_TYPE);
+  }
+
+  @Test
+  public void testJdkContentWriterControl() throws Exception {
+    assertExplicitContentWriter(true, CONTENT_TYPE);
+  }
+
+  @Test
+  public void testJettyFormContentWriterControl() throws Exception {
+    assertExplicitContentWriter(false, FORM_CONTENT_TYPE);
+  }
+
+  @Test
+  public void testJdkFormContentWriterControl() throws Exception {
+    assertExplicitContentWriter(true, FORM_CONTENT_TYPE);
+  }
+
+  private void assertLegacyRequestBody(boolean jdk, RequestWriter writer) 
throws Exception {
+    GenericSolrRequest request =
+        new GenericSolrRequest(
+            SolrRequest.METHOD.POST, "/update", 
SolrRequest.SolrRequestType.UPDATE) {
+          @Override
+          public Collection<ContentStream> getContentStreams() {
+            return streams(REQUEST_BODY);
+          }
+        };
+    assertNull(request.getContentWriter(null));
+    assertPostBody(jdk, writer, request, REQUEST_BODY);
+  }
+
+  private void assertWriterOverride(boolean jdk, boolean xml) throws Exception 
{
+    AtomicInteger calls = new AtomicInteger();
+    RequestWriter writer = streamOverrideWriter(xml, calls);
+    assertPostBody(jdk, writer, new LegacyStreamsRequest(), WRITER_BODY);
+    assertTrue("The custom writer's stream hook must be consulted", 
calls.get() > 0);
+  }
+
+  private RequestWriter streamOverrideWriter(boolean xml, AtomicInteger calls) 
{
+    RequestWriter writer;
+    if (xml) {
+      writer =
+          new XMLRequestWriter() {
+            @Override
+            public Collection<ContentStream> getContentStreams(SolrRequest<?> 
request) {
+              calls.incrementAndGet();
+              return streams(WRITER_BODY);
+            }
+          };
+    } else {
+      writer =
+          new JavaBinRequestWriter() {
+            @Override
+            public Collection<ContentStream> getContentStreams(SolrRequest<?> 
request) {
+              calls.incrementAndGet();
+              return streams(WRITER_BODY);
+            }
+          };
+    }
+    return writer;
+  }
+
+  private void assertExplicitContentWriter(boolean jdk, String contentType) 
throws Exception {
+    GenericSolrRequest request =
+        new GenericSolrRequest(
+            SolrRequest.METHOD.POST, "/update", 
SolrRequest.SolrRequestType.UPDATE);
+    request.setContentWriter(
+        new RequestWriter.StringPayloadContentWriter(REQUEST_BODY, 
contentType));
+    for (boolean xml : List.of(false, true)) {
+      AtomicInteger calls = new AtomicInteger();
+      CapturedRequest captured =
+          assertPostBody(jdk, streamOverrideWriter(xml, calls), request, 
REQUEST_BODY);
+      assertEquals(contentType, captured.contentType());
+      assertEquals("An explicit writer must take precedence over the stream 
hook", 0, calls.get());
+    }
+  }
+
+  private CapturedRequest assertPostBody(
+      boolean jdk, RequestWriter writer, SolrRequest<?> request, String 
expectedBody)
+      throws Exception {
+    capturedRequest.set(null);
+    String url = solrTestRule.getBaseUrl() + "/legacy-content";
+    try (SolrClient client =
+        jdk
+            ? new HttpJdkSolrClient.Builder(url)
+                .withDefaultCollection("collection1")
+                .withRequestWriter(writer)
+                .withResponseParser(new XMLResponseParser())
+                .build()
+            : new HttpJettySolrClient.Builder(url)
+                .withDefaultCollection("collection1")
+                .withRequestWriter(writer)
+                .withResponseParser(new XMLResponseParser())
+                .build()) {
+      client.request(request);
+      CapturedRequest captured = capturedRequest.get();
+      assertNotNull("The servlet must capture the POST body", captured);
+      assertEquals("POST", captured.method());
+      // Record the legacy content type separately from the request-body 
assertion.
+      System.out.println(
+          "Captured POST: client="
+              + client.getClass().getSimpleName()
+              + ", writer="
+              + writer.getClass().getName()
+              + ", contentType="
+              + captured.contentType()
+              + ", body="
+              + new String(captured.body(), StandardCharsets.UTF_8));
+      assertArrayEquals(expectedBody.getBytes(StandardCharsets.UTF_8), 
captured.body());
+      return captured;
+    }
+  }
+
+  private record CapturedRequest(String method, String contentType, byte[] 
body) {}
+
+  /** Captures bytes before any servlet parameter parsing can consume a 
form-encoded body. */
+  public static class RawBodyServlet extends HttpServlet {
+
+    @Override
+    protected void doHead(HttpServletRequest request, HttpServletResponse 
response) {
+      response.setContentType("application/xml; charset=UTF-8");
+      response.setStatus(HttpServletResponse.SC_OK);
+    }
+
+    @Override
+    protected void doPost(HttpServletRequest request, HttpServletResponse 
response)
+        throws IOException {
+      byte[] body = request.getInputStream().readAllBytes();
+      capturedRequest.set(new CapturedRequest(request.getMethod(), 
request.getContentType(), body));
+      response.setContentType("application/xml; charset=UTF-8");
+      response
+          .getOutputStream()
+          .write(
+              "<response><lst name=\"responseHeader\"><int 
name=\"status\">0</int></lst></response>"
+                  .getBytes(StandardCharsets.UTF_8));
+    }
+  }
+
+  private static List<ContentStream> streams(String body) {
+    return List.of(new ContentStreamBase.StringStream(body, CONTENT_TYPE));
+  }
+
+  private static class LegacyStreamsRequest extends 
SolrRequest<SimpleSolrResponse> {
+
+    LegacyStreamsRequest() {
+      super(METHOD.POST, "/update", SolrRequestType.UPDATE);
+    }
+
+    @Override
+    public Collection<ContentStream> getContentStreams() {
+      return streams(REQUEST_BODY);
+    }
+
+    @Override
+    public SolrParams getParams() {
+      return new ModifiableSolrParams();
+    }
+
+    @Override
+    protected SimpleSolrResponse createResponse(NamedList<Object> response) {
+      return new SimpleSolrResponse();
+    }
+  }
+}
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/SolrRequest.java 
b/solr/solrj/src/java/org/apache/solr/client/solrj/SolrRequest.java
index 5dc69902c0a..9ec9540894c 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/SolrRequest.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/SolrRequest.java
@@ -272,14 +272,6 @@ public abstract class SolrRequest<T> implements 
Serializable {
     return ApiVersion.V1;
   }
 
-  /**
-   * @deprecated Please use {@link SolrRequest#getContentWriter(String)} 
instead.
-   */
-  @Deprecated(since = "7.2")
-  public Collection<ContentStream> getContentStreams() throws IOException {
-    return null;
-  }
-
   /**
    * If a request object wants to do a push write, implement this method.
    *
@@ -290,6 +282,14 @@ public abstract class SolrRequest<T> implements 
Serializable {
     return null;
   }
 
+  /**
+   * @deprecated Please use {@link SolrRequest#getContentWriter(String)} 
instead.
+   */
+  @Deprecated(since = "7.2")
+  public Collection<ContentStream> getContentStreams() throws IOException {
+    return null;
+  }
+
   /**
    * Create a new SolrResponse to hold the response from the server. If the 
response extends {@link
    * SolrResponse}, then there's no need to use the arguments, as {@link
diff --git 
a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpJdkSolrClient.java 
b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpJdkSolrClient.java
index 722b241cef6..7e328150119 100644
--- 
a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpJdkSolrClient.java
+++ 
b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpJdkSolrClient.java
@@ -34,7 +34,6 @@ import java.net.http.HttpResponse;
 import java.net.http.HttpTimeoutException;
 import java.time.Duration;
 import java.time.temporal.ChronoUnit;
-import java.util.Collection;
 import java.util.HashMap;
 import java.util.Locale;
 import java.util.Map;
@@ -54,7 +53,6 @@ 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.util.ContentStream;
 import org.apache.solr.common.util.ExecutorUtil;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.ObjectReleaseTracker;
@@ -293,14 +291,8 @@ public class HttpJdkSolrClient extends HttpSolrClient {
       ModifiableSolrParams queryParams)
       throws IOException, URISyntaxException {
 
-    final RequestWriter.ContentWriter contentWriter = 
requestWriter.getContentWriter(solrRequest);
-
-    final Collection<ContentStream> streams;
-    if (contentWriter == null) {
-      streams = requestWriter.getContentStreams(solrRequest);
-    } else {
-      streams = null;
-    }
+    final RequestWriter.ContentWriter contentWriter =
+        requestWriter.getContentWriterOrStreams(solrRequest);
 
     String contentType = "application/x-www-form-urlencoded";
     if (contentWriter != null && contentWriter.getContentType() != null) {
@@ -308,7 +300,7 @@ public class HttpJdkSolrClient extends HttpSolrClient {
     }
     reqb.header("Content-Type", contentType);
 
-    if (isMultipart(streams)) {
+    if (isMultipart(contentWriter)) {
       throw new UnsupportedOperationException("This client does not support 
multipart.");
     }
 
@@ -323,14 +315,6 @@ public class HttpJdkSolrClient extends HttpSolrClient {
       bodyPublisher =
           HttpRequest.BodyPublishers.ofInputStream(
               () -> pReq.beginContentWriting(contentWriter, 
this.requestBodyExecutor));
-    } else if (streams != null && streams.size() == 1) {
-      boolean success = maybeTryHeadRequest(url);
-      if (!success) {
-        reqb.version(HttpClient.Version.HTTP_1_1);
-      }
-
-      InputStream is = streams.iterator().next().getStream();
-      bodyPublisher = HttpRequest.BodyPublishers.ofInputStream(() -> is);
     } else {
       // move any params specified in urlParamNames or solrRequest from 
queryParams into urlParams
       ModifiableSolrParams urlParams = calculateQueryParams(urlParamNames, 
queryParams);
diff --git 
a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpSolrClient.java 
b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpSolrClient.java
index 4f8918f42cb..fabd2c365ed 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpSolrClient.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/HttpSolrClient.java
@@ -53,7 +53,6 @@ import org.apache.solr.common.params.CoreAdminParams;
 import org.apache.solr.common.params.ModifiableSolrParams;
 import org.apache.solr.common.params.ShardParams;
 import org.apache.solr.common.params.SolrParams;
-import org.apache.solr.common.util.ContentStream;
 import org.apache.solr.common.util.NamedList;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -164,14 +163,8 @@ public abstract class HttpSolrClient extends SolrClient {
     return params;
   }
 
-  protected boolean isMultipart(Collection<ContentStream> streams) {
-    boolean isMultipart = false;
-    if (streams != null) {
-      boolean hasNullStreamName = false;
-      hasNullStreamName = streams.stream().anyMatch(cs -> cs.getName() == 
null);
-      isMultipart = !hasNullStreamName && streams.size() > 1;
-    }
-    return isMultipart;
+  protected boolean isMultipart(RequestWriter.ContentWriter contentWriter) {
+    return contentWriter instanceof RequestWriter.MultipartContentWriter;
   }
 
   protected ModifiableSolrParams calculateQueryParams(
@@ -192,10 +185,9 @@ public abstract class HttpSolrClient extends SolrClient {
   }
 
   protected void validateGetRequest(SolrRequest<?> solrRequest) throws 
IOException {
-    RequestWriter.ContentWriter contentWriter = 
requestWriter.getContentWriter(solrRequest);
-    Collection<ContentStream> streams =
-        contentWriter == null ? requestWriter.getContentStreams(solrRequest) : 
null;
-    if (contentWriter != null || streams != null) {
+    RequestWriter.ContentWriter contentWriter =
+        requestWriter.getContentWriterOrStreams(solrRequest);
+    if (contentWriter != null) {
       throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "GET can't 
send streams!");
     }
   }
diff --git 
a/solr/solrj/src/java/org/apache/solr/client/solrj/request/ConfigSetAdminRequest.java
 
b/solr/solrj/src/java/org/apache/solr/client/solrj/request/ConfigSetAdminRequest.java
index 2154f63ba12..c70a65510dc 100644
--- 
a/solr/solrj/src/java/org/apache/solr/client/solrj/request/ConfigSetAdminRequest.java
+++ 
b/solr/solrj/src/java/org/apache/solr/client/solrj/request/ConfigSetAdminRequest.java
@@ -20,6 +20,7 @@ import static org.apache.solr.common.params.CommonParams.NAME;
 
 import java.io.IOException;
 import java.io.OutputStream;
+import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.Collection;
 import java.util.Map;
@@ -31,7 +32,6 @@ import 
org.apache.solr.common.params.ConfigSetParams.ConfigSetAction;
 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.ContentStreamBase.FileStream;
 import org.apache.solr.common.util.NamedList;
 
 /**
@@ -140,9 +140,15 @@ public abstract class ConfigSetAdminRequest<
    * file to upload if {@link #setFilePath} is being used.
    */
   public static class Upload extends ConfigSetSpecificAdminRequest<Upload> {
-    private static final String NO_STREAM_ERROR = "There must be a 
ContentStream or File to Upload";
+    private static final String NO_STREAM_ERROR = "There must be content or a 
File to Upload";
 
+    /**
+     * @deprecated Use {@link #setUploadContent}; this is only set by {@link 
#setUploadStream}.
+     */
+    @Deprecated(since = "10.1")
     protected ContentStream stream;
+
+    protected RequestWriter.ContentWriter contentWriter;
     protected String filePath;
 
     protected Boolean overwrite;
@@ -180,13 +186,22 @@ public abstract class ConfigSetAdminRequest<
      * <p>This should either be a ZIP file containing the entire configset 
being uploaded, or an
      * individual file to upload into an existing configset if {@link 
#setFilePath} is being used.
      *
-     * @see #setUploadStream
+     * @see #setUploadContent
      */
     public final Upload setUploadFile(final Path file, final String 
contentType)
         throws IOException {
-      final FileStream fileStream = new FileStream(file);
-      fileStream.setContentType(contentType);
-      return setUploadStream(fileStream);
+      return setUploadContent(
+          new RequestWriter.ContentWriter() {
+            @Override
+            public void write(OutputStream os) throws IOException {
+              Files.copy(file, os);
+            }
+
+            @Override
+            public String getContentType() {
+              return contentType;
+            }
+          });
     }
 
     /**
@@ -220,48 +235,51 @@ public abstract class ConfigSetAdminRequest<
     }
 
     /**
-     * Specify the ContentStream to upload.
+     * Specify the content to upload.
      *
      * <p>This should either be a ZIP file containing the entire configset 
being uploaded, or an
      * individual file to upload into an existing configset if {@link 
#setFilePath} is being used.
      *
-     * @see #setUploadStream
+     * @see #setUploadFile
      */
+    public final Upload setUploadContent(final RequestWriter.ContentWriter 
contentWriter) {
+      this.contentWriter = contentWriter;
+      return getThis();
+    }
+
+    /**
+     * Specify the ContentStream to upload.
+     *
+     * @deprecated Use {@link #setUploadContent}.
+     */
+    @Deprecated(since = "10.1")
     public final Upload setUploadStream(final ContentStream stream) {
       this.stream = stream;
       return getThis();
     }
 
     @Override
+    @Deprecated(since = "10.1")
     public Collection<ContentStream> getContentStreams() throws IOException {
       return java.util.List.of(stream);
     }
 
     @Override
     public RequestWriter.ContentWriter getContentWriter(String expectedType) {
-      if (null == stream) {
+      if (null == contentWriter && null != stream) {
+        return super.getContentWriter(expectedType);
+      }
+      if (null == contentWriter) {
         throw new NullPointerException(NO_STREAM_ERROR);
       }
-      return new RequestWriter.ContentWriter() {
-        @Override
-        public void write(OutputStream os) throws IOException {
-          try (var inStream = stream.getStream()) {
-            inStream.transferTo(os);
-          }
-        }
-
-        @Override
-        public String getContentType() {
-          return stream.getContentType();
-        }
-      };
+      return contentWriter;
     }
 
     @Override
     public SolrParams getParams() {
       ModifiableSolrParams params = new 
ModifiableSolrParams(super.getParams());
 
-      if (null == stream) {
+      if (null == contentWriter && null == stream) {
         throw new NullPointerException(NO_STREAM_ERROR);
       }
 
diff --git 
a/solr/solrj/src/java/org/apache/solr/client/solrj/request/ContentStreamUpdateRequest.java
 
b/solr/solrj/src/java/org/apache/solr/client/solrj/request/ContentStreamUpdateRequest.java
index 9c19dfa64af..abb3749a97b 100644
--- 
a/solr/solrj/src/java/org/apache/solr/client/solrj/request/ContentStreamUpdateRequest.java
+++ 
b/solr/solrj/src/java/org/apache/solr/client/solrj/request/ContentStreamUpdateRequest.java
@@ -17,7 +17,6 @@
 package org.apache.solr.client.solrj.request;
 
 import java.io.IOException;
-import java.io.OutputStream;
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -32,6 +31,10 @@ import org.apache.solr.common.util.ContentStreamBase;
  * <p>See 
https://solr.apache.org/guide/solr/latest/indexing-guide/indexing-with-tika.html<br>
  * See 
https://solr.apache.org/guide/solr/latest/indexing-guide/indexing-with-update-handlers.html
  */
+/**
+ * @deprecated Use {@link ContentWriterUpdateRequest}.
+ */
+@Deprecated(since = "10.1")
 public class ContentStreamUpdateRequest extends AbstractUpdateRequest {
   List<ContentStream> contentStreams;
 
@@ -48,26 +51,6 @@ public class ContentStreamUpdateRequest extends 
AbstractUpdateRequest {
     return contentStreams;
   }
 
-  @Override
-  public RequestWriter.ContentWriter getContentWriter(String expectedType) {
-    if (contentStreams == null || contentStreams.isEmpty() || 
contentStreams.size() > 1)
-      return null;
-    ContentStream stream = contentStreams.get(0);
-    return new RequestWriter.ContentWriter() {
-      @Override
-      public void write(OutputStream os) throws IOException {
-        try (var inStream = stream.getStream()) {
-          inStream.transferTo(os);
-        }
-      }
-
-      @Override
-      public String getContentType() {
-        return stream.getContentType();
-      }
-    };
-  }
-
   /**
    * Add a File to the {@link org.apache.solr.common.util.ContentStream}s.
    *
diff --git 
a/solr/solrj/src/java/org/apache/solr/client/solrj/request/ContentWriterUpdateRequest.java
 
b/solr/solrj/src/java/org/apache/solr/client/solrj/request/ContentWriterUpdateRequest.java
new file mode 100644
index 00000000000..38e87d351e1
--- /dev/null
+++ 
b/solr/solrj/src/java/org/apache/solr/client/solrj/request/ContentWriterUpdateRequest.java
@@ -0,0 +1,83 @@
+/*
+ * 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.request;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.solr.client.solrj.request.RequestWriter.ContentWriter;
+import org.apache.solr.client.solrj.request.RequestWriter.NamedPart;
+
+/**
+ * Uploads one or more content parts to Solr Cell or another handler that 
consumes request content
+ * (CSV, XML, JSON). A single part is sent as the request body; several parts 
are sent as {@code
+ * multipart/form-data}.
+ *
+ * <p>See 
https://solr.apache.org/guide/solr/latest/indexing-guide/indexing-with-tika.html<br>
+ * See 
https://solr.apache.org/guide/solr/latest/indexing-guide/indexing-with-update-handlers.html
+ */
+public class ContentWriterUpdateRequest extends AbstractUpdateRequest {
+  private final List<NamedPart> parts = new ArrayList<>();
+
+  public ContentWriterUpdateRequest(String path) {
+    super(METHOD.POST, path);
+  }
+
+  @Override
+  public ContentWriter getContentWriter(String expectedType) {
+    if (parts.isEmpty()) return null;
+    if (parts.size() == 1) {
+      return parts.get(0).writer;
+    }
+    return new RequestWriter.MultipartContentWriter() {
+      @Override
+      public List<NamedPart> getParts() {
+        return List.copyOf(parts);
+      }
+    };
+  }
+
+  /** Adds a part written from {@code writer}, named {@code name} when sent as 
multipart. */
+  public void addPart(String name, ContentWriter writer) {
+    parts.add(new NamedPart(name, writer));
+  }
+
+  /** Adds the file's bytes as a part, named after the file. */
+  public void addFile(Path file, String contentType) {
+    addPart(
+        file.getFileName().toString(),
+        new ContentWriter() {
+          @Override
+          public void write(OutputStream os) throws IOException {
+            Files.copy(file, os);
+          }
+
+          @Override
+          public String getContentType() {
+            return contentType;
+          }
+        });
+  }
+
+  /** Adds {@code content}, encoded as UTF-8, as the request's only part. */
+  public void addContentWithType(String content, String contentType) {
+    addPart(null, new RequestWriter.StringPayloadContentWriter(content, 
contentType));
+  }
+}
diff --git 
a/solr/solrj/src/java/org/apache/solr/client/solrj/request/JavaBinRequestWriter.java
 
b/solr/solrj/src/java/org/apache/solr/client/solrj/request/JavaBinRequestWriter.java
index 0d3ada79b3d..803ea981951 100644
--- 
a/solr/solrj/src/java/org/apache/solr/client/solrj/request/JavaBinRequestWriter.java
+++ 
b/solr/solrj/src/java/org/apache/solr/client/solrj/request/JavaBinRequestWriter.java
@@ -53,13 +53,13 @@ public class JavaBinRequestWriter extends RequestWriter {
   }
 
   @Override
+  @Deprecated(since = "7.6")
   public Collection<ContentStream> getContentStreams(SolrRequest<?> req) 
throws IOException {
     if (req instanceof UpdateRequest updateRequest) {
       if (isEmpty(updateRequest)) return null;
       throw new RuntimeException("This Should not happen");
-    } else {
-      return req.getContentStreams();
     }
+    return req.getContentStreams();
   }
 
   @Override
diff --git 
a/solr/solrj/src/java/org/apache/solr/client/solrj/request/RequestWriter.java 
b/solr/solrj/src/java/org/apache/solr/client/solrj/request/RequestWriter.java
index f57cf2bafd7..967672ea748 100644
--- 
a/solr/solrj/src/java/org/apache/solr/client/solrj/request/RequestWriter.java
+++ 
b/solr/solrj/src/java/org/apache/solr/client/solrj/request/RequestWriter.java
@@ -43,20 +43,91 @@ public abstract class RequestWriter {
   }
 
   /**
-   * To be implemented by subclasses to serialize update requests into the 
appropriate format.
-   *
-   * <p>If this method returns null, {@link
-   * 
org.apache.solr.client.solrj.request.RequestWriter#getContentStreams(SolrRequest)}
 is then
-   * invoked to get content.
+   * A {@code multipart/form-data} body made of several named parts; build it 
from {@link
+   * #getParts}.
    */
+  public interface MultipartContentWriter extends ContentWriter {
+
+    List<NamedPart> getParts();
+
+    @Override
+    default void write(OutputStream os) throws IOException {
+      throw new UnsupportedOperationException(
+          "Multipart content is written per-part via getParts(), not 
write(OutputStream)");
+    }
+
+    @Override
+    default String getContentType() {
+      return "multipart/form-data";
+    }
+  }
+
+  /** One named part of a {@link MultipartContentWriter}'s body. */
+  public static final class NamedPart {
+    public final String name;
+    public final ContentWriter writer;
+
+    public NamedPart(String name, ContentWriter writer) {
+      this.name = name;
+      this.writer = writer;
+    }
+  }
+
+  /** To be implemented by subclasses to serialize update requests into the 
appropriate format. */
   public abstract ContentWriter getContentWriter(SolrRequest<?> req);
 
   /**
-   * @deprecated Use {@link #getContentWriter(SolrRequest)}.
+   * @deprecated Use {@link #getContentWriter(SolrRequest)}; this is consulted 
only when that
+   *     returns null.
    */
   @Deprecated(since = "7.6")
-  public abstract Collection<ContentStream> getContentStreams(SolrRequest<?> 
req)
-      throws IOException;
+  public Collection<ContentStream> getContentStreams(SolrRequest<?> req) 
throws IOException {
+    return null;
+  }
+
+  /**
+   * The content to send for a request: {@link 
#getContentWriter(SolrRequest)}, else any content
+   * from the deprecated {@link #getContentStreams(SolrRequest)}, else null.
+   */
+  @SuppressWarnings("deprecation")
+  public final ContentWriter getContentWriterOrStreams(SolrRequest<?> req) 
throws IOException {
+    ContentWriter writer = getContentWriter(req);
+    return writer != null ? writer : contentWriterOf(getContentStreams(req));
+  }
+
+  /**
+   * Adapts streams to a writer: multipart when there are several, all named; 
else the first.
+   *
+   * @return null if there are none
+   */
+  @Deprecated(since = "10.1")
+  public static ContentWriter contentWriterOf(Collection<ContentStream> 
streams) {
+    if (streams == null || streams.isEmpty()) {
+      return null;
+    }
+    if (streams.size() > 1 && streams.stream().noneMatch(cs -> cs.getName() == 
null)) {
+      List<NamedPart> parts =
+          streams.stream().map(cs -> new NamedPart(cs.getName(), 
contentWriterOf(cs))).toList();
+      return (MultipartContentWriter) () -> parts;
+    }
+    return contentWriterOf(streams.iterator().next());
+  }
+
+  private static ContentWriter contentWriterOf(ContentStream stream) {
+    return new ContentWriter() {
+      @Override
+      public void write(OutputStream os) throws IOException {
+        try (var in = stream.getStream()) {
+          in.transferTo(os);
+        }
+      }
+
+      @Override
+      public String getContentType() {
+        return stream.getContentType();
+      }
+    };
+  }
 
   protected boolean isEmpty(UpdateRequest updateRequest) {
     return isNull(updateRequest.getDocuments())
diff --git 
a/solr/solrj/src/java/org/apache/solr/client/solrj/request/XMLRequestWriter.java
 
b/solr/solrj/src/java/org/apache/solr/client/solrj/request/XMLRequestWriter.java
index 3620fb988ee..f801c9b30c1 100644
--- 
a/solr/solrj/src/java/org/apache/solr/client/solrj/request/XMLRequestWriter.java
+++ 
b/solr/solrj/src/java/org/apache/solr/client/solrj/request/XMLRequestWriter.java
@@ -61,6 +61,7 @@ public class XMLRequestWriter extends RequestWriter {
   }
 
   @Override
+  @Deprecated(since = "7.6")
   public Collection<ContentStream> getContentStreams(SolrRequest<?> req) 
throws IOException {
     if (req instanceof UpdateRequest) {
       return null;
diff --git 
a/solr/solrj/src/java/org/apache/solr/client/solrj/util/ClientUtils.java 
b/solr/solrj/src/java/org/apache/solr/client/solrj/util/ClientUtils.java
index 32d8006ccca..01fecd1e6f8 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/util/ClientUtils.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/util/ClientUtils.java
@@ -47,7 +47,13 @@ public class ClientUtils {
 
   public static final String DEFAULT_PATH = "/select";
 
-  /** Take a string and make it an iterable ContentStream */
+  /**
+   * Take a string and make it an iterable ContentStream
+   *
+   * @deprecated ContentStream is deprecated; use a {@link
+   *     org.apache.solr.client.solrj.request.RequestWriter.ContentWriter}.
+   */
+  @Deprecated(since = "10.1")
   public static Collection<ContentStream> toContentStreams(
       final String str, final String contentType) {
     if (str == null) return null;
diff --git 
a/solr/solrj/src/java/org/apache/solr/common/util/CommandOperation.java 
b/solr/solrj/src/java/org/apache/solr/common/util/CommandOperation.java
index eeea5684324..91e4da4a2bc 100644
--- a/solr/solrj/src/java/org/apache/solr/common/util/CommandOperation.java
+++ b/solr/solrj/src/java/org/apache/solr/common/util/CommandOperation.java
@@ -28,7 +28,6 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import org.apache.solr.common.SolrException;
 import org.noggit.JSONParser;
 import org.noggit.ObjectBuilder;
 
@@ -297,47 +296,6 @@ public class CommandOperation {
     return new String(toJSON(Map.of(name, commandData)), 
StandardCharsets.UTF_8);
   }
 
-  public static List<CommandOperation> readCommands(
-      Iterable<ContentStream> streams, @SuppressWarnings({"rawtypes"}) 
NamedList resp)
-      throws IOException {
-    return readCommands(streams, resp, Set.of());
-  }
-
-  /**
-   * Read commands from request streams
-   *
-   * @param streams the streams
-   * @param resp solr query response
-   * @param singletonCommands , commands that cannot be repeated
-   * @return parsed list of commands
-   * @throws IOException if there is an error while parsing the stream
-   */
-  @SuppressWarnings({"unchecked"})
-  public static List<CommandOperation> readCommands(
-      Iterable<ContentStream> streams,
-      @SuppressWarnings({"rawtypes"}) NamedList resp,
-      Set<String> singletonCommands)
-      throws IOException {
-    if (streams == null) {
-      throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "missing 
content stream");
-    }
-    ArrayList<CommandOperation> ops = new ArrayList<>();
-    for (ContentStream stream : streams) {
-
-      if ("application/javabin".equals(stream.getContentType())) {
-        ops.addAll(parse(stream.getStream(), singletonCommands));
-      } else {
-        ops.addAll(parse(stream.getReader(), singletonCommands));
-      }
-    }
-    List<Map<String, Object>> errList = CommandOperation.captureErrors(ops);
-    if (!errList.isEmpty()) {
-      resp.add(CommandOperation.ERR_MSGS, errList);
-      return null;
-    }
-    return ops;
-  }
-
   public static List<CommandOperation> clone(List<CommandOperation> ops) {
     List<CommandOperation> opsCopy = new ArrayList<>(ops.size());
     for (CommandOperation op : ops) opsCopy.add(op.getCopy());
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 0b671fb81d4..f5b29e44ab2 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
@@ -27,7 +27,6 @@ import java.net.ConnectException;
 import java.net.SocketException;
 import java.time.Instant;
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -53,7 +52,6 @@ import org.apache.solr.common.SolrException;
 import org.apache.solr.common.cloud.ClusterState;
 import org.apache.solr.common.cloud.DocCollection;
 import org.apache.solr.common.params.ModifiableSolrParams;
-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;
@@ -457,11 +455,6 @@ public class CloudSolrClientCacheTest extends 
SolrTestCaseJ4 {
       return params;
     }
 
-    @Override
-    public Collection<ContentStream> getContentStreams() {
-      return null;
-    }
-
     @Override
     protected NamedList<Object> createResponse(NamedList<Object> namedList) {
       return namedList;
diff --git 
a/solr/solrj/src/test/org/apache/solr/client/solrj/request/TestConfigSetAdminRequest.java
 
b/solr/solrj/src/test/org/apache/solr/client/solrj/request/TestConfigSetAdminRequest.java
index 357e061b17b..51c5a7bc5ee 100644
--- 
a/solr/solrj/src/test/org/apache/solr/client/solrj/request/TestConfigSetAdminRequest.java
+++ 
b/solr/solrj/src/test/org/apache/solr/client/solrj/request/TestConfigSetAdminRequest.java
@@ -48,13 +48,11 @@ public class TestConfigSetAdminRequest extends 
SolrTestCaseJ4 {
     verifyException(upload, "ConfigSet");
 
     upload.setConfigSetName("name");
-    verifyException(upload, "There must be a ContentStream");
+    verifyException(upload, "There must be content");
 
     upload.setUploadFile(tmpFile, "application/zip");
 
-    assertEquals(1, upload.getContentStreams().size());
-    assertEquals(
-        "application/zip", 
upload.getContentStreams().stream().findFirst().get().getContentType());
+    assertEquals("application/zip", 
upload.getContentWriter(null).getContentType());
 
     assertNull(upload.getParams().get(ConfigSetParams.FILE_PATH));
     assertNull(upload.getParams().get(ConfigSetParams.OVERWRITE));
@@ -65,9 +63,7 @@ public class TestConfigSetAdminRequest extends SolrTestCaseJ4 
{
         .setFilePath("solrconfig.xml")
         .setOverwrite(true);
 
-    assertEquals(1, upload.getContentStreams().size());
-    assertEquals(
-        "application/xml", 
upload.getContentStreams().stream().findFirst().get().getContentType());
+    assertEquals("application/xml", 
upload.getContentWriter(null).getContentType());
 
     assertEquals("solrconfig.xml", 
upload.getParams().get(ConfigSetParams.FILE_PATH));
     assertEquals("true", upload.getParams().get(ConfigSetParams.OVERWRITE));
diff --git 
a/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/DirectJsonQueryRequestFacetingEmbeddedTest.java
 
b/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/DirectJsonQueryRequestFacetingEmbeddedTest.java
index 0717ab90c7d..93d2e2df946 100644
--- 
a/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/DirectJsonQueryRequestFacetingEmbeddedTest.java
+++ 
b/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/DirectJsonQueryRequestFacetingEmbeddedTest.java
@@ -24,7 +24,7 @@ import org.apache.solr.SolrTestCase;
 import org.apache.solr.SolrTestCaseJ4.SuppressSSL;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.request.AbstractUpdateRequest;
-import org.apache.solr.client.solrj.request.ContentStreamUpdateRequest;
+import org.apache.solr.client.solrj.request.ContentWriterUpdateRequest;
 import org.apache.solr.client.solrj.response.QueryResponse;
 import org.apache.solr.client.solrj.response.UpdateResponse;
 import org.apache.solr.client.solrj.response.json.BucketJsonFacet;
@@ -60,7 +60,7 @@ public class DirectJsonQueryRequestFacetingEmbeddedTest 
extends SolrTestCase {
 
     SolrClient client = solrTestRule.getSolrClient();
 
-    ContentStreamUpdateRequest up = new ContentStreamUpdateRequest("/update");
+    ContentWriterUpdateRequest up = new ContentWriterUpdateRequest("/update");
     up.setParam("collection", client.getDefaultCollection());
     up.addFile(getFile("solrj/techproducts.xml"), "application/xml");
     up.setAction(AbstractUpdateRequest.ACTION.COMMIT, true, true);
diff --git 
a/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/DirectJsonQueryRequestFacetingIntegrationTest.java
 
b/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/DirectJsonQueryRequestFacetingIntegrationTest.java
index 6e0c8b8891b..685a8dfee4a 100644
--- 
a/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/DirectJsonQueryRequestFacetingIntegrationTest.java
+++ 
b/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/DirectJsonQueryRequestFacetingIntegrationTest.java
@@ -20,7 +20,7 @@ package org.apache.solr.client.solrj.request.json;
 import java.util.List;
 import org.apache.solr.client.solrj.request.AbstractUpdateRequest;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
-import org.apache.solr.client.solrj.request.ContentStreamUpdateRequest;
+import org.apache.solr.client.solrj.request.ContentWriterUpdateRequest;
 import org.apache.solr.client.solrj.response.QueryResponse;
 import org.apache.solr.client.solrj.response.UpdateResponse;
 import org.apache.solr.client.solrj.response.json.BucketJsonFacet;
@@ -51,7 +51,7 @@ public class DirectJsonQueryRequestFacetingIntegrationTest 
extends SolrCloudTest
     CollectionAdminRequest.createCollection(COLLECTION_NAME, CONFIG_NAME, 1, 1)
         .process(cluster.getSolrClient());
 
-    ContentStreamUpdateRequest up = new ContentStreamUpdateRequest("/update");
+    ContentWriterUpdateRequest up = new ContentWriterUpdateRequest("/update");
     up.setParam("collection", COLLECTION_NAME);
     up.addFile(getFile("solrj/techproducts.xml"), "application/xml");
     up.setAction(AbstractUpdateRequest.ACTION.COMMIT, true, true);
diff --git 
a/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/JsonQueryRequestFacetingIntegrationTest.java
 
b/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/JsonQueryRequestFacetingIntegrationTest.java
index b6ed7c24958..24c0c5777b8 100644
--- 
a/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/JsonQueryRequestFacetingIntegrationTest.java
+++ 
b/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/JsonQueryRequestFacetingIntegrationTest.java
@@ -25,7 +25,7 @@ import java.util.List;
 import java.util.Map;
 import org.apache.solr.client.solrj.request.AbstractUpdateRequest;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
-import org.apache.solr.client.solrj.request.ContentStreamUpdateRequest;
+import org.apache.solr.client.solrj.request.ContentWriterUpdateRequest;
 import org.apache.solr.client.solrj.response.QueryResponse;
 import org.apache.solr.client.solrj.response.UpdateResponse;
 import org.apache.solr.client.solrj.response.json.BucketJsonFacet;
@@ -58,7 +58,7 @@ public class JsonQueryRequestFacetingIntegrationTest extends 
SolrCloudTestCase {
     CollectionAdminRequest.createCollection(COLLECTION_NAME, CONFIG_NAME, 1, 1)
         .process(cluster.getSolrClient());
 
-    ContentStreamUpdateRequest up = new ContentStreamUpdateRequest("/update");
+    ContentWriterUpdateRequest up = new ContentWriterUpdateRequest("/update");
     up.setParam("collection", COLLECTION_NAME);
     up.addFile(getFile("solrj/techproducts.xml"), "application/xml");
     up.setAction(AbstractUpdateRequest.ACTION.COMMIT, true, true);
diff --git 
a/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/JsonQueryRequestIntegrationTest.java
 
b/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/JsonQueryRequestIntegrationTest.java
index b43310e8fe1..02e46872560 100644
--- 
a/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/JsonQueryRequestIntegrationTest.java
+++ 
b/solr/solrj/src/test/org/apache/solr/client/solrj/request/json/JsonQueryRequestIntegrationTest.java
@@ -24,7 +24,7 @@ import java.util.List;
 import java.util.Map;
 import org.apache.solr.client.solrj.request.AbstractUpdateRequest;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
-import org.apache.solr.client.solrj.request.ContentStreamUpdateRequest;
+import org.apache.solr.client.solrj.request.ContentWriterUpdateRequest;
 import org.apache.solr.client.solrj.response.QueryResponse;
 import org.apache.solr.client.solrj.response.UpdateResponse;
 import org.apache.solr.cloud.SolrCloudTestCase;
@@ -55,7 +55,7 @@ public class JsonQueryRequestIntegrationTest extends 
SolrCloudTestCase {
     CollectionAdminRequest.createCollection(COLLECTION_NAME, CONFIG_NAME, 1, 1)
         .process(cluster.getSolrClient());
 
-    ContentStreamUpdateRequest up = new ContentStreamUpdateRequest("/update");
+    ContentWriterUpdateRequest up = new ContentWriterUpdateRequest("/update");
     up.setParam("collection", COLLECTION_NAME);
     up.addFile(getFile("solrj/books.csv"), "application/csv");
     up.setAction(AbstractUpdateRequest.ACTION.COMMIT, true, true);
diff --git a/solr/test-framework/src/java/org/apache/solr/util/TestHarness.java 
b/solr/test-framework/src/java/org/apache/solr/util/TestHarness.java
index f76dcc9c3b0..0f53c315cde 100644
--- a/solr/test-framework/src/java/org/apache/solr/util/TestHarness.java
+++ b/solr/test-framework/src/java/org/apache/solr/util/TestHarness.java
@@ -26,12 +26,11 @@ import java.util.Map;
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.client.solrj.embedded.EmbeddedSolrServer;
 import org.apache.solr.client.solrj.impl.SolrZkClientTimeout;
-import org.apache.solr.client.solrj.request.ContentStreamUpdateRequest;
+import org.apache.solr.client.solrj.request.ContentWriterUpdateRequest;
 import org.apache.solr.client.solrj.response.InputStreamResponseParser;
 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.util.ContentStreamBase;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.core.CloudConfig;
 import org.apache.solr.core.CoreContainer;
@@ -265,8 +264,8 @@ public class TestHarness extends BaseTestHarness {
       assert null != mdcSnap; // prevent compiler warning of unused var
 
       EmbeddedSolrServer server = new EmbeddedSolrServer(getCoreContainer(), 
getCore().getName());
-      ContentStreamUpdateRequest xmlRequest = new 
ContentStreamUpdateRequest("/update");
-      xmlRequest.addContentStream(new ContentStreamBase.StringStream(xml, 
"text/xml"));
+      ContentWriterUpdateRequest xmlRequest = new 
ContentWriterUpdateRequest("/update");
+      xmlRequest.addContentWithType(xml, "text/xml");
 
       // Request XML response format and use InputStreamResponseParser
       xmlRequest.getParams().add("wt", "xml");

Reply via email to