You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@solr.apache.org by md...@apache.org on 2021/11/16 17:59:09 UTC

[solr] branch main updated: SOLR-15784 Remove SolrJ dep on commons-io (#407)

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

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


The following commit(s) were added to refs/heads/main by this push:
     new bfee656  SOLR-15784 Remove SolrJ dep on commons-io (#407)
bfee656 is described below

commit bfee65681ec6c4305ba6694754e32bc9a0d8c131
Author: Mike Drob <md...@apache.org>
AuthorDate: Tue Nov 16 11:59:02 2021 -0600

    SOLR-15784 Remove SolrJ dep on commons-io (#407)
---
 gradle/validation/forbidden-apis.gradle            | 23 +++----
 gradle/validation/forbidden-apis/defaults.all.txt  |  6 ++
 solr/CHANGES.txt                                   |  2 +
 .../apache/solr/schema/TestICUCollationField.java  |  7 +--
 .../schema/TestICUCollationFieldDocValues.java     |  8 +--
 .../org/apache/solr/filestore/PackageStoreAPI.java | 14 +++--
 .../apache/solr/response/RawResponseWriter.java    |  5 +-
 .../java/org/apache/solr/servlet/HttpSolrCall.java |  3 +-
 .../HTMLStripFieldUpdateProcessorFactory.java      |  2 +-
 .../solr/core/BlobRepositoryMockingTest.java       |  4 +-
 .../apache/solr/request/TestRemoteStreaming.java   |  2 +-
 .../transform/TestSubQueryTransformerDistrib.java  |  7 +--
 solr/solrj/build.gradle                            |  2 +-
 .../solr/client/solrj/impl/Http2SolrClient.java    |  8 +--
 .../solr/client/solrj/impl/HttpSolrClient.java     |  9 +--
 .../solr/client/solrj/impl/NoOpResponseParser.java |  6 +-
 .../solrj/request/ConfigSetAdminRequest.java       |  4 +-
 .../solrj/request/ContentStreamUpdateRequest.java  |  3 +-
 .../solrj/request/MultiContentWriterRequest.java   |  2 +-
 .../solrj/request/StreamingUpdateRequest.java      |  4 +-
 .../solr/client/solrj/request/V2Request.java       |  3 +-
 .../apache/solr/common/EmptyEntityResolver.java    |  8 +--
 .../org/apache/solr/common/cloud/SolrZkClient.java |  8 +--
 .../apache/solr/common/util/ContentStreamTest.java | 12 ++--
 .../solr/common/util/TestFastJavabinDecoder.java   |  3 +-
 .../apache/solr/EmbeddedSolrServerTestBase.java    | 32 +---------
 .../java/org/apache/solr/SolrJettyTestBase.java    | 45 ++++++--------
 .../src/java/org/apache/solr/SolrTestCaseHS.java   | 12 ++--
 .../src/java/org/apache/solr/SolrTestCaseJ4.java   | 72 ++++++++++------------
 .../collections/AbstractIncrementalBackupTest.java |  5 +-
 .../java/org/apache/solr/util/DirectoryUtil.java   | 57 +++++++++++++++++
 31 files changed, 189 insertions(+), 189 deletions(-)

diff --git a/gradle/validation/forbidden-apis.gradle b/gradle/validation/forbidden-apis.gradle
index 4965182..2a79a4d 100644
--- a/gradle/validation/forbidden-apis.gradle
+++ b/gradle/validation/forbidden-apis.gradle
@@ -56,37 +56,34 @@ allprojects { prj ->
         .collect { id -> "${id.name}-unsafe-${id.version}" as String }
     }
 
-    // Configure defaults for sourceSets.main
-    forbiddenApisMain {
+    // Configure defaults for all sourceSets (main and test)
+    forbiddenApis {
       bundledSignatures += [
           'jdk-unsafe',
           'jdk-deprecated',
           'jdk-non-portable',
           'jdk-reflection',
-          'jdk-system-out',
       ]
 
       suppressAnnotations += [
           "**.SuppressForbidden"
       ]
+
+      ignoreSignaturesOfMissingClasses = true
     }
 
-    // Configure defaults for sourceSets.test
-    forbiddenApisTest {
+    // Configure defaults for sourceSets.main
+    forbiddenApisMain {
       bundledSignatures += [
-          'jdk-unsafe',
-          'jdk-deprecated',
-          'jdk-non-portable',
-          'jdk-reflection',
+          'jdk-system-out'
       ]
+    }
 
+    // Configure defaults for sourceSets.test
+    forbiddenApisTest {
       signaturesFiles = files(
           file("${resources}/defaults.tests.txt")
       )
-
-      suppressAnnotations += [
-          "**.SuppressForbidden"
-      ]
     }
 
     // Configure defaults for sourceSets.tools (if present).
diff --git a/gradle/validation/forbidden-apis/defaults.all.txt b/gradle/validation/forbidden-apis/defaults.all.txt
index 51643a0..e04c085 100644
--- a/gradle/validation/forbidden-apis/defaults.all.txt
+++ b/gradle/validation/forbidden-apis/defaults.all.txt
@@ -91,3 +91,9 @@ java.lang.System#currentTimeMillis()
 
 @defaultMessage Use slf4j classes instead
 java.util.logging.**
+
+@defaultMessage Use InputStream.transferTo(OutputStream) or Reader.transferTo(Writer) instead
+org.apache.commons.io.IOUtils#copy(java.io.InputStream, java.io.OutputStream)
+org.apache.commons.io.IOUtils#copyLarge(java.io.InputStream, java.io.OutputStream)
+org.apache.commons.io.IOUtils#copy(java.io.Reader, java.io.Writer)
+org.apache.commons.io.IOUtils#copyLarge(java.io.Reader, java.io.Writer)
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index a3cdd5d..eba215c 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -398,6 +398,8 @@ Other Changes
 
 * SOLR-15774: Avoid weird off-by-one errors with Angular's 'chosen' select box directive for the security and schema-designer screens in Admin UI (Timothy Potter)
 
+* SOLR-15784: Remove SolrJ dependency on commons-io. (Mike Drob)
+
 Bug Fixes
 ---------------------
 * SOLR-14546: Fix for a relatively hard to hit issue in OverseerTaskProcessor that could lead to out of order execution
diff --git a/solr/contrib/analysis-extras/src/test/org/apache/solr/schema/TestICUCollationField.java b/solr/contrib/analysis-extras/src/test/org/apache/solr/schema/TestICUCollationField.java
index 6c4ecb5..8fdafa9 100644
--- a/solr/contrib/analysis-extras/src/test/org/apache/solr/schema/TestICUCollationField.java
+++ b/solr/contrib/analysis-extras/src/test/org/apache/solr/schema/TestICUCollationField.java
@@ -18,11 +18,10 @@ package org.apache.solr.schema;
 
 import java.io.ByteArrayInputStream;
 import java.io.File;
-import java.io.FileOutputStream;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
 
 import org.apache.commons.io.FileUtils;
-import org.apache.commons.io.IOUtils;
 import org.apache.lucene.analysis.util.FilesystemResourceLoader;
 import org.apache.lucene.util.ResourceLoader;
 import org.apache.solr.SolrTestCaseJ4;
@@ -86,9 +85,7 @@ public class TestICUCollationField extends SolrTestCaseJ4 {
     RuleBasedCollator tailoredCollator = new RuleBasedCollator(baseCollator.getRules() + DIN5007_2_tailorings);
     String tailoredRules = tailoredCollator.getRules();
     final String osFileName = "customrules.dat";
-    final FileOutputStream os = new FileOutputStream(new File(confDir, osFileName));
-    IOUtils.write(tailoredRules, os, "UTF-8");
-    os.close();
+    Files.writeString(confDir.toPath().resolve(osFileName), tailoredRules, StandardCharsets.UTF_8);
 
     assumeWorkingMockito();
 
diff --git a/solr/contrib/analysis-extras/src/test/org/apache/solr/schema/TestICUCollationFieldDocValues.java b/solr/contrib/analysis-extras/src/test/org/apache/solr/schema/TestICUCollationFieldDocValues.java
index 57b403a..2ebc72b 100644
--- a/solr/contrib/analysis-extras/src/test/org/apache/solr/schema/TestICUCollationFieldDocValues.java
+++ b/solr/contrib/analysis-extras/src/test/org/apache/solr/schema/TestICUCollationFieldDocValues.java
@@ -17,10 +17,10 @@
 package org.apache.solr.schema;
 
 import java.io.File;
-import java.io.FileOutputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
 
 import org.apache.commons.io.FileUtils;
-import org.apache.commons.io.IOUtils;
 import org.apache.solr.SolrTestCaseJ4;
 import org.junit.BeforeClass;
 
@@ -81,9 +81,7 @@ public class TestICUCollationFieldDocValues extends SolrTestCaseJ4 {
 
     RuleBasedCollator tailoredCollator = new RuleBasedCollator(baseCollator.getRules() + DIN5007_2_tailorings);
     String tailoredRules = tailoredCollator.getRules();
-    FileOutputStream os = new FileOutputStream(new File(confDir, "customrules.dat"));
-    IOUtils.write(tailoredRules, os, "UTF-8");
-    os.close();
+    Files.writeString(confDir.toPath().resolve("customrules.dat"), tailoredRules, StandardCharsets.UTF_8);
 
     return tmpFile.getAbsolutePath();
   }
diff --git a/solr/core/src/java/org/apache/solr/filestore/PackageStoreAPI.java b/solr/core/src/java/org/apache/solr/filestore/PackageStoreAPI.java
index 8cb414e..c934149 100644
--- a/solr/core/src/java/org/apache/solr/filestore/PackageStoreAPI.java
+++ b/solr/core/src/java/org/apache/solr/filestore/PackageStoreAPI.java
@@ -334,16 +334,18 @@ public class PackageStoreAPI {
       ModifiableSolrParams solrParams = new ModifiableSolrParams();
       solrParams.add(CommonParams.WT, FILE_STREAM);
       req.setParams(SolrParams.wrapDefaults(solrParams, req.getParams()));
-      rsp.add(FILE_STREAM, (SolrCore.RawWriter) os -> {
-        packageStore.get(path, (it) -> {
+      rsp.add(FILE_STREAM, (SolrCore.RawWriter) os ->
+        packageStore.get(path, it -> {
           try {
-            org.apache.commons.io.IOUtils.copy(it.getInputStream(), os);
+            InputStream inputStream = it.getInputStream();
+            if (inputStream != null) {
+              inputStream.transferTo(os);
+            }
           } catch (IOException e) {
             throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Error reading file" + path);
           }
-        }, false);
-
-      });
+        }, false)
+      );
     }
 
   }
diff --git a/solr/core/src/java/org/apache/solr/response/RawResponseWriter.java b/solr/core/src/java/org/apache/solr/response/RawResponseWriter.java
index 29fc6ec..62d1874 100644
--- a/solr/core/src/java/org/apache/solr/response/RawResponseWriter.java
+++ b/solr/core/src/java/org/apache/solr/response/RawResponseWriter.java
@@ -22,7 +22,6 @@ import java.io.OutputStream;
 import java.io.Reader;
 import java.io.Writer;
 
-import org.apache.commons.io.IOUtils;
 import org.apache.solr.common.util.ContentStream;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.request.SolrQueryRequest;
@@ -84,7 +83,7 @@ public class RawResponseWriter implements BinaryQueryResponseWriter {
       // copy the contents to the writer...
       ContentStream content = (ContentStream)obj;
       try(Reader reader = content.getReader()) {
-        IOUtils.copy( reader, writer );
+        reader.transferTo(writer);
       }
     } else {
       getBaseWriter( request ).write( writer, request, response );
@@ -98,7 +97,7 @@ public class RawResponseWriter implements BinaryQueryResponseWriter {
       // copy the contents to the writer...
       ContentStream content = (ContentStream)obj;
       try(InputStream in = content.getStream()) {
-        IOUtils.copy( in, out );
+        in.transferTo(out);
       }
     } else {
       QueryResponseWriterUtil.writeQueryResponse(out, 
diff --git a/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java b/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java
index a414102..e932184 100644
--- a/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java
+++ b/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java
@@ -42,7 +42,6 @@ import java.util.concurrent.TimeUnit;
 
 import io.opentracing.Span;
 import io.opentracing.tag.Tags;
-import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.http.Header;
 import org.apache.http.HeaderIterator;
@@ -754,7 +753,7 @@ public class HttpSolrCall {
         InputStream is = httpEntity.getContent();
         OutputStream os = resp.getOutputStream();
 
-        IOUtils.copyLarge(is, os);
+        is.transferTo(os);
       }
 
     } catch (IOException e) {
diff --git a/solr/core/src/java/org/apache/solr/update/processor/HTMLStripFieldUpdateProcessorFactory.java b/solr/core/src/java/org/apache/solr/update/processor/HTMLStripFieldUpdateProcessorFactory.java
index a22e382..e313b0e 100644
--- a/solr/core/src/java/org/apache/solr/update/processor/HTMLStripFieldUpdateProcessorFactory.java
+++ b/solr/core/src/java/org/apache/solr/update/processor/HTMLStripFieldUpdateProcessorFactory.java
@@ -68,7 +68,7 @@ public final class HTMLStripFieldUpdateProcessorFactory extends FieldMutatingUpd
         try {
           in = new HTMLStripCharFilter
               (new StringReader(s.toString()));
-          IOUtils.copy(in, result);
+          in.transferTo(result);
           return result.toString();
         } catch (IOException e) {
           // we tried and failed
diff --git a/solr/core/src/test/org/apache/solr/core/BlobRepositoryMockingTest.java b/solr/core/src/test/org/apache/solr/core/BlobRepositoryMockingTest.java
index 05ad8e1..26233d3 100644
--- a/solr/core/src/test/org/apache/solr/core/BlobRepositoryMockingTest.java
+++ b/solr/core/src/test/org/apache/solr/core/BlobRepositoryMockingTest.java
@@ -19,13 +19,13 @@ package org.apache.solr.core;
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.InputStreamReader;
 import java.io.StringWriter;
 import java.nio.ByteBuffer;
 import java.nio.charset.Charset;
 import java.util.Objects;
 import java.util.concurrent.ConcurrentHashMap;
 
-import org.apache.commons.io.IOUtils;
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.common.SolrException;
 import org.junit.Before;
@@ -169,7 +169,7 @@ public class BlobRepositoryMockingTest {
       public String[][] decode(InputStream inputStream) {
         StringWriter writer = new StringWriter();
         try {
-          IOUtils.copy(inputStream, writer, UTF8);
+          new InputStreamReader(inputStream, UTF8).transferTo(writer);
         } catch (IOException e) {
           throw new RuntimeException(e);
         }
diff --git a/solr/core/src/test/org/apache/solr/request/TestRemoteStreaming.java b/solr/core/src/test/org/apache/solr/request/TestRemoteStreaming.java
index 20e9a07..7389228 100644
--- a/solr/core/src/test/org/apache/solr/request/TestRemoteStreaming.java
+++ b/solr/core/src/test/org/apache/solr/request/TestRemoteStreaming.java
@@ -97,7 +97,7 @@ public class TestRemoteStreaming extends SolrJettyTestBase {
       InputStream inputStream = (InputStream) obj;
       try {
         StringWriter strWriter = new StringWriter();
-        IOUtils.copy(new InputStreamReader(inputStream, StandardCharsets.UTF_8),strWriter);
+        new InputStreamReader(inputStream, StandardCharsets.UTF_8).transferTo(strWriter);
         return strWriter.toString();
       } finally {
         IOUtils.closeQuietly(inputStream);
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 524dff9..6047df4 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
@@ -20,7 +20,7 @@ import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URL;
-import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
@@ -30,7 +30,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.Random;
 
-import org.apache.commons.io.IOUtils;
 import org.apache.solr.JSONTestUtil;
 import org.apache.solr.client.solrj.SolrServerException;
 import org.apache.solr.client.solrj.impl.CloudSolrClient;
@@ -160,11 +159,11 @@ public class TestSubQueryTransformerDistrib extends SolrCloudTestCase {
 
     try(final InputStream jsonResponse = node.openStream()){
       final ByteArrayOutputStream outBuffer = new ByteArrayOutputStream();
-      IOUtils.copy(jsonResponse, outBuffer);
+      jsonResponse.transferTo(outBuffer);
 
       final Object expected = ((SolrDocumentList) hits.get(0).getFieldValue("depts")).get(0).get("text_t");
       final String err = JSONTestUtil.match("/response/docs/[0]/depts/docs/[0]/text_t"
-          ,outBuffer.toString(Charset.forName("UTF-8").toString()),
+          ,outBuffer.toString(StandardCharsets.UTF_8),
           "\""+expected+"\"");
       assertNull(err,err);
     }
diff --git a/solr/solrj/build.gradle b/solr/solrj/build.gradle
index 35388b6..4384d5d 100644
--- a/solr/solrj/build.gradle
+++ b/solr/solrj/build.gradle
@@ -24,7 +24,6 @@ dependencies {
   implementation 'org.slf4j:slf4j-api'
   runtimeOnly 'org.slf4j:jcl-over-slf4j'
 
-  implementation 'commons-io:commons-io'
   implementation 'org.apache.commons:commons-math3'
 
   api 'org.eclipse.jetty.http2:http2-client'
@@ -44,6 +43,7 @@ dependencies {
   })
 
   testImplementation project(':solr:test-framework')
+  testImplementation 'commons-io:commons-io'
   testImplementation 'org.eclipse.jetty:jetty-webapp'
   testRuntimeOnly ('org.eclipse.jetty:jetty-alpn-java-server', {
     exclude group: "org.eclipse.jetty.alpn", module: "alpn-api"
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/Http2SolrClient.java b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/Http2SolrClient.java
index 768cb1f..26e9459 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/Http2SolrClient.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/Http2SolrClient.java
@@ -44,7 +44,6 @@ import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 
-import org.apache.commons.io.IOUtils;
 import org.apache.http.HttpStatus;
 import org.apache.http.entity.ContentType;
 import org.apache.solr.client.solrj.ResponseParser;
@@ -725,14 +724,15 @@ public class Http2SolrClient extends SolrClient {
         String procMimeType = ContentType.parse(procCt).getMimeType().trim().toLowerCase(Locale.ROOT);
         if (!procMimeType.equals(mimeType)) {
           // unexpected mime type
-          String msg = "Expected mime type " + procMimeType + " but got " + mimeType + ".";
+          String prefix = "Expected mime type " + procMimeType + " but got " + mimeType + ". ";
           String exceptionEncoding = encoding != null? encoding : FALLBACK_CHARSET.name();
           try {
-            msg = msg + " " + IOUtils.toString(is, exceptionEncoding);
+            ByteArrayOutputStream body = new ByteArrayOutputStream();
+            is.transferTo(body);
+            throw new RemoteSolrException(serverBaseUrl, httpStatus, prefix + body.toString(exceptionEncoding), null);
           } catch (IOException e) {
             throw new RemoteSolrException(serverBaseUrl, httpStatus, "Could not parse response with encoding " + exceptionEncoding, e);
           }
-          throw new RemoteSolrException(serverBaseUrl, httpStatus, msg, null);
         }
       }
 
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 fae16bf..a97e145 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
@@ -16,7 +16,6 @@
  */
 package org.apache.solr.client.solrj.impl;
 
-import org.apache.commons.io.IOUtils;
 import org.apache.http.Header;
 import org.apache.http.HttpEntity;
 import org.apache.http.HttpMessage;
@@ -63,6 +62,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.slf4j.MDC;
 
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -611,14 +611,15 @@ public class HttpSolrClient extends BaseHttpSolrClient {
           }
 
           // unexpected mime type
-          String msg = "Expected mime type " + procMimeType + " but got " + mimeType + ".";
+          String prefix = "Expected mime type " + procMimeType + " but got " + mimeType + ". ";
           Charset exceptionCharset = charset != null? charset : FALLBACK_CHARSET;
           try {
-            msg = msg + " " + IOUtils.toString(respBody, exceptionCharset);
+            ByteArrayOutputStream body = new ByteArrayOutputStream();
+            respBody.transferTo(body);
+            throw new RemoteSolrException(baseUrl, httpStatus, prefix + body.toString(exceptionCharset), null);
           } catch (IOException e) {
             throw new RemoteSolrException(baseUrl, httpStatus, "Could not parse response with encoding " + exceptionCharset, e);
           }
-          throw new RemoteSolrException(baseUrl, httpStatus, msg, null);
         }
       }
       
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/NoOpResponseParser.java b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/NoOpResponseParser.java
index dba26e3..f790c6c 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/NoOpResponseParser.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/NoOpResponseParser.java
@@ -16,13 +16,13 @@
  */
 package org.apache.solr.client.solrj.impl;
 
-import org.apache.commons.io.IOUtils;
 import org.apache.solr.client.solrj.ResponseParser;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.util.NamedList;
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.InputStreamReader;
 import java.io.Reader;
 import java.io.StringWriter;
 
@@ -54,7 +54,7 @@ public class NoOpResponseParser extends ResponseParser {
   public NamedList<Object> processResponse(Reader reader) {
     try {
       StringWriter writer = new StringWriter();
-      IOUtils.copy(reader, writer);
+      reader.transferTo(writer);
       String output = writer.toString();
       NamedList<Object> list = new NamedList<>();
       list.add("response", output);
@@ -68,7 +68,7 @@ public class NoOpResponseParser extends ResponseParser {
   public NamedList<Object> processResponse(InputStream body, String encoding) {
     try {
       StringWriter writer = new StringWriter();
-      IOUtils.copy(body, writer, encoding);
+      new InputStreamReader(body, encoding).transferTo(writer);
       String output = writer.toString();
       NamedList<Object> list = new NamedList<>();
       list.add("response", output);
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 6614fc6..8b9f093 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
@@ -38,8 +38,6 @@ import org.apache.solr.common.util.ContentStreamBase.FileStream;
 
 import static org.apache.solr.common.params.CommonParams.NAME;
 
-import org.apache.commons.io.IOUtils;
-
 /**
  * This class is experimental and subject to change.
  *
@@ -215,7 +213,7 @@ public abstract class ConfigSetAdminRequest
         @Override
         public void write(OutputStream os) throws IOException {
           try(var inStream = stream.getStream()) {
-            IOUtils.copy(inStream, os);
+            inStream.transferTo(os);
           }
         }
         
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 762b1e1..846ce79 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
@@ -23,7 +23,6 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 
-import org.apache.commons.io.IOUtils;
 import org.apache.solr.common.util.ContentStream;
 import org.apache.solr.common.util.ContentStreamBase;
 
@@ -62,7 +61,7 @@ public class ContentStreamUpdateRequest extends AbstractUpdateRequest {
       @Override
       public void write(OutputStream os) throws IOException {
         try(var inStream = stream.getStream()) {
-          IOUtils.copy(inStream, os);
+          inStream.transferTo(os);
         }
       }
 
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/request/MultiContentWriterRequest.java b/solr/solrj/src/java/org/apache/solr/client/solrj/request/MultiContentWriterRequest.java
index 631011f..c67385b 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/request/MultiContentWriterRequest.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/request/MultiContentWriterRequest.java
@@ -119,7 +119,7 @@ public class MultiContentWriterRequest extends AbstractUpdateRequest {
 
   public static ByteBuffer readByteBuffer(InputStream is) throws IOException {
     BinaryRequestWriter.BAOS baos = new BinaryRequestWriter.BAOS();
-    org.apache.commons.io.IOUtils.copy(is, baos);
+    is.transferTo(baos);
     return ByteBuffer.wrap(baos.getbuf(), 0, baos.size());
   }
 }
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/request/StreamingUpdateRequest.java b/solr/solrj/src/java/org/apache/solr/client/solrj/request/StreamingUpdateRequest.java
index 7534de5..3b2ed5e 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/request/StreamingUpdateRequest.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/request/StreamingUpdateRequest.java
@@ -24,8 +24,6 @@ import java.io.InputStream;
 import java.io.OutputStream;
 import java.nio.charset.StandardCharsets;
 
-import org.apache.commons.io.IOUtils;
-
 /** A simple update request which streams content to the server
  */
 public class StreamingUpdateRequest extends AbstractUpdateRequest {
@@ -57,7 +55,7 @@ public class StreamingUpdateRequest extends AbstractUpdateRequest {
       @Override
       public void write(OutputStream os) throws IOException {
         try (InputStream is = new FileInputStream(f)) {
-          IOUtils.copy(is, os);
+          is.transferTo(os);
         }
       }
 
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/request/V2Request.java b/solr/solrj/src/java/org/apache/solr/client/solrj/request/V2Request.java
index f3451ef..870efa5 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/request/V2Request.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/request/V2Request.java
@@ -25,7 +25,6 @@ import java.util.concurrent.atomic.AtomicLong;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-import org.apache.commons.io.IOUtils;
 import org.apache.solr.client.solrj.ResponseParser;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrRequest;
@@ -87,7 +86,7 @@ public class V2Request extends SolrRequest<V2Response> implements MapWriter {
           return;
         }
         if (payload instanceof InputStream) {
-          IOUtils.copy((InputStream) payload, os);
+          ((InputStream) payload).transferTo(os);
           return;
         }
         if (useBinary) {
diff --git a/solr/solrj/src/java/org/apache/solr/common/EmptyEntityResolver.java b/solr/solrj/src/java/org/apache/solr/common/EmptyEntityResolver.java
index 6536b95..8c1f109 100644
--- a/solr/solrj/src/java/org/apache/solr/common/EmptyEntityResolver.java
+++ b/solr/solrj/src/java/org/apache/solr/common/EmptyEntityResolver.java
@@ -17,6 +17,8 @@
 package org.apache.solr.common;
 
 import java.io.InputStream;
+
+import org.apache.http.impl.io.EmptyInputStream;
 import org.xml.sax.InputSource;
 import org.xml.sax.EntityResolver;
 import javax.xml.XMLConstants;
@@ -24,8 +26,6 @@ import javax.xml.parsers.SAXParserFactory;
 import javax.xml.stream.XMLInputFactory;
 import javax.xml.stream.XMLResolver;
 
-import org.apache.commons.io.input.ClosedInputStream;
-
 /**
  * This class provides several singletons of entity resolvers used by
  * SAX and StAX in the Java API. This is needed to make secure
@@ -41,14 +41,14 @@ public final class EmptyEntityResolver {
   public static final EntityResolver SAX_INSTANCE = new EntityResolver() {
     @Override
     public InputSource resolveEntity(String publicId, String systemId) {
-      return new InputSource(ClosedInputStream.CLOSED_INPUT_STREAM);
+      return new InputSource(EmptyInputStream.INSTANCE);
     }
   };
 
   public static final XMLResolver STAX_INSTANCE = new XMLResolver() {
     @Override
     public InputStream resolveEntity(String publicId, String systemId, String baseURI, String namespace) {
-      return ClosedInputStream.CLOSED_INPUT_STREAM;
+      return EmptyInputStream.INSTANCE;
     }
   };
   
diff --git a/solr/solrj/src/java/org/apache/solr/common/cloud/SolrZkClient.java b/solr/solrj/src/java/org/apache/solr/common/cloud/SolrZkClient.java
index b9122a7..a9d981f 100644
--- a/solr/solrj/src/java/org/apache/solr/common/cloud/SolrZkClient.java
+++ b/solr/solrj/src/java/org/apache/solr/common/cloud/SolrZkClient.java
@@ -22,6 +22,7 @@ import java.io.IOException;
 import java.io.PrintStream;
 import java.lang.invoke.MethodHandles;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.List;
 import java.util.concurrent.ExecutorService;
@@ -31,7 +32,6 @@ import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.regex.Pattern;
 
-import org.apache.commons.io.FileUtils;
 import org.apache.solr.client.solrj.SolrServerException;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.StringUtils;
@@ -427,13 +427,13 @@ public class SolrZkClient implements Closeable {
 
   public void makePath(String path, File file, boolean failOnExists, boolean retryOnConnLoss)
       throws IOException, KeeperException, InterruptedException {
-    makePath(path, FileUtils.readFileToByteArray(file),
+    makePath(path, Files.readAllBytes(file.toPath()),
         CreateMode.PERSISTENT, null, failOnExists, retryOnConnLoss, 0);
   }
 
   public void makePath(String path, File file, boolean retryOnConnLoss) throws IOException,
       KeeperException, InterruptedException {
-    makePath(path, FileUtils.readFileToByteArray(file), retryOnConnLoss);
+    makePath(path, Files.readAllBytes(file.toPath()), retryOnConnLoss);
   }
 
   public void makePath(String path, CreateMode createMode, boolean retryOnConnLoss) throws KeeperException,
@@ -586,7 +586,7 @@ public class SolrZkClient implements Closeable {
     if (log.isDebugEnabled()) {
       log.debug("Write to ZooKeeper: {} to {}", file.getAbsolutePath(), path);
     }
-    byte[] data = FileUtils.readFileToByteArray(file);
+    byte[] data = Files.readAllBytes(file.toPath());
     return setData(path, data, retryOnConnLoss);
   }
 
diff --git a/solr/solrj/src/test/org/apache/solr/common/util/ContentStreamTest.java b/solr/solrj/src/test/org/apache/solr/common/util/ContentStreamTest.java
index 6b27e5e..8db8ecf 100644
--- a/solr/solrj/src/test/org/apache/solr/common/util/ContentStreamTest.java
+++ b/solr/solrj/src/test/org/apache/solr/common/util/ContentStreamTest.java
@@ -52,7 +52,7 @@ public class ContentStreamTest extends SolrTestCaseJ4 {
          InputStream is = srl.openResource("solrj/README");
          FileOutputStream os = new FileOutputStream(file)) {
       assertNotNull(is);
-      IOUtils.copy(is, os);
+      is.transferTo(os);
     }
 
     ContentStreamBase stream = new ContentStreamBase.FileStream(file);
@@ -76,7 +76,7 @@ public class ContentStreamTest extends SolrTestCaseJ4 {
          InputStream is = srl.openResource("solrj/README");
          FileOutputStream os = new FileOutputStream(file);
          GZIPOutputStream zos = new GZIPOutputStream(os)) {
-      IOUtils.copy(is, zos);
+      is.transferTo(zos);
     }
 
     ContentStreamBase stream = new ContentStreamBase.FileStream(file);
@@ -101,7 +101,7 @@ public class ContentStreamTest extends SolrTestCaseJ4 {
     try (SolrResourceLoader srl = new SolrResourceLoader(Paths.get("").toAbsolutePath());
          InputStream is = srl.openResource("solrj/README");
          FileOutputStream os = new FileOutputStream(file)) {
-      IOUtils.copy(is, os);
+      is.transferTo(os);
     }
 
     ContentStreamBase stream = new ContentStreamBase.URLStream(new URL(file.toURI().toASCIIString()));
@@ -132,7 +132,7 @@ public class ContentStreamTest extends SolrTestCaseJ4 {
          InputStream is = srl.openResource("solrj/README");
          FileOutputStream os = new FileOutputStream(file);
          GZIPOutputStream zos = new GZIPOutputStream(os)) {
-      IOUtils.copy(is, zos);
+      is.transferTo(zos);
     }
 
     ContentStreamBase stream = new ContentStreamBase.URLStream(new URL(file.toURI().toASCIIString()));
@@ -158,7 +158,7 @@ public class ContentStreamTest extends SolrTestCaseJ4 {
          InputStream is = srl.openResource("solrj/README");
          FileOutputStream os = new FileOutputStream(file);
          GZIPOutputStream zos = new GZIPOutputStream(os)) {
-      IOUtils.copy(is, zos);
+      is.transferTo(zos);
     }
 
     ContentStreamBase stream = new ContentStreamBase.URLStream(new URL(file.toURI().toASCIIString()));
@@ -184,7 +184,7 @@ public class ContentStreamTest extends SolrTestCaseJ4 {
          InputStream is = srl.openResource("solrj/README");
          FileOutputStream os = new FileOutputStream(file);
          GZIPOutputStream zos = new GZIPOutputStream(os)) {
-      IOUtils.copy(is, zos);
+      is.transferTo(zos);
     }
 
     ContentStreamBase stream = new ContentStreamBase.URLStream(new URL(file.toURI().toASCIIString()));
diff --git a/solr/solrj/src/test/org/apache/solr/common/util/TestFastJavabinDecoder.java b/solr/solrj/src/test/org/apache/solr/common/util/TestFastJavabinDecoder.java
index 3ea5f44..0841545 100644
--- a/solr/solrj/src/test/org/apache/solr/common/util/TestFastJavabinDecoder.java
+++ b/solr/solrj/src/test/org/apache/solr/common/util/TestFastJavabinDecoder.java
@@ -26,7 +26,6 @@ import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 
-import org.apache.commons.io.IOUtils;
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.client.solrj.FastStreamingDocsCallback;
 import org.apache.solr.client.solrj.impl.BinaryRequestWriter;
@@ -124,7 +123,7 @@ public class TestFastJavabinDecoder extends SolrTestCaseJ4 {
   public void testFastJavabinStreamingDecoder() throws IOException {
     BinaryRequestWriter.BAOS baos = new BinaryRequestWriter.BAOS();
     try (InputStream is = getClass().getResourceAsStream("/solrj/javabin_sample.bin")) {
-      IOUtils.copy(is, baos);
+      is.transferTo(baos);
     }
 
     SolrDocumentList list;
diff --git a/solr/test-framework/src/java/org/apache/solr/EmbeddedSolrServerTestBase.java b/solr/test-framework/src/java/org/apache/solr/EmbeddedSolrServerTestBase.java
index 5785c00..96ecfe3 100644
--- a/solr/test-framework/src/java/org/apache/solr/EmbeddedSolrServerTestBase.java
+++ b/solr/test-framework/src/java/org/apache/solr/EmbeddedSolrServerTestBase.java
@@ -22,23 +22,17 @@ import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.Writer;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
-import java.util.Properties;
 
-import org.apache.commons.io.FileUtils;
-import org.apache.lucene.util.LuceneTestCase;
 import org.apache.solr.client.solrj.embedded.EmbeddedSolrServer;
 import org.apache.solr.common.util.ContentStream;
 import org.apache.solr.common.util.ContentStreamBase;
 import org.apache.solr.common.util.ContentStreamBase.ByteArrayStream;
-import org.apache.solr.util.ExternalPaths;
 import org.junit.After;
 import org.junit.AfterClass;
 
@@ -123,33 +117,9 @@ abstract public class EmbeddedSolrServerTestBase extends SolrTestCaseJ4 {
   }
 
   public static void initCore() throws Exception {
-    final String home = legacyExampleCollection1SolrHome();
+    final String home = SolrJettyTestBase.legacyExampleCollection1SolrHome();
     final String config = home + "/" + DEFAULT_CORE_NAME + "/conf/solrconfig.xml";
     final String schema = home + "/" + DEFAULT_CORE_NAME + "/conf/schema.xml";
     initCore(config, schema, home);
   }
-
-  public static String legacyExampleCollection1SolrHome() throws IOException {
-    final String sourceHome = ExternalPaths.SOURCE_HOME;
-    if (sourceHome == null)
-      throw new IllegalStateException("No source home! Cannot create the legacy example solr home directory.");
-
-    final File tempSolrHome = LuceneTestCase.createTempDir().toFile();
-    FileUtils.copyFileToDirectory(new File(sourceHome, "server/solr/solr.xml"), tempSolrHome);
-    final File collectionDir = new File(tempSolrHome, DEFAULT_CORE_NAME);
-    FileUtils.forceMkdir(collectionDir);
-    final File configSetDir = new File(sourceHome, "server/solr/configsets/sample_techproducts_configs/conf");
-    FileUtils.copyDirectoryToDirectory(configSetDir, collectionDir);
-
-    final Properties props = new Properties();
-    props.setProperty("name", DEFAULT_CORE_NAME);
-
-    try (Writer writer = new OutputStreamWriter(FileUtils.openOutputStream(new File(collectionDir, "core.properties")),
-        "UTF-8");) {
-      props.store(writer, null);
-    }
-
-    return tempSolrHome.getAbsolutePath();
-  }
-
 }
diff --git a/solr/test-framework/src/java/org/apache/solr/SolrJettyTestBase.java b/solr/test-framework/src/java/org/apache/solr/SolrJettyTestBase.java
index 750dd15..07dae97 100644
--- a/solr/test-framework/src/java/org/apache/solr/SolrJettyTestBase.java
+++ b/solr/test-framework/src/java/org/apache/solr/SolrJettyTestBase.java
@@ -17,8 +17,12 @@
 package org.apache.solr;
 
 import java.io.File;
+import java.io.IOException;
 import java.io.OutputStreamWriter;
+import java.io.UncheckedIOException;
+import java.io.Writer;
 import java.lang.invoke.MethodHandles;
+import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.Properties;
 import java.util.SortedMap;
@@ -29,6 +33,7 @@ import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.embedded.JettyConfig;
 import org.apache.solr.client.solrj.embedded.JettySolrRunner;
 import org.apache.solr.client.solrj.impl.HttpSolrClient;
+import org.apache.solr.util.DirectoryUtil;
 import org.apache.solr.util.ExternalPaths;
 import org.eclipse.jetty.servlet.ServletHolder;
 import org.junit.After;
@@ -172,39 +177,27 @@ abstract public class SolrJettyTestBase extends SolrTestCaseJ4
     if (sourceHome == null)
       throw new IllegalStateException("No source home! Cannot create the legacy example solr home directory.");
 
-    String legacyExampleSolrHome = null;
     try {
-      File tempSolrHome = LuceneTestCase.createTempDir().toFile();
-      org.apache.commons.io.FileUtils.copyFileToDirectory(new File(sourceHome, "server/solr/solr.xml"), tempSolrHome);
-      File collection1Dir = new File(tempSolrHome, "collection1");
-      org.apache.commons.io.FileUtils.forceMkdir(collection1Dir);
+      Path tempSolrHome = LuceneTestCase.createTempDir();
+      Path serverSolr = tempSolrHome.getFileSystem().getPath(sourceHome, "server", "solr");
+      Files.copy(serverSolr.resolve("solr.xml"), tempSolrHome.resolve("solr.xml"));
+
+      Path sourceConfig = serverSolr.resolve("configsets").resolve("sample_techproducts_configs");
+      Path collection1Dir = tempSolrHome.resolve("collection1");
+
+      DirectoryUtil.copyDirectoryContents(sourceConfig.resolve("conf"), collection1Dir.resolve("conf"));
 
-      File configSetDir = new File(sourceHome, "server/solr/configsets/sample_techproducts_configs/conf");
-      org.apache.commons.io.FileUtils.copyDirectoryToDirectory(configSetDir, collection1Dir);
       Properties props = new Properties();
       props.setProperty("name", "collection1");
-      OutputStreamWriter writer = null;
-      try {
-        writer = new OutputStreamWriter(FileUtils.openOutputStream(
-            new File(collection1Dir, "core.properties")), StandardCharsets.UTF_8);
+      try (Writer writer = new OutputStreamWriter(Files.newOutputStream(collection1Dir.resolve("core.properties")), StandardCharsets.UTF_8)) {
         props.store(writer, null);
-      } finally {
-        if (writer != null) {
-          try {
-            writer.close();
-          } catch (Exception ignore){}
-        }
-      }
-      legacyExampleSolrHome = tempSolrHome.getAbsolutePath();
-    } catch (Exception exc) {
-      if (exc instanceof RuntimeException) {
-        throw (RuntimeException)exc;
-      } else {
-        throw new RuntimeException(exc);
       }
+      return tempSolrHome.toString();
+    } catch (RuntimeException e) {
+      throw e;
+    } catch (IOException e) {
+      throw new UncheckedIOException(e);
     }
-
-    return legacyExampleSolrHome;
   }
 
 }
diff --git a/solr/test-framework/src/java/org/apache/solr/SolrTestCaseHS.java b/solr/test-framework/src/java/org/apache/solr/SolrTestCaseHS.java
index 9165faf..647d7ae 100644
--- a/solr/test-framework/src/java/org/apache/solr/SolrTestCaseHS.java
+++ b/solr/test-framework/src/java/org/apache/solr/SolrTestCaseHS.java
@@ -24,6 +24,7 @@ import java.io.Writer;
 import java.lang.invoke.MethodHandles;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -36,7 +37,6 @@ import java.util.Properties;
 import java.util.Random;
 import java.util.Set;
 
-import org.apache.commons.io.FileUtils;
 import org.apache.lucene.util.IOUtils;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrServerException;
@@ -544,9 +544,9 @@ public class SolrTestCaseHS extends SolrTestCaseJ4 {
     }
 
     private static void copyConfFile(File dstRoot, String destCollection, String file) throws Exception {
-      File subHome = new File(dstRoot, destCollection + File.separator + "conf");
-      String top = SolrTestCaseJ4.TEST_HOME() + "/collection1/conf";
-      FileUtils.copyFile(new File(top, file), new File(subHome, file));
+      Path subHome = dstRoot.toPath().resolve(destCollection).resolve("conf");
+      Path top = SolrTestCaseJ4.TEST_PATH().resolve("collection1").resolve("conf");
+      Files.copy(top.resolve(file), subHome.resolve(file));
     }
 
     public void copyConfigFile(File dstRoot, String destCollection, String file) throws Exception {
@@ -554,9 +554,7 @@ public class SolrTestCaseHS extends SolrTestCaseJ4 {
         createHome();
       }
 
-      File subHome = new File(dstRoot, destCollection + File.separator + "conf");
-      String top = SolrTestCaseJ4.TEST_HOME() + "/collection1/conf";
-      FileUtils.copyFile(new File(top, file), new File(subHome, file));
+      SolrInstance.copyConfFile(dstRoot, destCollection, file);
     }
 
   }
diff --git a/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java b/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
index af5d873..2ea25fa 100644
--- a/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
+++ b/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
@@ -41,6 +41,7 @@ import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
 import java.security.SecureRandom;
@@ -74,7 +75,6 @@ import com.carrotsearch.randomizedtesting.rules.SystemPropertiesRestoreRule;
 
 import io.opentracing.noop.NoopTracerFactory;
 import io.opentracing.util.GlobalTracer;
-import org.apache.commons.io.FileUtils;
 import org.apache.http.client.HttpClient;
 import org.apache.logging.log4j.Level;
 import org.apache.lucene.analysis.MockAnalyzer;
@@ -2146,7 +2146,7 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
       throws IOException, SAXException {
 
     try {
-      String xml = FileUtils.readFileToString(file, "UTF-8");
+      String xml = Files.readString(file.toPath());
       String results = TestHarness.validateXPath(xml, xpath);
       if (null != results) {
         String msg = "File XPath failure: file=" + file.getPath() + " xpath="
@@ -2193,62 +2193,52 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
   }
 
   public static void copyMinConf(File dstRoot, String propertiesContent, String solrconfigXmlName) throws IOException {
+    Path dstPath = dstRoot.toPath();
+    Path subHome = dstPath.resolve("conf");
+    Files.createDirectories(subHome);
 
-    File subHome = new File(dstRoot, "conf");
-    if (! dstRoot.exists()) {
-      assertTrue("Failed to make subdirectory ", dstRoot.mkdirs());
-    }
-    Files.createFile(dstRoot.toPath().resolve("core.properties"));
     if (propertiesContent != null) {
-      FileUtils.writeStringToFile(new File(dstRoot, "core.properties"), propertiesContent, StandardCharsets.UTF_8);
+      Files.writeString(dstRoot.toPath().resolve(CORE_PROPERTIES_FILENAME), propertiesContent);
     }
-    String top = SolrTestCaseJ4.TEST_HOME() + "/collection1/conf";
-    FileUtils.copyFile(new File(top, "schema-tiny.xml"), new File(subHome, "schema.xml"));
-    FileUtils.copyFile(new File(top, solrconfigXmlName), new File(subHome, "solrconfig.xml"));
-    FileUtils.copyFile(new File(top, "solrconfig.snippet.randomindexconfig.xml"), new File(subHome, "solrconfig.snippet.randomindexconfig.xml"));
+    Path top = SolrTestCaseJ4.TEST_PATH().resolve("collection1").resolve("conf");
+    Files.copy(top.resolve("schema-tiny.xml"), subHome.resolve("schema.xml"));
+    Files.copy(top.resolve(solrconfigXmlName), subHome.resolve("solrconfig.xml"));
+    Files.copy(top.resolve("solrconfig.snippet.randomindexconfig.xml"), subHome.resolve("solrconfig.snippet.randomindexconfig.xml"));
   }
 
   // Creates minimal full setup, including solr.xml
   public static void copyMinFullSetup(File dstRoot) throws IOException {
-    if (! dstRoot.exists()) {
-      assertTrue("Failed to make subdirectory ", dstRoot.mkdirs());
-    }
-    File xmlF = new File(SolrTestCaseJ4.TEST_HOME(), "solr.xml");
-    FileUtils.copyFile(xmlF, new File(dstRoot, "solr.xml"));
+    Files.createDirectories(dstRoot.toPath());
+    Files.copy(SolrTestCaseJ4.TEST_PATH().resolve("solr.xml"), dstRoot.toPath().resolve("solr.xml"));
     copyMinConf(dstRoot);
   }
 
   // Just copies the file indicated to the tmp home directory naming it "solr.xml"
   public static void copyXmlToHome(File dstRoot, String fromFile) throws IOException {
-    if (! dstRoot.exists()) {
-      assertTrue("Failed to make subdirectory ", dstRoot.mkdirs());
-    }
-    File xmlF = new File(SolrTestCaseJ4.TEST_HOME(), fromFile);
-    FileUtils.copyFile(xmlF, new File(dstRoot, "solr.xml"));
-    
+    Files.createDirectories(dstRoot.toPath());
+    Files.copy(SolrTestCaseJ4.TEST_PATH().resolve(fromFile), dstRoot.toPath().resolve("solr.xml"));
   }
   // Creates a consistent configuration, _including_ solr.xml at dstRoot. Creates collection1/conf and copies
   // the stock files in there.
 
   public static void copySolrHomeToTemp(File dstRoot, String collection) throws IOException {
-    if (!dstRoot.exists()) {
-      assertTrue("Failed to make subdirectory ", dstRoot.mkdirs());
-    }
-    FileUtils.copyFile(new File(SolrTestCaseJ4.TEST_HOME(), "solr.xml"), new File(dstRoot, "solr.xml"));
-
-    File subHome = new File(dstRoot, collection + File.separator + "conf");
-    String top = SolrTestCaseJ4.TEST_HOME() + "/collection1/conf";
-    FileUtils.copyFile(new File(top, "currency.xml"), new File(subHome, "currency.xml"));
-    FileUtils.copyFile(new File(top, "mapping-ISOLatin1Accent.txt"), new File(subHome, "mapping-ISOLatin1Accent.txt"));
-    FileUtils.copyFile(new File(top, "old_synonyms.txt"), new File(subHome, "old_synonyms.txt"));
-    FileUtils.copyFile(new File(top, "open-exchange-rates.json"), new File(subHome, "open-exchange-rates.json"));
-    FileUtils.copyFile(new File(top, "protwords.txt"), new File(subHome, "protwords.txt"));
-    FileUtils.copyFile(new File(top, "schema.xml"), new File(subHome, "schema.xml"));
-    FileUtils.copyFile(new File(top, "enumsConfig.xml"), new File(subHome, "enumsConfig.xml"));
-    FileUtils.copyFile(new File(top, "solrconfig.snippet.randomindexconfig.xml"), new File(subHome, "solrconfig.snippet.randomindexconfig.xml"));
-    FileUtils.copyFile(new File(top, "solrconfig.xml"), new File(subHome, "solrconfig.xml"));
-    FileUtils.copyFile(new File(top, "stopwords.txt"), new File(subHome, "stopwords.txt"));
-    FileUtils.copyFile(new File(top, "synonyms.txt"), new File(subHome, "synonyms.txt"));
+    Path subHome = dstRoot.toPath().resolve(collection).resolve("conf");
+    Files.createDirectories(subHome);
+
+    Files.copy(SolrTestCaseJ4.TEST_PATH().resolve("solr.xml"), dstRoot.toPath().resolve("solr.xml"), StandardCopyOption.REPLACE_EXISTING);
+
+    Path top = SolrTestCaseJ4.TEST_PATH().resolve("collection1").resolve("conf");
+    Files.copy(top.resolve("currency.xml"), subHome.resolve("currency.xml"));
+    Files.copy(top.resolve("mapping-ISOLatin1Accent.txt"), subHome.resolve("mapping-ISOLatin1Accent.txt"));
+    Files.copy(top.resolve("old_synonyms.txt"), subHome.resolve("old_synonyms.txt"));
+    Files.copy(top.resolve("open-exchange-rates.json"), subHome.resolve("open-exchange-rates.json"));
+    Files.copy(top.resolve("protwords.txt"), subHome.resolve("protwords.txt"));
+    Files.copy(top.resolve("schema.xml"), subHome.resolve("schema.xml"));
+    Files.copy(top.resolve("enumsConfig.xml"), subHome.resolve("enumsConfig.xml"));
+    Files.copy(top.resolve("solrconfig.snippet.randomindexconfig.xml"), subHome.resolve("solrconfig.snippet.randomindexconfig.xml"));
+    Files.copy(top.resolve("solrconfig.xml"), subHome.resolve("solrconfig.xml"));
+    Files.copy(top.resolve("stopwords.txt"), subHome.resolve("stopwords.txt"));
+    Files.copy(top.resolve("synonyms.txt"), subHome.resolve("synonyms.txt"));
   }
 
   public boolean compareSolrDocument(Object expected, Object actual) {
diff --git a/solr/test-framework/src/java/org/apache/solr/cloud/api/collections/AbstractIncrementalBackupTest.java b/solr/test-framework/src/java/org/apache/solr/cloud/api/collections/AbstractIncrementalBackupTest.java
index e31489f..09cf4cb 100644
--- a/solr/test-framework/src/java/org/apache/solr/cloud/api/collections/AbstractIncrementalBackupTest.java
+++ b/solr/test-framework/src/java/org/apache/solr/cloud/api/collections/AbstractIncrementalBackupTest.java
@@ -17,7 +17,6 @@
 
 package org.apache.solr.cloud.api.collections;
 
-import org.apache.commons.io.IOUtils;
 import org.apache.lucene.codecs.CodecUtil;
 import org.apache.lucene.index.IndexCommit;
 import org.apache.lucene.store.Directory;
@@ -349,13 +348,13 @@ public abstract class AbstractIncrementalBackupTest extends SolrCloudTestCase {
                 .filter(x -> fileNames.contains(x.getName()))
                 .findAny().get();
         try (FileInputStream fis = new FileInputStream(fileGetCorrupted)){
-            byte[] contents = IOUtils.readFully(fis, (int) fileGetCorrupted.length());
+            byte[] contents = fis.readAllBytes();
             contents[contents.length - CodecUtil.footerLength() - 1] += 1;
             contents[contents.length - CodecUtil.footerLength() - 2] += 1;
             contents[contents.length - CodecUtil.footerLength() - 3] += 1;
             contents[contents.length - CodecUtil.footerLength() - 4] += 1;
             try (FileOutputStream fos = new FileOutputStream(fileGetCorrupted)) {
-                IOUtils.write(contents, fos);
+                fos.write(contents);
             }
         } finally {
             solrCore.close();
diff --git a/solr/test-framework/src/java/org/apache/solr/util/DirectoryUtil.java b/solr/test-framework/src/java/org/apache/solr/util/DirectoryUtil.java
new file mode 100644
index 0000000..1758ff2
--- /dev/null
+++ b/solr/test-framework/src/java/org/apache/solr/util/DirectoryUtil.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.util;
+
+import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+
+public class DirectoryUtil {
+  private DirectoryUtil() {}
+
+  /**
+   * Recursively copy the contents of one directory into another. For example:
+   * <pre>
+   *   copyDirectory(Path.of("src"), Path.of("dst"))
+   * </pre>
+   * will copy the contents of src directly into dst. This will not create a new "src" folder inside of dst.
+   */
+  public static void copyDirectoryContents(final Path source, final Path destination) throws IOException {
+    assert source.getFileSystem().equals(destination.getFileSystem());
+
+    Files.walkFileTree(source, new SimpleFileVisitor<>() {
+      private Path resolveTarget(Path other) {
+        return destination.resolve(source.relativize(other));
+      }
+
+      @Override
+      public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
+        Files.createDirectories(resolveTarget(dir));
+        return FileVisitResult.CONTINUE;
+      }
+
+      @Override
+      public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+        Files.copy(file, resolveTarget(file));
+        return FileVisitResult.CONTINUE;
+      }
+    });
+  }
+}