You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gg...@apache.org on 2023/04/16 16:08:23 UTC

[commons-io] branch master updated: Add builders and avoid creating more constructors for all permutations of current options.

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

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-io.git


The following commit(s) were added to refs/heads/master by this push:
     new 7ecca22f Add builders and avoid creating more constructors for all permutations of current options.
7ecca22f is described below

commit 7ecca22f175c644da3096940a4ce899be5b33740
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Sun Apr 16 12:08:18 2023 -0400

    Add builders and avoid creating more constructors for all permutations
    of current options.
    
    In the future new options should be added via builder and no new public
    or protected constructors should be added for input streams, output
    streams, readers, and writers. Likely the same for filters.
---
 src/changes/changes.xml                            |   3 +
 .../apache/commons/io/build/AbstractOrigin.java    | 276 +++++++++++++
 .../commons/io/build/AbstractOriginSupplier.java   | 277 +++++++++++++
 .../commons/io/build/AbstractStreamBuilder.java    | 179 +++++++++
 .../apache/commons/io/build/AbstractSupplier.java  |  41 ++
 .../org/apache/commons/io/build/package-info.java  |  23 ++
 .../commons/io/filefilter/WildcardFileFilter.java  | 149 +++++--
 .../apache/commons/io/input/BOMInputStream.java    |  70 +++-
 .../io/input/BufferedFileChannelInputStream.java   |  52 +++
 .../io/input/MemoryMappedFileInputStream.java      |  49 +++
 .../input/MessageDigestCalculatingInputStream.java | 125 ++++--
 .../io/input/RandomAccessFileInputStream.java      |  75 +++-
 .../commons/io/input/ReadAheadInputStream.java     |  86 +++-
 .../apache/commons/io/input/ReaderInputStream.java | 152 ++++---
 .../commons/io/input/ReversedLinesFileReader.java  |  66 ++-
 .../java/org/apache/commons/io/input/Tailer.java   | 271 +++++++------
 .../apache/commons/io/input/XmlStreamReader.java   | 446 +++++++++++----------
 .../io/output/DeferredFileOutputStream.java        | 236 +++++++----
 .../commons/io/output/FileWriterWithEncoding.java  | 184 +++++++--
 .../commons/io/output/LockableFileWriter.java      | 226 +++++++----
 .../commons/io/output/WriterOutputStream.java      | 251 ++++++++----
 .../apache/commons/io/output/XmlStreamWriter.java  |  53 ++-
 .../java/org/apache/commons/io/FileUtilsTest.java  |  59 ++-
 .../io/filefilter/WildcardFileFilterTest.java      |  29 +-
 .../commons/io/function/IOBaseStreamTest.java      |   2 +-
 .../commons/io/function/IOPredicateTest.java       |   4 +-
 .../commons/io/input/BOMInputStreamTest.java       |  89 ++--
 .../input/BufferedFileChannelInputStreamTest.java  |   9 +-
 .../io/input/MemoryMappedFileInputStreamTest.java  |  45 ++-
 .../MessageDigestCalculatingInputStreamTest.java   |   8 +-
 .../io/input/RandomAccessFileInputStreamTest.java  |  49 ++-
 .../commons/io/input/ReadAheadInputStreamTest.java |  34 +-
 .../commons/io/input/ReaderInputStreamTest.java    | 116 ++++--
 .../ReversedLinesFileReaderTestParamBlockSize.java |  10 +-
 .../ReversedLinesFileReaderTestParamFile.java      |  44 +-
 .../commons/io/input/SequenceReaderTest.java       |   1 +
 .../org/apache/commons/io/input/TailerTest.java    |   5 +-
 .../commons/io/input/XmlStreamReaderTest.java      |  74 +++-
 .../io/output/DeferredFileOutputStreamTest.java    |  98 ++++-
 .../io/output/FileWriterWithEncodingTest.java      |  62 +++
 .../commons/io/output/LockableFileWriterTest.java  |  62 ++-
 .../io/output/UncheckedFilterOutputStreamTest.java |   4 +-
 .../commons/io/output/WriterOutputStreamTest.java  |  55 ++-
 .../commons/io/output/XmlStreamWriterTest.java     |  26 +-
 44 files changed, 3218 insertions(+), 957 deletions(-)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 7cbdb7dc..4500183e 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -468,6 +468,9 @@ The <action> type attribute can be add,update,fix,remove.
       <action dev="ggregory" type="add" due-to="Gary Gregory">
         Add FileSystem.getBlockSize().
       </action>
+      <action dev="ggregory" type="add" due-to="Gary Gregory">
+        Add builders and avoid creating more constructors for all permutations of current options.
+      </action>
       <!-- UPDATE -->
       <action dev="kinow" type="update" due-to="Dependabot, Gary Gregory">
         Bump actions/cache from 2.1.6 to 3.0.10 #307, #337, #393.
diff --git a/src/main/java/org/apache/commons/io/build/AbstractOrigin.java b/src/main/java/org/apache/commons/io/build/AbstractOrigin.java
new file mode 100644
index 00000000..1e4ca524
--- /dev/null
+++ b/src/main/java/org/apache/commons/io/build/AbstractOrigin.java
@@ -0,0 +1,276 @@
+/*
+ * 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.commons.io.build;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.Writer;
+import java.net.URI;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Objects;
+
+/**
+ * Abstracts the origin of data for builders like a {@link File}, {@link Path}, and so on.
+ *
+ * @param <T> the type of instances to build.
+ * @param <B> the type of builder subclass.
+ * @since 2.12.0
+ */
+public abstract class AbstractOrigin<T, B extends AbstractOrigin<T, B>> extends AbstractSupplier<T, B> {
+
+    /**
+     * A {@link File} origin.
+     */
+    public static class FileOrigin extends AbstractOrigin<File, FileOrigin> {
+
+        public FileOrigin(final File origin) {
+            super(origin);
+        }
+
+        @Override
+        public File getFile() {
+            return get();
+        }
+
+        @Override
+        public Path getPath() {
+            return get().toPath();
+        }
+
+    }
+
+    /**
+     * An {@link InputStream} origin.
+     * <p>
+     * This origin cannot provide other aspects.
+     * </p>
+     */
+    public static class InputStreamOrigin extends AbstractOrigin<InputStream, InputStreamOrigin> {
+
+        public InputStreamOrigin(final InputStream origin) {
+            super(origin);
+        }
+
+        @Override
+        public InputStream getInputStream(final OpenOption... options) {
+            return get();
+        }
+
+    }
+
+    /**
+     * An {@link OutputStream} origin.
+     * <p>
+     * This origin cannot provide other aspects.
+     * </p>
+     */
+    public static class OutputStreamOrigin extends AbstractOrigin<OutputStream, OutputStreamOrigin> {
+
+        public OutputStreamOrigin(final OutputStream origin) {
+            super(origin);
+        }
+
+        @Override
+        public OutputStream getOutputStream(final OpenOption... options) {
+            return get();
+        }
+
+    }
+
+    /**
+     * A {@link Path} origin.
+     */
+    public static class PathOrigin extends AbstractOrigin<Path, PathOrigin> {
+
+        public PathOrigin(final Path origin) {
+            super(origin);
+        }
+
+        @Override
+        public File getFile() {
+            return get().toFile();
+        }
+
+        @Override
+        public Path getPath() {
+            return get();
+        }
+
+    }
+
+    /**
+     * An {@link Reader} origin.
+     * <p>
+     * This origin cannot provide other aspects.
+     * </p>
+     */
+    public static class ReaderOrigin extends AbstractOrigin<Reader, ReaderOrigin> {
+
+        public ReaderOrigin(final Reader origin) {
+            super(origin);
+        }
+
+        @Override
+        public Reader getReader(final Charset charset) throws IOException {
+            return get();
+        }
+    }
+
+    /**
+     * A {@link URI} origin.
+     */
+    public static class URIOrigin extends AbstractOrigin<URI, URIOrigin> {
+
+        public URIOrigin(final URI origin) {
+            super(origin);
+        }
+
+        @Override
+        public URI get() {
+            return origin;
+        }
+
+        @Override
+        public File getFile() {
+            return getPath().toFile();
+        }
+
+        @Override
+        public Path getPath() {
+            return Paths.get(get());
+        }
+
+    }
+
+    /**
+     * An {@link Writer} origin.
+     * <p>
+     * This origin cannot provide other aspects.
+     * </p>
+     */
+    public static class WriterOrigin extends AbstractOrigin<Writer, WriterOrigin> {
+
+        public WriterOrigin(final Writer origin) {
+            super(origin);
+        }
+
+        @Override
+        public Writer getWriter(final Charset charset, final OpenOption... options) throws IOException {
+            return get();
+        }
+    }
+
+    /**
+     * The non-null origin.
+     */
+    final T origin;
+
+    /**
+     * Constructs a new instance for a subclass.
+     *
+     * @param origin The origin.
+     */
+    protected AbstractOrigin(final T origin) {
+        this.origin = Objects.requireNonNull(origin, "origin");
+    }
+
+    /**
+     * Gets the origin.
+     *
+     * @return the origin.
+     */
+    @Override
+    public T get() {
+        return origin;
+    }
+
+    /**
+     * Gets a new Reader on the origin, buffered by default.
+     *
+     * @param charset the charset to use for decoding
+     * @return a new Reader on the origin.
+     * @throws IOException if an I/O error occurs opening the file.
+     */
+    public Reader getReader(final Charset charset) throws IOException {
+        return Files.newBufferedReader(getPath(), charset);
+    }
+
+    /**
+     * Gets a new Writer on the origin, buffered by default.
+     *
+     * @param charset the charset to use for encoding
+     * @param options options specifying how the file is opened
+     * @return a new Writer on the origin.
+     * @throws IOException if an I/O error occurs opening or creating the file.
+     */
+    public Writer getWriter(final Charset charset, final OpenOption... options) throws IOException {
+        return Files.newBufferedWriter(getPath(), charset, options);
+    }
+
+    /**
+     * Gets this origin as a Path, if possible.
+     *
+     * @return this origin as a Path, if possible.
+     */
+    public File getFile() {
+        throw new UnsupportedOperationException(origin.toString());
+    }
+
+    /**
+     * Gets this origin as an InputStream, if possible.
+     *
+     * @param options options specifying how the file is opened
+     * @return this origin as an InputStream, if possible.
+     * @throws IOException if an I/O error occurs.
+     */
+    public InputStream getInputStream(final OpenOption... options) throws IOException {
+        return Files.newInputStream(getPath(), options);
+    }
+
+    /**
+     * Gets this origin as an OutputStream, if possible.
+     *
+     * @param options options specifying how the file is opened
+     * @return this origin as an OutputStream, if possible.
+     * @throws IOException if an I/O error occurs.
+     */
+    public OutputStream getOutputStream(final OpenOption... options) throws IOException {
+        return Files.newOutputStream(getPath(), options);
+    }
+
+    /**
+     * Gets this origin as a Path, if possible.
+     *
+     * @return this origin as a Path\, if possible.
+     */
+    public Path getPath() {
+        throw new UnsupportedOperationException(origin.toString());
+    }
+
+    @Override
+    public String toString() {
+        return origin.toString();
+    }
+}
diff --git a/src/main/java/org/apache/commons/io/build/AbstractOriginSupplier.java b/src/main/java/org/apache/commons/io/build/AbstractOriginSupplier.java
new file mode 100644
index 00000000..6935e9e0
--- /dev/null
+++ b/src/main/java/org/apache/commons/io/build/AbstractOriginSupplier.java
@@ -0,0 +1,277 @@
+/*
+ * 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.commons.io.build;
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.Writer;
+import java.net.URI;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Objects;
+
+import org.apache.commons.io.build.AbstractOrigin.FileOrigin;
+import org.apache.commons.io.build.AbstractOrigin.InputStreamOrigin;
+import org.apache.commons.io.build.AbstractOrigin.OutputStreamOrigin;
+import org.apache.commons.io.build.AbstractOrigin.PathOrigin;
+import org.apache.commons.io.build.AbstractOrigin.ReaderOrigin;
+import org.apache.commons.io.build.AbstractOrigin.URIOrigin;
+import org.apache.commons.io.build.AbstractOrigin.WriterOrigin;
+
+/**
+ * Abstracts building a typed instance of {@code T}.
+ *
+ * @param <T> the type of instances to build.
+ * @param <B> the type of builder subclass.
+ * @since 2.12.0
+ */
+public abstract class AbstractOriginSupplier<T, B extends AbstractOriginSupplier<T, B>> extends AbstractSupplier<T, B> {
+
+    protected static int checkBufferSize(final int initialBufferSize) {
+        if (initialBufferSize < 0) {
+            throw new IllegalArgumentException("Initial buffer size must be at least 0.");
+        }
+        return initialBufferSize;
+    }
+
+    /**
+     * Creates a new file origin for a file.
+     *
+     * @param origin the file.
+     * @return a new file origin
+     */
+    protected static FileOrigin newFileOrigin(final File origin) {
+        return new FileOrigin(origin);
+    }
+
+    /**
+     * Creates a new file origin for a file path.
+     *
+     * @param origin the file path.
+     * @return a new file origin
+     */
+    protected static FileOrigin newFileOrigin(final String origin) {
+        return new FileOrigin(new File(origin));
+    }
+
+    /**
+     * Creates a new input stream origin for a file.
+     *
+     * @param origin the input stream.
+     * @return a new input stream origin
+     */
+    protected static InputStreamOrigin newInputStreamOrigin(final InputStream origin) {
+        return new InputStreamOrigin(origin);
+    }
+
+    /**
+     * Creates a new output stream origin for a file.
+     *
+     * @param origin the output stream.
+     * @return a new output stream origin
+     */
+    protected static OutputStreamOrigin newOutputStreamOrigin(final OutputStream origin) {
+        return new OutputStreamOrigin(origin);
+    }
+
+    /**
+     * Creates a new path origin for a file.
+     *
+     * @param origin the path.
+     * @return a new path origin
+     */
+    protected static PathOrigin newPathOrigin(final Path origin) {
+        return new PathOrigin(origin);
+    }
+
+    /**
+     * Creates a new path name origin for a path name.
+     *
+     * @param origin the path name.
+     * @return a new path name origin
+     */
+    protected static PathOrigin newPathOrigin(final String origin) {
+        return new PathOrigin(Paths.get(origin));
+    }
+
+    /**
+     * Creates a new reader origin for a reader.
+     *
+     * @param origin the reader.
+     * @return a new reader origin
+     */
+    protected static ReaderOrigin newReaderOrigin(final Reader origin) {
+        return new ReaderOrigin(origin);
+    }
+
+    /**
+     * Creates a new reader origin for a URI.
+     *
+     * @param origin the URI.
+     * @return a new URI origin
+     */
+    protected static URIOrigin newURIOrigin(final URI origin) {
+        return new URIOrigin(origin);
+    }
+
+    /**
+     * Creates a new writer origin for a file.
+     *
+     * @param origin the writer.
+     * @return a new writer origin
+     */
+    protected static WriterOrigin newWriterOrigin(final Writer origin) {
+        return new WriterOrigin(origin);
+    }
+
+    /**
+     * The underlying origin.
+     */
+    private AbstractOrigin<?, ?> origin;
+
+    /**
+     * Checks whether the origin is null.
+     *
+     * @return the origin.
+     * @throws NullPointerException if the {@code origin} is {@code null}
+     */
+    protected AbstractOrigin<?, ?> checkOrigin() {
+        return Objects.requireNonNull(origin, "origin");
+    }
+
+    /**
+     * Gets the origin.
+     *
+     * @return the origin.
+     */
+    protected AbstractOrigin<?, ?> getOrigin() {
+        return origin;
+    }
+
+    /**
+     * Tests whether the origin is null.
+     *
+     * @return whether the origin is null.
+     */
+    protected boolean hasOrigin() {
+        return origin != null;
+    }
+
+    /**
+     * Sets a new origin.
+     *
+     * @param origin the new origin.
+     * @return this
+     */
+    public B setFile(final File origin) {
+        return setOrigin(newFileOrigin(origin));
+    }
+
+    /**
+     * Sets a new origin.
+     *
+     * @param origin the new origin.
+     * @return this
+     */
+    public B setFile(final String origin) {
+        return setOrigin(newFileOrigin(origin));
+    }
+
+    /**
+     * Sets a new origin.
+     *
+     * @param origin the new origin.
+     * @return this
+     */
+    public B setInputStream(final InputStream origin) {
+        return setOrigin(newInputStreamOrigin(origin));
+    }
+
+    /**
+     * Sets a new origin.
+     *
+     * @param origin the new origin.
+     * @return this
+     */
+    protected B setOrigin(final AbstractOrigin<?, ?> origin) {
+        this.origin = origin;
+        return asThis();
+    }
+
+    /**
+     * Sets a new origin.
+     *
+     * @param origin the new origin.
+     * @return this
+     */
+    public B setOutputStream(final OutputStream origin) {
+        return setOrigin(newOutputStreamOrigin(origin));
+    }
+
+    /**
+     * Sets a new origin.
+     *
+     * @param origin the new origin.
+     * @return this
+     */
+    public B setPath(final Path origin) {
+        return setOrigin(newPathOrigin(origin));
+    }
+
+    /**
+     * Sets a new origin.
+     *
+     * @param origin the new origin.
+     * @return this
+     */
+    public B setPath(final String origin) {
+        return setOrigin(newPathOrigin(origin));
+    }
+
+    /**
+     * Sets a new origin.
+     *
+     * @param origin the new origin.
+     * @return this
+     */
+    public B setReader(final Reader origin) {
+        return setOrigin(newReaderOrigin(origin));
+    }
+
+    /**
+     * Sets a new origin.
+     *
+     * @param origin the new origin.
+     * @return this
+     */
+    public B setURI(final URI origin) {
+        return setOrigin(newURIOrigin(origin));
+    }
+
+    /**
+     * Sets a new origin.
+     *
+     * @param origin the new origin.
+     * @return this
+     */
+    public B setWriter(final Writer origin) {
+        return setOrigin(newWriterOrigin(origin));
+    }
+}
diff --git a/src/main/java/org/apache/commons/io/build/AbstractStreamBuilder.java b/src/main/java/org/apache/commons/io/build/AbstractStreamBuilder.java
new file mode 100644
index 00000000..3bc2f22d
--- /dev/null
+++ b/src/main/java/org/apache/commons/io/build/AbstractStreamBuilder.java
@@ -0,0 +1,179 @@
+/*
+ * 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.commons.io.build;
+
+import java.nio.charset.Charset;
+
+import org.apache.commons.io.Charsets;
+import org.apache.commons.io.IOUtils;
+
+/**
+ * Abstracts building a typed instance of {@code T}.
+ *
+ * @param <T> the type of instances to build.
+ * @param <B> the type of builder subclass.
+ * @since 2.12.0
+ */
+public abstract class AbstractStreamBuilder<T, B extends AbstractStreamBuilder<T, B>> extends AbstractOriginSupplier<T, B> {
+
+    protected static int checkBufferSize(final int initialBufferSize) {
+        if (initialBufferSize < 0) {
+            throw new IllegalArgumentException("Initial buffer size must be at least 0.");
+        }
+        return initialBufferSize;
+    }
+
+    /**
+     * The buffer size, defaults to {@link IOUtils#DEFAULT_BUFFER_SIZE} ({@value IOUtils#DEFAULT_BUFFER_SIZE}).
+     */
+    private int bufferSize = IOUtils.DEFAULT_BUFFER_SIZE;
+
+    /**
+     * The buffer size, defaults to {@link IOUtils#DEFAULT_BUFFER_SIZE} ({@value IOUtils#DEFAULT_BUFFER_SIZE}).
+     */
+    private int bufferSizeDefault = IOUtils.DEFAULT_BUFFER_SIZE;
+
+    /**
+     * The Charset, defaults to {@link Charset#defaultCharset()}.
+     */
+    private Charset charset = Charset.defaultCharset();
+
+    /**
+     * The Charset, defaults to {@link Charset#defaultCharset()}.
+     */
+    private Charset charsetDefault = Charset.defaultCharset();
+
+    /**
+     * Gets the buffer size, defaults to {@link IOUtils#DEFAULT_BUFFER_SIZE} ({@value IOUtils#DEFAULT_BUFFER_SIZE}).
+     *
+     * @return the buffer size, defaults to {@link IOUtils#DEFAULT_BUFFER_SIZE} ({@value IOUtils#DEFAULT_BUFFER_SIZE}).
+     */
+    protected int getBufferSize() {
+        return bufferSize;
+    }
+
+    /**
+     * Gets the buffer size default, defaults to {@link IOUtils#DEFAULT_BUFFER_SIZE} ({@value IOUtils#DEFAULT_BUFFER_SIZE}).
+     *
+     * @return the buffer size default, defaults to {@link IOUtils#DEFAULT_BUFFER_SIZE} ({@value IOUtils#DEFAULT_BUFFER_SIZE}).
+     */
+    protected int getBufferSizeDefault() {
+        return bufferSizeDefault;
+    }
+
+    /**
+     * Gets the Charset, defaults to {@link Charset#defaultCharset()}.
+     *
+     * @return the Charset, defaults to {@link Charset#defaultCharset()}.
+     */
+    protected Charset getCharset() {
+        return charset;
+    }
+
+    /**
+     * Gets the Charset default, defaults to {@link Charset#defaultCharset()}.
+     *
+     * @return the Charset default, defaults to {@link Charset#defaultCharset()}.
+     */
+    protected Charset getCharsetDefault() {
+        return charsetDefault;
+    }
+
+    /**
+     * Sets the buffer size.
+     * <p>
+     * Subclasses may ignore this setting.
+     * </p>
+     *
+     * @param bufferSize the buffer size.
+     * @return this.
+     */
+    public B setBufferSize(final int bufferSize) {
+        this.bufferSize = bufferSize >= 0 ? bufferSize : bufferSizeDefault;
+        return asThis();
+    }
+
+    /**
+     * Sets the buffer size.
+     * <p>
+     * Subclasses may ignore this setting.
+     * </p>
+     *
+     * @param bufferSize the buffer size, null resets to the default.
+     * @return this.
+     */
+    public B setBufferSize(final Integer bufferSize) {
+        setBufferSize(bufferSize != null ? bufferSize : bufferSizeDefault);
+        return asThis();
+    }
+
+    /**
+     * Sets the buffer size for subclasses to initialize.
+     * <p>
+     * Subclasses may ignore this setting.
+     * </p>
+     *
+     * @param bufferSizeDefault the buffer size, null resets to the default.
+     * @return this.
+     */
+    protected B setBufferSizeDefault(final int bufferSizeDefault) {
+        this.bufferSizeDefault = bufferSizeDefault;
+        return asThis();
+    }
+
+    /**
+     * Sets the Charset.
+     * <p>
+     * Subclasses may ignore this setting.
+     * </p>
+     *
+     * @param charset the Charset, null resets to the default.
+     * @return this.
+     */
+    public B setCharset(final Charset charset) {
+        this.charset = Charsets.toCharset(charset, charsetDefault);
+        return asThis();
+    }
+
+    /**
+     * Sets the Charset.
+     * <p>
+     * Subclasses may ignore this setting.
+     * </p>
+     *
+     * @param charset the Charset name, null resets to the default.
+     * @return this.
+     */
+    public B setCharset(final String charset) {
+        return setCharset(Charsets.toCharset(charset, charsetDefault));
+    }
+
+    /**
+     * Sets the Charset default for subclasses to initialize.
+     * <p>
+     * Subclasses may ignore this setting.
+     * </p>
+     *
+     * @param defaultCharset the Charset name, null resets to the default.
+     * @return this.
+     */
+    protected B setCharsetDefault(final Charset defaultCharset) {
+        this.charsetDefault = defaultCharset;
+        return asThis();
+    }
+}
diff --git a/src/main/java/org/apache/commons/io/build/AbstractSupplier.java b/src/main/java/org/apache/commons/io/build/AbstractSupplier.java
new file mode 100644
index 00000000..f35c9bca
--- /dev/null
+++ b/src/main/java/org/apache/commons/io/build/AbstractSupplier.java
@@ -0,0 +1,41 @@
+/*
+ * 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.commons.io.build;
+
+import org.apache.commons.io.function.IOSupplier;
+
+/**
+ * Abstracts supplying a typed instance of {@code T}. Use to implement the builder pattern.
+ *
+ * @param <T> the type of instances to build.
+ * @param <B> the type of builder subclass.
+ * @since 2.12.0
+ */
+public abstract class AbstractSupplier<T, B extends AbstractSupplier<T, B>> implements IOSupplier<T> {
+
+    /**
+     * Returns this instance typed as the proper subclass type.
+     *
+     * @return this instance typed as the proper subclass type.
+     */
+    @SuppressWarnings("unchecked")
+    protected B asThis() {
+        return (B) this;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/io/build/package-info.java b/src/main/java/org/apache/commons/io/build/package-info.java
new file mode 100644
index 00000000..1b9fc8e4
--- /dev/null
+++ b/src/main/java/org/apache/commons/io/build/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+/**
+ * Provides classes to implement IO builders.
+ *
+ * @since 2.12.0
+ */
+package org.apache.commons.io.build;
diff --git a/src/main/java/org/apache/commons/io/filefilter/WildcardFileFilter.java b/src/main/java/org/apache/commons/io/filefilter/WildcardFileFilter.java
index a532e441..90d9d73c 100644
--- a/src/main/java/org/apache/commons/io/filefilter/WildcardFileFilter.java
+++ b/src/main/java/org/apache/commons/io/filefilter/WildcardFileFilter.java
@@ -27,24 +27,22 @@ import java.util.stream.Stream;
 
 import org.apache.commons.io.FilenameUtils;
 import org.apache.commons.io.IOCase;
+import org.apache.commons.io.build.AbstractSupplier;
 
 /**
  * Filters files using the supplied wildcards.
  * <p>
- * This filter selects files and directories based on one or more wildcards.
- * Testing is case-sensitive by default, but this can be configured.
+ * This filter selects files and directories based on one or more wildcards. Testing is case-sensitive by default, but this can be configured.
  * </p>
  * <p>
- * The wildcard matcher uses the characters '?' and '*' to represent a
- * single or multiple wildcard characters.
- * This is the same as often found on DOS/Unix command lines.
- * The check is case-sensitive by default.
- * See {@link FilenameUtils#wildcardMatchOnSystem(String,String)} for more information.
+ * The wildcard matcher uses the characters '?' and '*' to represent a single or multiple wildcard characters. This is the same as often found on DOS/Unix
+ * command lines. The check is case-sensitive by default. See {@link FilenameUtils#wildcardMatchOnSystem(String,String)} for more information.
  * </p>
  * <p>
  * For example:
  * </p>
  * <h2>Using Classic IO</h2>
+ *
  * <pre>
  * File dir = FileUtils.current();
  * FileFilter fileFilter = new WildcardFileFilter("*test*.java~*~");
@@ -55,6 +53,7 @@ import org.apache.commons.io.IOCase;
  * </pre>
  *
  * <h2>Using NIO</h2>
+ *
  * <pre>
  * final Path dir = PathUtils.current();
  * final AccumulatorPathVisitor visitor = AccumulatorPathVisitor.withLongCounters(new WildcardFileFilter("*test*.java~*~"));
@@ -77,21 +76,102 @@ import org.apache.commons.io.IOCase;
  */
 public class WildcardFileFilter extends AbstractFileFilter implements Serializable {
 
+    /**
+     * Builds a new {@link WildcardFileFilter} instance.
+     *
+     * @since 2.12.02
+     */
+    public static class Builder extends AbstractSupplier<WildcardFileFilter, Builder> {
+
+        /** The wildcards that will be used to match file names. */
+        private String[] wildcards;
+
+        /** Whether the comparison is case-sensitive. */
+        private IOCase ioCase = IOCase.SENSITIVE;
+
+        @Override
+        public WildcardFileFilter get() {
+            return new WildcardFileFilter(ioCase, wildcards);
+        }
+
+        /**
+         * Sets how to handle case sensitivity, null means case-sensitive.
+         *
+         * @param ioCase how to handle case sensitivity, null means case-sensitive.
+         * @return this
+         */
+        public Builder setIoCase(final IOCase ioCase) {
+            this.ioCase = IOCase.value(ioCase, IOCase.SENSITIVE);
+            return this;
+        }
+
+        /**
+         * Sets the list of wildcards to match, not null.
+         *
+         * @param wildcards the list of wildcards to match, not null.
+         * @return this
+         */
+        public Builder setWildcards(final List<String> wildcards) {
+            setWildcards(requireWildcards(wildcards).toArray(EMPTY_STRING_ARRAY));
+            return this;
+        }
+
+        /**
+         * Sets the wildcards to match, not null.
+         *
+         * @param wildcards the wildcards to match, not null.
+         * @return this
+         */
+        public Builder setWildcards(final String... wildcards) {
+            this.wildcards = requireWildcards(wildcards);
+            return this;
+        }
+
+    }
+
+    /**
+     * Constructs a new {@link Builder}.
+     *
+     * @return a new {@link Builder}.
+     * @since 2.12.0
+     */
+    public static Builder builder() {
+        return new Builder();
+    }
+
     private static final long serialVersionUID = -7426486598995782105L;
 
+    private static <T> T requireWildcards(final T wildcards) {
+        return Objects.requireNonNull(wildcards, "wildcards");
+    }
+
     /** The wildcards that will be used to match file names. */
     private final String[] wildcards;
 
     /** Whether the comparison is case-sensitive. */
     private final IOCase ioCase;
 
+    /**
+     * Constructs a new wildcard filter for an array of wildcards specifying case-sensitivity.
+     *
+     * @param wildcards the array of wildcards to match, not null
+     * @param ioCase    how to handle case sensitivity, null means case-sensitive
+     * @throws NullPointerException if the pattern array is null
+     */
+    private WildcardFileFilter(final IOCase ioCase, final String... wildcards) {
+        this.wildcards = requireWildcards(wildcards).clone();
+        this.ioCase = IOCase.value(ioCase, IOCase.SENSITIVE);
+    }
+
     /**
      * Constructs a new case-sensitive wildcard filter for a list of wildcards.
      *
-     * @param wildcards  the list of wildcards to match, not null
+     * @param wildcards the list of wildcards to match, not null
      * @throws IllegalArgumentException if the pattern list is null
-     * @throws ClassCastException if the list does not contain Strings
+     * @throws ClassCastException       if the list does not contain Strings
+     * @deprecated Use {@link #builder()}
      */
+    @Deprecated
     public WildcardFileFilter(final List<String> wildcards) {
         this(wildcards, IOCase.SENSITIVE);
     }
@@ -99,67 +179,71 @@ public class WildcardFileFilter extends AbstractFileFilter implements Serializab
     /**
      * Constructs a new wildcard filter for a list of wildcards specifying case-sensitivity.
      *
-     * @param wildcards  the list of wildcards to match, not null
-     * @param ioCase  how to handle case sensitivity, null means case-sensitive
+     * @param wildcards the list of wildcards to match, not null
+     * @param ioCase    how to handle case sensitivity, null means case-sensitive
      * @throws IllegalArgumentException if the pattern list is null
-     * @throws ClassCastException if the list does not contain Strings
+     * @throws ClassCastException       if the list does not contain Strings
+     * @deprecated Use {@link #builder()}
      */
+    @Deprecated
     public WildcardFileFilter(final List<String> wildcards, final IOCase ioCase) {
-        Objects.requireNonNull(wildcards, "wildcards");
-        this.wildcards = wildcards.toArray(EMPTY_STRING_ARRAY);
-        this.ioCase = IOCase.value(ioCase, IOCase.SENSITIVE);
+        this(ioCase, requireWildcards(wildcards).toArray(EMPTY_STRING_ARRAY));
     }
 
     /**
      * Constructs a new case-sensitive wildcard filter for a single wildcard.
      *
-     * @param wildcard  the wildcard to match
+     * @param wildcard the wildcard to match
      * @throws IllegalArgumentException if the pattern is null
+     * @deprecated Use {@link #builder()}
      */
+    @Deprecated
     public WildcardFileFilter(final String wildcard) {
-        this(wildcard, IOCase.SENSITIVE);
+        this(IOCase.SENSITIVE, requireWildcards(wildcard));
     }
 
     /**
      * Constructs a new case-sensitive wildcard filter for an array of wildcards.
      *
-     * @param wildcards  the array of wildcards to match
+     * @param wildcards the array of wildcards to match
      * @throws NullPointerException if the pattern array is null
+     * @deprecated Use {@link #builder()}
      */
+    @Deprecated
     public WildcardFileFilter(final String... wildcards) {
-        this(wildcards, IOCase.SENSITIVE);
+        this(IOCase.SENSITIVE, wildcards);
     }
 
     /**
      * Constructs a new wildcard filter for a single wildcard specifying case-sensitivity.
      *
-     * @param wildcard  the wildcard to match, not null
-     * @param ioCase  how to handle case sensitivity, null means case-sensitive
+     * @param wildcard the wildcard to match, not null
+     * @param ioCase   how to handle case sensitivity, null means case-sensitive
      * @throws NullPointerException if the pattern is null
+     * @deprecated Use {@link #builder()}
      */
+    @Deprecated
     public WildcardFileFilter(final String wildcard, final IOCase ioCase) {
-        Objects.requireNonNull(wildcard, "wildcard");
-        this.wildcards = new String[] {wildcard};
-        this.ioCase = IOCase.value(ioCase, IOCase.SENSITIVE);
+        this(ioCase, wildcard);
     }
 
     /**
      * Constructs a new wildcard filter for an array of wildcards specifying case-sensitivity.
      *
-     * @param wildcards  the array of wildcards to match, not null
-     * @param ioCase  how to handle case sensitivity, null means case-sensitive
+     * @param wildcards the array of wildcards to match, not null
+     * @param ioCase    how to handle case sensitivity, null means case-sensitive
      * @throws NullPointerException if the pattern array is null
+     * @deprecated Use {@link #builder()}
      */
+    @Deprecated
     public WildcardFileFilter(final String[] wildcards, final IOCase ioCase) {
-        Objects.requireNonNull(wildcards, "wildcards");
-        this.wildcards = wildcards.clone();
-        this.ioCase = IOCase.value(ioCase, IOCase.SENSITIVE);
+        this(ioCase, wildcards);
     }
 
     /**
      * Checks to see if the file name matches one of the wildcards.
      *
-     * @param file  the file to check
+     * @param file the file to check
      * @return true if the file name matches one of the wildcards
      */
     @Override
@@ -171,7 +255,7 @@ public class WildcardFileFilter extends AbstractFileFilter implements Serializab
      * Checks to see if the file name matches one of the wildcards.
      *
      * @param dir  the file directory (ignored)
-     * @param name  the file name
+     * @param name the file name
      * @return true if the file name matches one of the wildcards
      */
     @Override
@@ -181,7 +265,8 @@ public class WildcardFileFilter extends AbstractFileFilter implements Serializab
 
     /**
      * Checks to see if the file name matches one of the wildcards.
-     * @param file  the file to check
+     *
+     * @param file the file to check
      *
      * @return true if the file name matches one of the wildcards.
      * @since 2.9.0
diff --git a/src/main/java/org/apache/commons/io/input/BOMInputStream.java b/src/main/java/org/apache/commons/io/input/BOMInputStream.java
index 7098b4c0..6aeae3f1 100644
--- a/src/main/java/org/apache/commons/io/input/BOMInputStream.java
+++ b/src/main/java/org/apache/commons/io/input/BOMInputStream.java
@@ -27,6 +27,7 @@ import java.util.Objects;
 
 import org.apache.commons.io.ByteOrderMark;
 import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.build.AbstractStreamBuilder;
 
 /**
  * This class is used to wrap a stream that includes an encoded {@link ByteOrderMark} as its first bytes.
@@ -90,10 +91,69 @@ import org.apache.commons.io.IOUtils;
  */
 public class BOMInputStream extends ProxyInputStream {
 
+    /**
+     * Builds a new {@link ReaderInputStream} instance.
+     * <p>
+     * For example:
+     * </p>
+     * <pre>{@code
+     * BOMInputStream s = BOMInputStream.builder()
+     *   .setByteOrderMarks(ByteOrderMark.UTF_8)
+     *   .setInclude(false)
+     *   .get()}
+     * </pre>
+     * <p>
+     * @since 2.12.02
+     */
+    public static class Builder extends AbstractStreamBuilder<BOMInputStream, Builder> {
+
+        private boolean include;
+        private ByteOrderMark[] byteOrderMarks = { ByteOrderMark.UTF_8 };
+
+        @SuppressWarnings("resource")
+        @Override
+        public BOMInputStream get() throws IOException {
+            return new BOMInputStream(getOrigin().getInputStream(), include, byteOrderMarks);
+        }
+
+        /**
+         * Sets the ByteOrderMarks to detect and optionally exclude.
+         *
+         * @param byteOrderMarks the ByteOrderMarks to detect and optionally exclude.
+         * @return this
+         */
+        public Builder setByteOrderMarks(final ByteOrderMark[] byteOrderMarks) {
+            this.byteOrderMarks = byteOrderMarks;
+            return this;
+        }
+
+        /**
+         * Sets whether to include the UTF-8 BOM (true) or to exclude it (false).
+         *
+         * @param include true to include the UTF-8 BOM or false to exclude it. return this;
+         * @return this
+         */
+        public Builder setInclude(final boolean include) {
+            this.include = include;
+            return this;
+        }
+
+    }
+
     /**
      * Compares ByteOrderMark objects in descending length order.
      */
-    private static final Comparator<ByteOrderMark> ByteOrderMarkLengthComparator = (bom1, bom2) -> Integer.compare(bom2.length(), bom1.length());
+    private static final Comparator<ByteOrderMark> ByteOrderMarkLengthComparator = Comparator.comparing(ByteOrderMark::length).reversed();
+
+    /**
+     * Constructs a new {@link Builder}.
+     *
+     * @return a new {@link Builder}.
+     * @since 2.12.0
+     */
+    public static Builder builder() {
+        return new Builder();
+    }
 
     private final boolean include;
 
@@ -113,7 +173,9 @@ public class BOMInputStream extends ProxyInputStream {
      *
      * @param delegate
      *            the InputStream to delegate to
+     * @deprecated Use {@link #builder()}
      */
+    @Deprecated
     public BOMInputStream(final InputStream delegate) {
         this(delegate, false, ByteOrderMark.UTF_8);
     }
@@ -125,7 +187,9 @@ public class BOMInputStream extends ProxyInputStream {
      *            the InputStream to delegate to
      * @param include
      *            true to include the UTF-8 BOM or false to exclude it
+     * @deprecated Use {@link #builder()}
      */
+    @Deprecated
     public BOMInputStream(final InputStream delegate, final boolean include) {
         this(delegate, include, ByteOrderMark.UTF_8);
     }
@@ -139,7 +203,9 @@ public class BOMInputStream extends ProxyInputStream {
      *            true to include the specified BOMs or false to exclude them
      * @param boms
      *            The BOMs to detect and optionally exclude
+     * @deprecated Use {@link #builder()}
      */
+    @Deprecated
     public BOMInputStream(final InputStream delegate, final boolean include, final ByteOrderMark... boms) {
         super(delegate);
         if (IOUtils.length(boms) == 0) {
@@ -160,7 +226,9 @@ public class BOMInputStream extends ProxyInputStream {
      *            the InputStream to delegate to
      * @param boms
      *            The BOMs to detect and exclude
+     * @deprecated Use {@link #builder()}
      */
+    @Deprecated
     public BOMInputStream(final InputStream delegate, final ByteOrderMark... boms) {
         this(delegate, false, boms);
     }
diff --git a/src/main/java/org/apache/commons/io/input/BufferedFileChannelInputStream.java b/src/main/java/org/apache/commons/io/input/BufferedFileChannelInputStream.java
index 70f7d5cc..3ac4810c 100644
--- a/src/main/java/org/apache/commons/io/input/BufferedFileChannelInputStream.java
+++ b/src/main/java/org/apache/commons/io/input/BufferedFileChannelInputStream.java
@@ -25,6 +25,8 @@ import java.nio.file.StandardOpenOption;
 import java.util.Objects;
 
 import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.build.AbstractStreamBuilder;
+import org.apache.commons.io.output.DeferredFileOutputStream;
 
 /**
  * {@link InputStream} implementation which uses direct buffer to read a file to avoid extra copy of data between Java
@@ -40,6 +42,48 @@ import org.apache.commons.io.IOUtils;
  */
 public final class BufferedFileChannelInputStream extends InputStream {
 
+    /**
+     * Builds a new {@link DeferredFileOutputStream} instance.
+     * <p>
+     * Using File IO:
+     * </p>
+     * <pre>{@code
+     * BufferedFileChannelInputStream s = BufferedFileChannelInputStream.builder()
+     *   .setFile(file)
+     *   .setBufferSize(4096)
+     *   .get()}
+     * </pre>
+     * <p>
+     * Using NIO Path:
+     * </p>
+     * <pre>{@code
+     * BufferedFileChannelInputStream s = BufferedFileChannelInputStream.builder()
+     *   .setPath(path)
+     *   .setBufferSize(4096)
+     *   .get()}
+     * </pre>
+     *
+     * @since 2.12.02
+     */
+    public static class Builder extends AbstractStreamBuilder<BufferedFileChannelInputStream, Builder> {
+
+        @Override
+        public BufferedFileChannelInputStream get() throws IOException {
+            return new BufferedFileChannelInputStream(getOrigin().getPath(), getBufferSize());
+        }
+
+    }
+
+    /**
+     * Constructs a new {@link Builder}.
+     *
+     * @return a new {@link Builder}.
+     * @since 2.12.0
+     */
+    public static Builder builder() {
+        return new Builder();
+    }
+
     private final ByteBuffer byteBuffer;
 
     private final FileChannel fileChannel;
@@ -49,7 +93,9 @@ public final class BufferedFileChannelInputStream extends InputStream {
      *
      * @param file The file to stream.
      * @throws IOException If an I/O error occurs
+     * @deprecated Use {@link #builder()}
      */
+    @Deprecated
     public BufferedFileChannelInputStream(final File file) throws IOException {
         this(file, IOUtils.DEFAULT_BUFFER_SIZE);
     }
@@ -60,7 +106,9 @@ public final class BufferedFileChannelInputStream extends InputStream {
      * @param file The file to stream.
      * @param bufferSize buffer size.
      * @throws IOException If an I/O error occurs
+     * @deprecated Use {@link #builder()}
      */
+    @Deprecated
     public BufferedFileChannelInputStream(final File file, final int bufferSize) throws IOException {
         this(file.toPath(), bufferSize);
     }
@@ -70,7 +118,9 @@ public final class BufferedFileChannelInputStream extends InputStream {
      *
      * @param path The path to stream.
      * @throws IOException If an I/O error occurs
+     * @deprecated Use {@link #builder()}
      */
+    @Deprecated
     public BufferedFileChannelInputStream(final Path path) throws IOException {
         this(path, IOUtils.DEFAULT_BUFFER_SIZE);
     }
@@ -81,7 +131,9 @@ public final class BufferedFileChannelInputStream extends InputStream {
      * @param path The path to stream.
      * @param bufferSize buffer size.
      * @throws IOException If an I/O error occurs
+     * @deprecated Use {@link #builder()}
      */
+    @Deprecated
     public BufferedFileChannelInputStream(final Path path, final int bufferSize) throws IOException {
         Objects.requireNonNull(path, "path");
         fileChannel = FileChannel.open(path, StandardOpenOption.READ);
diff --git a/src/main/java/org/apache/commons/io/input/MemoryMappedFileInputStream.java b/src/main/java/org/apache/commons/io/input/MemoryMappedFileInputStream.java
index 797a41e9..a96a7838 100644
--- a/src/main/java/org/apache/commons/io/input/MemoryMappedFileInputStream.java
+++ b/src/main/java/org/apache/commons/io/input/MemoryMappedFileInputStream.java
@@ -27,6 +27,8 @@ import java.nio.channels.FileChannel.MapMode;
 import java.nio.file.Path;
 import java.nio.file.StandardOpenOption;
 
+import org.apache.commons.io.build.AbstractStreamBuilder;
+
 /**
  * An {@link InputStream} that utilizes memory mapped files to improve performance. A sliding window of the file is
  * mapped to memory to avoid mapping the entire file to memory at one time. The size of the sliding buffer is
@@ -54,12 +56,51 @@ import java.nio.file.StandardOpenOption;
  */
 public class MemoryMappedFileInputStream extends InputStream {
 
+    /**
+     * Builds a new {@link ReaderInputStream} instance.
+     * <p>
+     * For example:
+     * </p>
+     * <pre>{@code
+     * MemoryMappedFileInputStream s = MemoryMappedFileInputStream.builder()
+     *   .setPath(path)
+     *   .setBufferSize(256 * 1024)
+     *   .get()}
+     * </pre>
+     * <p>
+     * @since 2.12.0
+     */
+    public static class Builder extends AbstractStreamBuilder<MemoryMappedFileInputStream, Builder> {
+
+        public Builder() {
+            setBufferSizeDefault(DEFAULT_BUFFER_SIZE);
+            setBufferSize(DEFAULT_BUFFER_SIZE);
+        }
+
+        @Override
+        public MemoryMappedFileInputStream get() throws IOException {
+            return new MemoryMappedFileInputStream(getOrigin().getPath(), getBufferSize());
+        }
+    }
+
     /**
      * Default size of the sliding memory mapped buffer. We use 256K, equal to 65536 pages (given a 4K page size).
      * Increasing the value beyond the default size will generally not provide any increase in throughput.
      */
     private static final int DEFAULT_BUFFER_SIZE = 256 * 1024;
+
     private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.wrap(new byte[0]).asReadOnlyBuffer();
+
+    /**
+     * Constructs a new {@link Builder}.
+     *
+     * @return a new {@link Builder}.
+     * @since 2.12.0
+     */
+    public static Builder builder() {
+        return new Builder();
+    }
+
     private final int bufferSize;
     private final FileChannel channel;
     private ByteBuffer buffer = EMPTY_BUFFER;
@@ -75,7 +116,9 @@ public class MemoryMappedFileInputStream extends InputStream {
      *
      * @param file The path of the file to open.
      * @throws IOException If an I/O error occurs
+     * @deprecated Use {@link #builder()}
      */
+    @Deprecated
     public MemoryMappedFileInputStream(final Path file) throws IOException {
         this(file, DEFAULT_BUFFER_SIZE);
     }
@@ -86,7 +129,9 @@ public class MemoryMappedFileInputStream extends InputStream {
      * @param file The path of the file to open.
      * @param bufferSize Size of the sliding buffer.
      * @throws IOException If an I/O error occurs.
+     * @deprecated Use {@link #builder()}
      */
+    @Deprecated
     public MemoryMappedFileInputStream(final Path file, final int bufferSize) throws IOException {
         this.bufferSize = bufferSize;
         this.channel = FileChannel.open(file, StandardOpenOption.READ);
@@ -119,6 +164,10 @@ public class MemoryMappedFileInputStream extends InputStream {
         }
     }
 
+    int getBufferSize() {
+        return bufferSize;
+    }
+
     private void nextBuffer() throws IOException {
         final long remainingInFile = channel.size() - nextBufferPosition;
         if (remainingInFile > 0) {
diff --git a/src/main/java/org/apache/commons/io/input/MessageDigestCalculatingInputStream.java b/src/main/java/org/apache/commons/io/input/MessageDigestCalculatingInputStream.java
index 5062c352..f66830f2 100644
--- a/src/main/java/org/apache/commons/io/input/MessageDigestCalculatingInputStream.java
+++ b/src/main/java/org/apache/commons/io/input/MessageDigestCalculatingInputStream.java
@@ -22,23 +22,90 @@ import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.security.Provider;
 
+import org.apache.commons.io.build.AbstractStreamBuilder;
 
 /**
- * This class is an example for using an {@link ObservableInputStream}. It creates its own
- * {@link org.apache.commons.io.input.ObservableInputStream.Observer}, which calculates a checksum using a
- * MessageDigest, for example an MD5 sum.
+ * This class is an example for using an {@link ObservableInputStream}. It creates its own {@link org.apache.commons.io.input.ObservableInputStream.Observer},
+ * which calculates a checksum using a MessageDigest, for example an MD5 sum.
  * <p>
- * See the MessageDigest section in the
- * <a href= "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#MessageDigest"> Java
+ * See the MessageDigest section in the <a href= "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#MessageDigest"> Java
  * Cryptography Architecture Standard Algorithm Name Documentation</a> for information about standard algorithm names.
  * </p>
  * <p>
- * <em>Note</em>: Neither {@link ObservableInputStream}, nor {@link MessageDigest}, are thread safe. So is
- * {@link MessageDigestCalculatingInputStream}.
+ * <em>Note</em>: Neither {@link ObservableInputStream}, nor {@link MessageDigest}, are thread safe. So is {@link MessageDigestCalculatingInputStream}.
+ * </p>
+ * <p>
+ * TODO Rename to MessageDigestInputStream in 3.0.
  * </p>
  */
 public class MessageDigestCalculatingInputStream extends ObservableInputStream {
 
+    /**
+     * Builds a new {@link ReaderInputStream} instance.
+     * <p>
+     * For example:
+     * </p>
+     * <pre>{@code
+     * MessageDigestCalculatingInputStream s = MessageDigestCalculatingInputStream.builder()
+     *   .setPath(path)
+     *   .setMessageDigest("SHA-512")
+     *   .get()}
+     * </pre>
+     * <p>
+     * @since 2.12.02
+     */
+    public static class Builder extends AbstractStreamBuilder<MessageDigestCalculatingInputStream, Builder> {
+
+        private MessageDigest messageDigest;
+
+        public Builder() {
+            try {
+                this.messageDigest = getDefaultMessageDigest();
+            } catch (final NoSuchAlgorithmException e) {
+                // Should not happen.
+                throw new IllegalStateException(e);
+            }
+        }
+
+        @SuppressWarnings("resource")
+        @Override
+        public MessageDigestCalculatingInputStream get() throws IOException {
+            return new MessageDigestCalculatingInputStream(getOrigin().getInputStream(), messageDigest);
+        }
+
+        /**
+         * Sets the message digest.
+         *
+         * @param messageDigest the message digest.
+         */
+        public void setMessageDigest(final MessageDigest messageDigest) {
+            this.messageDigest = messageDigest;
+        }
+
+        /**
+         * Sets the name of the name of the message digest algorithm.
+         *
+         * @param algorithm the name of the algorithm. See the MessageDigest section in the
+         *                  <a href= "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#MessageDigest"> Java Cryptography
+         *                  Architecture Standard Algorithm Name Documentation</a> for information about standard algorithm names.
+         * @throws NoSuchAlgorithmException if no Provider supports a MessageDigestSpi implementation for the specified algorithm.
+         */
+        public void setMessageDigest(final String algorithm) throws NoSuchAlgorithmException {
+            this.messageDigest = MessageDigest.getInstance(algorithm);
+        }
+
+    }
+
+    /**
+     * Constructs a new {@link Builder}.
+     *
+     * @return a new {@link Builder}.
+     * @since 2.12.0
+     */
+    public static Builder builder() {
+        return new Builder();
+    }
+
     /**
      * Maintains the message digest.
      */
@@ -47,6 +114,7 @@ public class MessageDigestCalculatingInputStream extends ObservableInputStream {
 
         /**
          * Creates an MessageDigestMaintainingObserver for the given MessageDigest.
+         *
          * @param messageDigest the message digest to use
          */
         public MessageDigestMaintainingObserver(final MessageDigest messageDigest) {
@@ -86,56 +154,53 @@ public class MessageDigestCalculatingInputStream extends ObservableInputStream {
     private final MessageDigest messageDigest;
 
     /**
-     * Creates a new instance, which calculates a signature on the given stream, using a {@link MessageDigest} with the
-     * "MD5" algorithm.
+     * Creates a new instance, which calculates a signature on the given stream, using a {@link MessageDigest} with the "MD5" algorithm.
      * <p>
      * The MD5 algorithm is weak and should not be used.
      * </p>
      *
      * @param inputStream the stream to calculate the message digest for
-     * @throws NoSuchAlgorithmException if no Provider supports a MessageDigestSpi implementation for the specified
-     *         algorithm.
+     * @throws NoSuchAlgorithmException if no Provider supports a MessageDigestSpi implementation for the specified algorithm.
+     * @deprecated Use {@link #builder()}.
      */
+    @Deprecated
     public MessageDigestCalculatingInputStream(final InputStream inputStream) throws NoSuchAlgorithmException {
         this(inputStream, getDefaultMessageDigest());
     }
 
     /**
-     * Creates a new instance, which calculates a signature on the given stream,
-     * using the given {@link MessageDigest}.
-     * @param inputStream the stream to calculate the message digest for
+     * Creates a new instance, which calculates a signature on the given stream, using the given {@link MessageDigest}.
+     *
+     * @param inputStream   the stream to calculate the message digest for
      * @param messageDigest the message digest to use
+     * @deprecated Use {@link #builder()}.
      */
+    @Deprecated
     public MessageDigestCalculatingInputStream(final InputStream inputStream, final MessageDigest messageDigest) {
         super(inputStream, new MessageDigestMaintainingObserver(messageDigest));
         this.messageDigest = messageDigest;
     }
 
     /**
-     * Creates a new instance, which calculates a signature on the given stream, using a {@link MessageDigest} with the
-     * given algorithm.
+     * Creates a new instance, which calculates a signature on the given stream, using a {@link MessageDigest} with the given algorithm.
      *
      * @param inputStream the stream to calculate the message digest for
-     * @param algorithm the name of the algorithm requested. See the MessageDigest section in the
-     *        <a href= "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#MessageDigest">
-     *        Java Cryptography Architecture Standard Algorithm Name Documentation</a> for information about standard
-     *        algorithm names.
-     * @throws NoSuchAlgorithmException if no Provider supports a MessageDigestSpi implementation for the specified
-     *         algorithm.
+     * @param algorithm   the name of the algorithm requested. See the MessageDigest section in the
+     *                    <a href= "https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#MessageDigest"> Java Cryptography
+     *                    Architecture Standard Algorithm Name Documentation</a> for information about standard algorithm names.
+     * @throws NoSuchAlgorithmException if no Provider supports a MessageDigestSpi implementation for the specified algorithm.
+     * @deprecated Use {@link #builder()}.
      */
-    public MessageDigestCalculatingInputStream(final InputStream inputStream, final String algorithm)
-        throws NoSuchAlgorithmException {
+    @Deprecated
+    public MessageDigestCalculatingInputStream(final InputStream inputStream, final String algorithm) throws NoSuchAlgorithmException {
         this(inputStream, MessageDigest.getInstance(algorithm));
     }
 
     /**
-     * Gets the {@link MessageDigest}, which is being used for generating the
-     * checksum.
+     * Gets the {@link MessageDigest}, which is being used for generating the checksum.
      * <p>
-     * <em>Note</em>: The checksum will only reflect the data, which has been read so far.
-     * This is probably not, what you expect. Make sure, that the complete data has been
-     * read, if that is what you want. The easiest way to do so is by invoking
-     * {@link #consume()}.
+     * <em>Note</em>: The checksum will only reflect the data, which has been read so far. This is probably not, what you expect. Make sure, that the complete
+     * data has been read, if that is what you want. The easiest way to do so is by invoking {@link #consume()}.
      * </p>
      *
      * @return the message digest used
diff --git a/src/main/java/org/apache/commons/io/input/RandomAccessFileInputStream.java b/src/main/java/org/apache/commons/io/input/RandomAccessFileInputStream.java
index 584d8f32..fcc79552 100644
--- a/src/main/java/org/apache/commons/io/input/RandomAccessFileInputStream.java
+++ b/src/main/java/org/apache/commons/io/input/RandomAccessFileInputStream.java
@@ -22,6 +22,10 @@ import java.io.InputStream;
 import java.io.RandomAccessFile;
 import java.util.Objects;
 
+import org.apache.commons.io.RandomAccessFileMode;
+import org.apache.commons.io.build.AbstractStreamBuilder;
+import org.apache.commons.io.output.DeferredFileOutputStream;
+
 /**
  * Streams data from a {@link RandomAccessFile} starting at its current position.
  *
@@ -29,6 +33,71 @@ import java.util.Objects;
  */
 public class RandomAccessFileInputStream extends InputStream {
 
+    /**
+     * Builds a new {@link DeferredFileOutputStream} instance.
+     * <p>
+     * For example:
+     * </p>
+     * <pre>{@code
+     * RandomAccessFileInputStream s = RandomAccessFileInputStream.builder()
+     *   .setPath(path)
+     *   .setCloseOnClose(true)
+     *   .get()}
+     * </pre>
+     * <p>
+     * @since 2.12.02
+     */
+    public static class Builder extends AbstractStreamBuilder<RandomAccessFileInputStream, Builder> {
+
+        private RandomAccessFile randomAccessFile;
+        private boolean closeOnClose;
+
+        @SuppressWarnings("resource") // Caller closes depending on settings
+        @Override
+        public RandomAccessFileInputStream get() throws IOException {
+            if (randomAccessFile != null) {
+                if (getOrigin() != null) {
+                    throw new IllegalStateException(String.format("Only set one of RandomAccessFile (%s) or origin (%s)", randomAccessFile, getOrigin()));
+                }
+                return new RandomAccessFileInputStream(randomAccessFile, closeOnClose);
+            }
+            return new RandomAccessFileInputStream(RandomAccessFileMode.READ_ONLY.create(getOrigin().getFile()), closeOnClose);
+        }
+
+        /**
+         * Sets whether to close the underlying file when this stream is closed.
+         *
+         * @param closeOnClose Whether to close the underlying file when this stream is closed.
+         * @return this
+         */
+        public Builder setCloseOnClose(final boolean closeOnClose) {
+            this.closeOnClose = closeOnClose;
+            return this;
+        }
+
+        /**
+         * Sets the RandomAccessFile to stream.
+         *
+         * @param randomAccessFile the RandomAccessFile to stream.
+         * @return this
+         */
+        public Builder setRandomAccessFile(final RandomAccessFile randomAccessFile) {
+            this.randomAccessFile = randomAccessFile;
+            return this;
+        }
+
+    }
+
+    /**
+     * Constructs a new {@link Builder}.
+     *
+     * @return a new {@link Builder}.
+     * @since 2.12.0
+     */
+    public static Builder builder() {
+        return new Builder();
+    }
+
     private final boolean closeOnClose;
     private final RandomAccessFile randomAccessFile;
 
@@ -36,7 +105,9 @@ public class RandomAccessFileInputStream extends InputStream {
      * Constructs a new instance configured to leave the underlying file open when this stream is closed.
      *
      * @param file The file to stream.
+     * @deprecated Use {@link #builder()}
      */
+    @Deprecated
     public RandomAccessFileInputStream(final RandomAccessFile file) {
         this(file, false);
     }
@@ -44,9 +115,11 @@ public class RandomAccessFileInputStream extends InputStream {
     /**
      * Constructs a new instance.
      *
-     * @param file The file to stream.
+     * @param file         The file to stream.
      * @param closeOnClose Whether to close the underlying file when this stream is closed.
+     * @deprecated Use {@link #builder()}
      */
+    @Deprecated
     public RandomAccessFileInputStream(final RandomAccessFile file, final boolean closeOnClose) {
         this.randomAccessFile = Objects.requireNonNull(file, "file");
         this.closeOnClose = closeOnClose;
diff --git a/src/main/java/org/apache/commons/io/input/ReadAheadInputStream.java b/src/main/java/org/apache/commons/io/input/ReadAheadInputStream.java
index 1939f266..c6de88d4 100644
--- a/src/main/java/org/apache/commons/io/input/ReadAheadInputStream.java
+++ b/src/main/java/org/apache/commons/io/input/ReadAheadInputStream.java
@@ -29,12 +29,13 @@ import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.locks.Condition;
 import java.util.concurrent.locks.ReentrantLock;
 
+import org.apache.commons.io.build.AbstractStreamBuilder;
+
 /**
- * Implements {@link InputStream} to asynchronously read ahead from an underlying input stream when a specified amount
- * of data has been read from the current buffer. It does so by maintaining two buffers: an active buffer and a read
- * ahead buffer. The active buffer contains data which should be returned when a read() call is issued. The read ahead
- * buffer is used to asynchronously read from the underlying input stream. When the current active buffer is exhausted,
- * we flip the two buffers so that we can start reading from the read ahead buffer without being blocked by disk I/O.
+ * Implements {@link InputStream} to asynchronously read ahead from an underlying input stream when a specified amount of data has been read from the current
+ * buffer. It does so by maintaining two buffers: an active buffer and a read ahead buffer. The active buffer contains data which should be returned when a
+ * read() call is issued. The read ahead buffer is used to asynchronously read from the underlying input stream. When the current active buffer is exhausted, we
+ * flip the two buffers so that we can start reading from the read ahead buffer without being blocked by disk I/O.
  * <p>
  * This class was ported and adapted from Apache Spark commit 933dc6cb7b3de1d8ccaf73d124d6eb95b947ed19.
  * </p>
@@ -43,6 +44,54 @@ import java.util.concurrent.locks.ReentrantLock;
  */
 public class ReadAheadInputStream extends InputStream {
 
+    /**
+     * Builds a new {@link ReaderInputStream} instance.
+     * <p>
+     * For example:
+     * </p>
+     * <pre>{@code
+     * ReadAheadInputStream s = ReadAheadInputStream.builder()
+     *   .setPath(path)
+     *   .setExecutorService(Executors.newSingleThreadExecutor(ReadAheadInputStream::newThread))
+     *   .get()}
+     * </pre>
+     * <p>
+     * @since 2.12.02
+     */
+    public static class Builder extends AbstractStreamBuilder<ReadAheadInputStream, Builder> {
+
+        private ExecutorService executorService;
+
+        @SuppressWarnings("resource")
+        @Override
+        public ReadAheadInputStream get() throws IOException {
+            return new ReadAheadInputStream(getOrigin().getInputStream(), getBufferSize(), executorService != null ? executorService : newExecutorService(),
+                    executorService == null);
+        }
+
+        /**
+         * Sets the executor service for the read-ahead thread.
+         *
+         * @param executorService the executor service for the read-ahead thread.
+         * @return this
+         */
+        public Builder setExecutorService(final ExecutorService executorService) {
+            this.executorService = executorService;
+            return this;
+        }
+
+    }
+
+    /**
+     * Constructs a new {@link Builder}.
+     *
+     * @return a new {@link Builder}.
+     * @since 2.12.0
+     */
+    public static Builder builder() {
+        return new Builder();
+    }
+
     private static final ThreadLocal<byte[]> BYTE_ARRAY_1 = ThreadLocal.withInitial(() -> new byte[1]);
 
     /**
@@ -115,9 +164,11 @@ public class ReadAheadInputStream extends InputStream {
     /**
      * Creates an instance with the specified buffer size and read-ahead threshold
      *
-     * @param inputStream The underlying input stream.
+     * @param inputStream       The underlying input stream.
      * @param bufferSizeInBytes The buffer size.
+     * @deprecated Use {@link #builder()}
      */
+    @Deprecated
     public ReadAheadInputStream(final InputStream inputStream, final int bufferSizeInBytes) {
         this(inputStream, bufferSizeInBytes, newExecutorService(), true);
     }
@@ -125,25 +176,26 @@ public class ReadAheadInputStream extends InputStream {
     /**
      * Creates an instance with the specified buffer size and read-ahead threshold
      *
-     * @param inputStream The underlying input stream.
+     * @param inputStream       The underlying input stream.
      * @param bufferSizeInBytes The buffer size.
-     * @param executorService An executor service for the read-ahead thread.
+     * @param executorService   An executor service for the read-ahead thread.
+     * @deprecated Use {@link #builder()}
      */
-    public ReadAheadInputStream(final InputStream inputStream, final int bufferSizeInBytes,
-        final ExecutorService executorService) {
+    @Deprecated
+    public ReadAheadInputStream(final InputStream inputStream, final int bufferSizeInBytes, final ExecutorService executorService) {
         this(inputStream, bufferSizeInBytes, executorService, false);
     }
 
     /**
      * Creates an instance with the specified buffer size and read-ahead threshold
      *
-     * @param inputStream The underlying input stream.
-     * @param bufferSizeInBytes The buffer size.
-     * @param executorService An executor service for the read-ahead thread.
+     * @param inputStream             The underlying input stream.
+     * @param bufferSizeInBytes       The buffer size.
+     * @param executorService         An executor service for the read-ahead thread.
      * @param shutdownExecutorService Whether or not to shut down the given ExecutorService on close.
      */
-    private ReadAheadInputStream(final InputStream inputStream, final int bufferSizeInBytes,
-        final ExecutorService executorService, final boolean shutdownExecutorService) {
+    private ReadAheadInputStream(final InputStream inputStream, final int bufferSizeInBytes, final ExecutorService executorService,
+            final boolean shutdownExecutorService) {
         if (bufferSizeInBytes <= 0) {
             throw new IllegalArgumentException("bufferSizeInBytes should be greater than 0, but the value is " + bufferSizeInBytes);
         }
@@ -396,8 +448,8 @@ public class ReadAheadInputStream extends InputStream {
     }
 
     /**
-     * Internal skip function which should be called only from skip(). The assumption is that the stateChangeLock is
-     * already acquired in the caller before calling this function.
+     * Internal skip function which should be called only from skip(). The assumption is that the stateChangeLock is already acquired in the caller before
+     * calling this function.
      *
      * @param n the number of bytes to be skipped.
      * @return the actual number of bytes skipped.
diff --git a/src/main/java/org/apache/commons/io/input/ReaderInputStream.java b/src/main/java/org/apache/commons/io/input/ReaderInputStream.java
index 2eb92226..fa9e3d80 100644
--- a/src/main/java/org/apache/commons/io/input/ReaderInputStream.java
+++ b/src/main/java/org/apache/commons/io/input/ReaderInputStream.java
@@ -31,24 +31,22 @@ import java.util.Objects;
 
 import org.apache.commons.io.Charsets;
 import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.build.AbstractStreamBuilder;
 import org.apache.commons.io.charset.CharsetEncoders;
 
 /**
- * {@link InputStream} implementation that reads a character stream from a {@link Reader} and transforms it to a byte
- * stream using a specified charset encoding. The stream is transformed using a {@link CharsetEncoder} object,
- * guaranteeing that all charset encodings supported by the JRE are handled correctly. In particular for charsets such
- * as UTF-16, the implementation ensures that one and only one byte order marker is produced.
+ * {@link InputStream} implementation that reads a character stream from a {@link Reader} and transforms it to a byte stream using a specified charset encoding.
+ * The stream is transformed using a {@link CharsetEncoder} object, guaranteeing that all charset encodings supported by the JRE are handled correctly. In
+ * particular for charsets such as UTF-16, the implementation ensures that one and only one byte order marker is produced.
  * <p>
- * Since in general it is not possible to predict the number of characters to be read from the {@link Reader} to satisfy
- * a read request on the {@link ReaderInputStream}, all reads from the {@link Reader} are buffered. There is therefore
- * no well defined correlation between the current position of the {@link Reader} and that of the
- * {@link ReaderInputStream}. This also implies that in general there is no need to wrap the underlying {@link Reader}
- * in a {@link java.io.BufferedReader}.
+ * Since in general it is not possible to predict the number of characters to be read from the {@link Reader} to satisfy a read request on the
+ * {@link ReaderInputStream}, all reads from the {@link Reader} are buffered. There is therefore no well defined correlation between the current position of the
+ * {@link Reader} and that of the {@link ReaderInputStream}. This also implies that in general there is no need to wrap the underlying {@link Reader} in a
+ * {@link java.io.BufferedReader}.
  * </p>
  * <p>
- * {@link ReaderInputStream} implements the inverse transformation of {@link java.io.InputStreamReader}; in the
- * following example, reading from {@code in2} would return the same byte sequence as reading from {@code in} (provided
- * that the initial byte sequence is legal with respect to the charset encoding):
+ * {@link ReaderInputStream} implements the inverse transformation of {@link java.io.InputStreamReader}; in the following example, reading from {@code in2}
+ * would return the same byte sequence as reading from {@code in} (provided that the initial byte sequence is legal with respect to the charset encoding):
  * </p>
  *
  * <pre>
@@ -58,21 +56,18 @@ import org.apache.commons.io.charset.CharsetEncoders;
  * ReaderInputStream in2 = new ReaderInputStream(reader, cs);
  * </pre>
  * <p>
- * {@link ReaderInputStream} implements the same transformation as {@link java.io.OutputStreamWriter}, except that the
- * control flow is reversed: both classes transform a character stream into a byte stream, but
- * {@link java.io.OutputStreamWriter} pushes data to the underlying stream, while {@link ReaderInputStream} pulls it
- * from the underlying stream.
+ * {@link ReaderInputStream} implements the same transformation as {@link java.io.OutputStreamWriter}, except that the control flow is reversed: both classes
+ * transform a character stream into a byte stream, but {@link java.io.OutputStreamWriter} pushes data to the underlying stream, while {@link ReaderInputStream}
+ * pulls it from the underlying stream.
  * </p>
  * <p>
- * Note that while there are use cases where there is no alternative to using this class, very often the need to use
- * this class is an indication of a flaw in the design of the code. This class is typically used in situations where an
- * existing API only accepts an {@link InputStream}, but where the most natural way to produce the data is as a
- * character stream, i.e. by providing a {@link Reader} instance. An example of a situation where this problem may
- * appear is when implementing the {@code javax.activation.DataSource} interface from the Java Activation Framework.
+ * Note that while there are use cases where there is no alternative to using this class, very often the need to use this class is an indication of a flaw in
+ * the design of the code. This class is typically used in situations where an existing API only accepts an {@link InputStream}, but where the most natural way
+ * to produce the data is as a character stream, i.e. by providing a {@link Reader} instance. An example of a situation where this problem may appear is when
+ * implementing the {@code javax.activation.DataSource} interface from the Java Activation Framework.
  * </p>
  * <p>
- * The {@link #available()} method of this class always returns 0. The methods {@link #mark(int)} and {@link #reset()}
- * are not supported.
+ * The {@link #available()} method of this class always returns 0. The methods {@link #mark(int)} and {@link #reset()} are not supported.
  * </p>
  * <p>
  * Instances of {@link ReaderInputStream} are not thread safe.
@@ -83,11 +78,65 @@ import org.apache.commons.io.charset.CharsetEncoders;
  */
 public class ReaderInputStream extends InputStream {
 
+    /**
+     * Builds a new {@link ReaderInputStream} instance.
+     * <p>
+     * For example:
+     * </p>
+     * <pre>{@code
+     * ReaderInputStream s = ReaderInputStream.builder()
+     *   .setPath(path)
+     *   .setCharsetEncoder(Charset.defaultCharset().newEncoder())
+     *   .get()}
+     * </pre>
+     * <p>
+     * @since 2.12.02
+     */
+    public static class Builder extends AbstractStreamBuilder<ReaderInputStream, Builder> {
+
+        private CharsetEncoder charsetEncoder = super.getCharset().newEncoder();
+
+        @SuppressWarnings("resource")
+        @Override
+        public ReaderInputStream get() throws IOException {
+            return new ReaderInputStream(getOrigin().getReader(getCharset()), charsetEncoder, getBufferSize());
+        }
+
+        @Override
+        public Builder setCharset(final Charset charset) {
+            charsetEncoder = charset.newEncoder();
+            return super.setCharset(charset);
+        }
+
+        /**
+         * Sets the charset encoder.
+         *
+         * @param charsetEncoder the charset encoder.
+         * @return this
+         */
+        public Builder setCharsetEncoder(final CharsetEncoder charsetEncoder) {
+            this.charsetEncoder = charsetEncoder;
+            super.setCharset(charsetEncoder.charset());
+            return asThis();
+        }
+
+    }
+
+    /**
+     * Constructs a new {@link Builder}.
+     *
+     * @return a new {@link Builder}.
+     * @since 2.12.0
+     */
+    public static Builder builder() {
+        return new Builder();
+    }
+
     static int checkMinBufferSize(final CharsetEncoder charsetEncoder, final int bufferSize) {
         final float minRequired = minBufferSize(charsetEncoder);
         if (bufferSize < minRequired) {
-            throw new IllegalArgumentException(
-                String.format("Buffer size %,d must be at least %s for a CharsetEncoder %s.", bufferSize, minRequired, charsetEncoder.charset().displayName()));
+            throw new IllegalArgumentException(String.format("Buffer size %,d must be at least %s for a CharsetEncoder %s.", bufferSize, minRequired,
+                    charsetEncoder.charset().displayName()));
         }
         return bufferSize;
     }
@@ -101,13 +150,12 @@ public class ReaderInputStream extends InputStream {
     private final CharsetEncoder charsetEncoder;
 
     /**
-     * CharBuffer used as input for the decoder. It should be reasonably large as we read data from the underlying Reader
-     * into this buffer.
+     * CharBuffer used as input for the decoder. It should be reasonably large as we read data from the underlying Reader into this buffer.
      */
     private final CharBuffer encoderIn;
     /**
-     * ByteBuffer used as output for the decoder. This buffer can be small as it is only used to transfer data from the
-     * decoder to the buffer provided by the caller.
+     * ByteBuffer used as output for the decoder. This buffer can be small as it is only used to transfer data from the decoder to the buffer provided by the
+     * caller.
      */
     private final ByteBuffer encoderOut;
 
@@ -120,7 +168,7 @@ public class ReaderInputStream extends InputStream {
      * {@value IOUtils#DEFAULT_BUFFER_SIZE} characters.
      *
      * @param reader the target {@link Reader}
-     * @deprecated 2.5 use {@link #ReaderInputStream(Reader, Charset)} instead
+     * @deprecated Use {@link ReaderInputStream#builder()} instead
      */
     @Deprecated
     public ReaderInputStream(final Reader reader) {
@@ -136,7 +184,9 @@ public class ReaderInputStream extends InputStream {
      *
      * @param reader  the target {@link Reader}
      * @param charset the charset encoding
+     * @deprecated Use {@link ReaderInputStream#builder()} instead
      */
+    @Deprecated
     public ReaderInputStream(final Reader reader, final Charset charset) {
         this(reader, charset, IOUtils.DEFAULT_BUFFER_SIZE);
     }
@@ -145,14 +195,15 @@ public class ReaderInputStream extends InputStream {
      * Constructs a new {@link ReaderInputStream}.
      *
      * <p>
-     * The encoder created for the specified charset will use {@link CodingErrorAction#REPLACE} for malformed input
-     * and unmappable characters.
+     * The encoder created for the specified charset will use {@link CodingErrorAction#REPLACE} for malformed input and unmappable characters.
      * </p>
      *
-     * @param reader the target {@link Reader}.
-     * @param charset the charset encoding.
+     * @param reader     the target {@link Reader}.
+     * @param charset    the charset encoding.
      * @param bufferSize the size of the input buffer in number of characters.
+     * @deprecated Use {@link ReaderInputStream#builder()} instead
      */
+    @Deprecated
     public ReaderInputStream(final Reader reader, final Charset charset, final int bufferSize) {
         // @formatter:off
         this(reader,
@@ -167,14 +218,16 @@ public class ReaderInputStream extends InputStream {
      * Constructs a new {@link ReaderInputStream}.
      *
      * <p>
-     * This constructor does not call {@link CharsetEncoder#reset() reset} on the provided encoder. The caller
-     * of this constructor should do this when providing an encoder which had already been in use.
+     * This constructor does not call {@link CharsetEncoder#reset() reset} on the provided encoder. The caller of this constructor should do this when providing
+     * an encoder which had already been in use.
      * </p>
      *
-     * @param reader the target {@link Reader}
+     * @param reader         the target {@link Reader}
      * @param charsetEncoder the charset encoder
      * @since 2.1
+     * @deprecated Use {@link ReaderInputStream#builder()} instead
      */
+    @Deprecated
     public ReaderInputStream(final Reader reader, final CharsetEncoder charsetEncoder) {
         this(reader, charsetEncoder, IOUtils.DEFAULT_BUFFER_SIZE);
     }
@@ -183,15 +236,17 @@ public class ReaderInputStream extends InputStream {
      * Constructs a new {@link ReaderInputStream}.
      *
      * <p>
-     * This constructor does not call {@link CharsetEncoder#reset() reset} on the provided encoder. The caller
-     * of this constructor should do this when providing an encoder which had already been in use.
+     * This constructor does not call {@link CharsetEncoder#reset() reset} on the provided encoder. The caller of this constructor should do this when providing
+     * an encoder which had already been in use.
      * </p>
      *
-     * @param reader the target {@link Reader}
+     * @param reader         the target {@link Reader}
      * @param charsetEncoder the charset encoder, null defaults to the default Charset encoder.
-     * @param bufferSize the size of the input buffer in number of characters
+     * @param bufferSize     the size of the input buffer in number of characters
      * @since 2.1
+     * @deprecated Use {@link ReaderInputStream#builder()} instead
      */
+    @Deprecated
     public ReaderInputStream(final Reader reader, final CharsetEncoder charsetEncoder, final int bufferSize) {
         this.reader = reader;
         this.charsetEncoder = CharsetEncoders.toCharsetEncoder(charsetEncoder);
@@ -210,7 +265,9 @@ public class ReaderInputStream extends InputStream {
      *
      * @param reader      the target {@link Reader}
      * @param charsetName the name of the charset encoding
+     * @deprecated Use {@link ReaderInputStream#builder()} instead
      */
+    @Deprecated
     public ReaderInputStream(final Reader reader, final String charsetName) {
         this(reader, charsetName, IOUtils.DEFAULT_BUFFER_SIZE);
     }
@@ -219,14 +276,15 @@ public class ReaderInputStream extends InputStream {
      * Constructs a new {@link ReaderInputStream}.
      *
      * <p>
-     * The encoder created for the specified charset will use {@link CodingErrorAction#REPLACE} for malformed input
-     * and unmappable characters.
+     * The encoder created for the specified charset will use {@link CodingErrorAction#REPLACE} for malformed input and unmappable characters.
      * </p>
      *
-     * @param reader the target {@link Reader}
+     * @param reader      the target {@link Reader}
      * @param charsetName the name of the charset encoding, null maps to the default Charset.
-     * @param bufferSize the size of the input buffer in number of characters
+     * @param bufferSize  the size of the input buffer in number of characters
+     * @deprecated Use {@link ReaderInputStream#builder()} instead
      */
+    @Deprecated
     public ReaderInputStream(final Reader reader, final String charsetName, final int bufferSize) {
         this(reader, Charsets.toCharset(charsetName), bufferSize);
     }
@@ -316,8 +374,8 @@ public class ReaderInputStream extends InputStream {
      * Reads the specified number of bytes into an array.
      *
      * @param array the byte array to read into
-     * @param off the offset to start reading bytes into
-     * @param len the number of bytes to read
+     * @param off   the offset to start reading bytes into
+     * @param len   the number of bytes to read
      * @return the number of bytes read or {@code -1} if the end of the stream has been reached
      * @throws IOException if an I/O error occurs.
      */
diff --git a/src/main/java/org/apache/commons/io/input/ReversedLinesFileReader.java b/src/main/java/org/apache/commons/io/input/ReversedLinesFileReader.java
index e6a4fce2..c470486c 100644
--- a/src/main/java/org/apache/commons/io/input/ReversedLinesFileReader.java
+++ b/src/main/java/org/apache/commons/io/input/ReversedLinesFileReader.java
@@ -34,8 +34,9 @@ import java.util.Collections;
 import java.util.List;
 
 import org.apache.commons.io.Charsets;
-import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.FileSystem;
 import org.apache.commons.io.StandardLineSeparator;
+import org.apache.commons.io.build.AbstractStreamBuilder;
 
 /**
  * Reads lines in a file reversely (similar to a BufferedReader, but starting at
@@ -45,6 +46,35 @@ import org.apache.commons.io.StandardLineSeparator;
  */
 public class ReversedLinesFileReader implements Closeable {
 
+    /**
+     * Builds a new {@link ReaderInputStream} instance.
+     * <p>
+     * For example:
+     * </p>
+     * <pre>{@code
+     * ReversedLinesFileReader r = ReversedLinesFileReader.builder()
+     *   .setPath(path)
+     *   .setBufferSize(4096)
+     *   .setCharset(StandardCharsets.UTF_8)
+     *   .get()}
+     * </pre>
+     * <p>
+     * @since 2.12.0
+     */
+    public static class Builder extends AbstractStreamBuilder<ReversedLinesFileReader, Builder> {
+
+        public Builder() {
+            setBufferSizeDefault(DEFAULT_BLOCK_SIZE);
+            setBufferSize(DEFAULT_BLOCK_SIZE);
+        }
+
+        @Override
+        public ReversedLinesFileReader get() throws IOException {
+            return new ReversedLinesFileReader(getOrigin().getPath(), getBufferSize(), getCharset());
+        }
+
+    }
+
     private class FilePart {
         private final long no;
 
@@ -203,7 +233,18 @@ public class ReversedLinesFileReader implements Closeable {
     }
 
     private static final String EMPTY_STRING = "";
-    private static final int DEFAULT_BLOCK_SIZE = IOUtils.DEFAULT_BUFFER_SIZE;
+
+    private static final int DEFAULT_BLOCK_SIZE = FileSystem.getCurrent().getBlockSize();
+
+    /**
+     * Constructs a new {@link Builder}.
+     *
+     * @return a new {@link Builder}.
+     * @since 2.12.0
+     */
+    public static Builder builder() {
+        return new Builder();
+    }
 
     private final int blockSize;
     private final Charset charset;
@@ -222,7 +263,7 @@ public class ReversedLinesFileReader implements Closeable {
      *
      * @param file the file to be read
      * @throws IOException if an I/O error occurs.
-     * @deprecated 2.5 use {@link #ReversedLinesFileReader(File, Charset)} instead
+     * @deprecated Use {@link ReaderInputStream#builder()} instead
      */
     @Deprecated
     public ReversedLinesFileReader(final File file) throws IOException {
@@ -237,7 +278,9 @@ public class ReversedLinesFileReader implements Closeable {
      * @param charset the charset to use, null uses the default Charset.
      * @throws IOException if an I/O error occurs.
      * @since 2.5
+     * @deprecated Use {@link ReaderInputStream#builder()} instead
      */
+    @Deprecated
     public ReversedLinesFileReader(final File file, final Charset charset) throws IOException {
         this(file.toPath(), charset);
     }
@@ -252,7 +295,9 @@ public class ReversedLinesFileReader implements Closeable {
      * @param charset  the encoding of the file, null uses the default Charset.
      * @throws IOException if an I/O error occurs.
      * @since 2.3
+     * @deprecated Use {@link ReaderInputStream#builder()} instead
      */
+    @Deprecated
     public ReversedLinesFileReader(final File file, final int blockSize, final Charset charset) throws IOException {
         this(file.toPath(), blockSize, charset);
     }
@@ -271,7 +316,9 @@ public class ReversedLinesFileReader implements Closeable {
      *                                                      in version 2.2 if the
      *                                                      encoding is not
      *                                                      supported.
+     * @deprecated Use {@link ReaderInputStream#builder()} instead
      */
+    @Deprecated
     public ReversedLinesFileReader(final File file, final int blockSize, final String charsetName) throws IOException {
         this(file.toPath(), blockSize, charsetName);
     }
@@ -284,7 +331,9 @@ public class ReversedLinesFileReader implements Closeable {
      * @param charset the charset to use, null uses the default Charset.
      * @throws IOException if an I/O error occurs.
      * @since 2.7
+     * @deprecated Use {@link ReaderInputStream#builder()} instead
      */
+    @Deprecated
     public ReversedLinesFileReader(final Path file, final Charset charset) throws IOException {
         this(file, DEFAULT_BLOCK_SIZE, charset);
     }
@@ -299,7 +348,9 @@ public class ReversedLinesFileReader implements Closeable {
      * @param charset  the encoding of the file, null uses the default Charset.
      * @throws IOException if an I/O error occurs.
      * @since 2.7
+     * @deprecated Use {@link ReaderInputStream#builder()} instead
      */
+    @Deprecated
     public ReversedLinesFileReader(final Path file, final int blockSize, final Charset charset) throws IOException {
         this.blockSize = blockSize;
         this.charset = Charsets.toCharset(charset);
@@ -307,14 +358,9 @@ public class ReversedLinesFileReader implements Closeable {
         // --- check & prepare encoding ---
         final CharsetEncoder charsetEncoder = this.charset.newEncoder();
         final float maxBytesPerChar = charsetEncoder.maxBytesPerChar();
-        if (maxBytesPerChar == 1f) {
+        if (maxBytesPerChar == 1f || this.charset == StandardCharsets.UTF_8) {
             // all one byte encodings are no problem
             byteDecrement = 1;
-        } else if (this.charset == StandardCharsets.UTF_8) {
-            // UTF-8 works fine out of the box, for multibyte sequences a second UTF-8 byte
-            // can never be a newline byte
-            // http://en.wikipedia.org/wiki/UTF-8
-            byteDecrement = 1;
         } else if (this.charset == Charset.forName("Shift_JIS") || // Same as for UTF-8
         // http://www.herongyang.com/Unicode/JIS-Shift-JIS-Encoding.html
                 this.charset == Charset.forName("windows-31j") || // Windows code page 932 (Japanese)
@@ -376,7 +422,9 @@ public class ReversedLinesFileReader implements Closeable {
      *                                                      encoding is not
      *                                                      supported.
      * @since 2.7
+     * @deprecated Use {@link ReaderInputStream#builder()} instead
      */
+    @Deprecated
     public ReversedLinesFileReader(final Path file, final int blockSize, final String charsetName) throws IOException {
         this(file, blockSize, Charsets.toCharset(charsetName));
     }
diff --git a/src/main/java/org/apache/commons/io/input/Tailer.java b/src/main/java/org/apache/commons/io/input/Tailer.java
index 7212b692..d29cb2bc 100644
--- a/src/main/java/org/apache/commons/io/input/Tailer.java
+++ b/src/main/java/org/apache/commons/io/input/Tailer.java
@@ -37,6 +37,8 @@ import java.util.Objects;
 
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.io.ThreadUtils;
+import org.apache.commons.io.build.AbstractOrigin;
+import org.apache.commons.io.build.AbstractStreamBuilder;
 import org.apache.commons.io.file.PathUtils;
 import org.apache.commons.io.file.attribute.FileTimes;
 
@@ -64,17 +66,10 @@ import org.apache.commons.io.file.attribute.FileTimes;
  * <h2>2. Using a Tailer</h2>
  *
  * <p>
- * You can create and use a Tailer in one of four ways:
+ * You can create and use a Tailer in one of three ways:
  * </p>
  * <ul>
  * <li>Using a {@link Builder}</li>
- * <li>Using one of the static helper methods:
- * <ul>
- * <li>{@link Tailer#create(File, TailerListener)}</li>
- * <li>{@link Tailer#create(File, TailerListener, long)}</li>
- * <li>{@link Tailer#create(File, TailerListener, long, boolean)}</li>
- * </ul>
- * </li>
  * <li>Using an {@link java.util.concurrent.Executor}</li>
  * <li>Using a {@link Thread}</li>
  * </ul>
@@ -87,17 +82,14 @@ import org.apache.commons.io.file.attribute.FileTimes;
  *
  * <pre>
  * TailerListener listener = new MyTailerListener();
- * Tailer tailer = new Tailer.Builder(file, listener).withDelayDuration(delay).build();
- * </pre>
- *
- * <h3>2.2 Using the static helper method</h3>
- *
- * <pre>
- * TailerListener listener = new MyTailerListener();
- * Tailer tailer = Tailer.create(file, listener, delay);
+ * Tailer tailer = Tailer.builder()
+ *   .setFile(file)
+ *   .setTailerListener(listener)
+ *   .setDelayDuration(delay)
+ *   .get();
  * </pre>
  *
- * <h3>2.3 Using an Executor</h3>
+ * <h3>2.2 Using an Executor</h3>
  *
  * <pre>
  * TailerListener listener = new MyTailerListener();
@@ -114,7 +106,7 @@ import org.apache.commons.io.file.attribute.FileTimes;
  * </pre>
  *
  *
- * <h3>2.4 Using a Thread</h3>
+ * <h3>2.3 Using a Thread</h3>
  *
  * <pre>
  * TailerListener listener = new MyTailerListener();
@@ -160,58 +152,40 @@ public class Tailer implements Runnable, AutoCloseable {
 
     /**
      * Builds a {@link Tailer} with default values.
-     *
+     * <p>
+     * For example:
+     * </p>
+     * <pre>{@code
+     * Tailer t = Tailer.builder()
+     *   .setPath(path)
+     *   .setCharset(StandardCharsets.UTF_8)
+     *   .setDelayDuration(Duration.ofSeconds(1))
+     *   .setReOpen(false)
+     *   .setStartThread(true)
+     *   .setTailable(tailable)
+     *   .setTailerListener(tailerListener)
+     *   .setTailFromEnd(false)
+     *   .get()}
+     * </pre>
+     * <p>
      * @since 2.12.0
      */
-    public static class Builder {
+    public static class Builder extends AbstractStreamBuilder<Tailer, Builder> {
 
-        private final Tailable tailable;
-        private final TailerListener tailerListener;
-        private Charset charset = DEFAULT_CHARSET;
-        private int bufferSize = IOUtils.DEFAULT_BUFFER_SIZE;
+        private Tailable tailable;
+        private TailerListener tailerListener;
         private Duration delayDuration = Duration.ofMillis(DEFAULT_DELAY_MILLIS);
         private boolean end;
         private boolean reOpen;
         private boolean startThread = true;
-
-        /**
-         * Creates a builder.
-         *
-         * @param file the file to follow.
-         * @param listener the TailerListener to use.
-         */
-        public Builder(final File file, final TailerListener listener) {
-            this(file.toPath(), listener);
-        }
-
-        /**
-         * Creates a builder.
-         *
-         * @param file the file to follow.
-         * @param listener the TailerListener to use.
-         */
-        public Builder(final Path file, final TailerListener listener) {
-            this(new TailablePath(file), listener);
-        }
-
-        /**
-         * Creates a builder.
-         *
-         * @param tailable the tailable to follow.
-         * @param tailerListener the TailerListener to use.
-         */
-        public Builder(final Tailable tailable, final TailerListener tailerListener) {
-            this.tailable = Objects.requireNonNull(tailable, "tailable");
-            this.tailerListener = Objects.requireNonNull(tailerListener, "tailerListener");
-        }
-
         /**
          * Builds and starts a new configured instance.
          *
          * @return a new configured instance.
          */
-        public Tailer build() {
-            final Tailer tailer = new Tailer(tailable, charset, tailerListener, delayDuration, end, reOpen, bufferSize);
+        @Override
+        public Tailer get() {
+            final Tailer tailer = new Tailer(tailable, getCharset(), tailerListener, delayDuration, end, reOpen, getBufferSize());
             if (startThread) {
                 final Thread thread = new Thread(tailer);
                 thread.setDaemon(true);
@@ -221,57 +195,63 @@ public class Tailer implements Runnable, AutoCloseable {
         }
 
         /**
-         * Sets the buffer size.
+         * Sets the delay duration.
          *
-         * @param bufferSize Buffer size.
-         * @return Builder with specific buffer size.
+         * @param delayDuration the delay between checks of the file for new content.
+         * @return this
          */
-        public Builder withBufferSize(final int bufferSize) {
-            this.bufferSize = bufferSize;
+        public Builder setDelayDuration(final Duration delayDuration) {
+            this.delayDuration = Objects.requireNonNull(delayDuration, "delayDuration");
             return this;
         }
 
+        @Override
+        protected Builder setOrigin(final AbstractOrigin<?, ?> origin) {
+            setTailable(new TailablePath(origin.getPath()));
+            return super.setOrigin(origin);
+        }
+
         /**
-         * Sets the Charset.
+         * Sets the re-open behavior.
          *
-         * @param charset the Charset to be used for reading the file.
-         * @return Builder with specific Charset.
+         * @param reOpen whether to close/reopen the file between chunks
+         * @return this
          */
-        public Builder withCharset(final Charset charset) {
-            this.charset = Objects.requireNonNull(charset, "charset");
+        public Builder setReOpen(final boolean reOpen) {
+            this.reOpen = reOpen;
             return this;
         }
 
         /**
-         * Sets the delay duration.
+         * Sets the daemon thread startup behavior.
          *
-         * @param delayDuration the delay between checks of the file for new content.
-         * @return Builder with specific delay duration.
+         * @param startThread whether to create a daemon thread automatically.
+         * @return this
          */
-        public Builder withDelayDuration(final Duration delayDuration) {
-            this.delayDuration = Objects.requireNonNull(delayDuration, "delayDuration");
+        public Builder setStartThread(final boolean startThread) {
+            this.startThread = startThread;
             return this;
         }
 
         /**
-         * Sets the re-open behavior.
+         * Sets the tailable.
          *
-         * @param reOpen whether to close/reopen the file between chunks
-         * @return Builder with specific re-open behavior
+         * @param tailable the tailable.
+         * @return this.
          */
-        public Builder withReOpen(final boolean reOpen) {
-            this.reOpen = reOpen;
+        public Builder setTailable(final Tailable tailable) {
+            this.tailable = Objects.requireNonNull(tailable, "tailable");
             return this;
         }
 
         /**
-         * Sets the daemon thread startup behavior.
+         * Sets the listener.
          *
-         * @param startThread whether to create a daemon thread automatically.
-         * @return Builder with specific daemon thread startup behavior.
+         * @param tailerListener the listener.
+         * @return this
          */
-        public Builder withStartThread(final boolean startThread) {
-            this.startThread = startThread;
+        public Builder setTailerListener(final TailerListener tailerListener) {
+            this.tailerListener = Objects.requireNonNull(tailerListener, "tailerListener");
             return this;
         }
 
@@ -279,9 +259,9 @@ public class Tailer implements Runnable, AutoCloseable {
          * Sets the tail start behavior.
          *
          * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
-         * @return Builder with specific tail start behavior.
+         * @return this
          */
-        public Builder withTailFromEnd(final boolean end) {
+        public Builder setTailFromEnd(final boolean end) {
             this.end = end;
             return this;
         }
@@ -454,6 +434,16 @@ public class Tailer implements Runnable, AutoCloseable {
     // The default charset used for reading files
     private static final Charset DEFAULT_CHARSET = Charset.defaultCharset();
 
+    /**
+     * Constructs a new {@link Builder}.
+     *
+     * @return Creates a new {@link Builder}.
+     * @since 2.12.0
+     */
+    public static Builder builder() {
+        return new Builder();
+    }
+
     /**
      * Creates and starts a Tailer for the given file.
      *
@@ -465,19 +455,21 @@ public class Tailer implements Runnable, AutoCloseable {
      * @param reOpen whether to close/reopen the file between chunks.
      * @param bufferSize buffer size.
      * @return The new tailer.
-     * @deprecated Use {@link Builder}.
+     * @deprecated Use {@link #builder()}.
      */
     @Deprecated
     public static Tailer create(final File file, final Charset charset, final TailerListener listener, final long delayMillis, final boolean end,
         final boolean reOpen, final int bufferSize) {
         //@formatter:off
-        return new Builder(file, listener)
-                .withCharset(charset)
-                .withDelayDuration(Duration.ofMillis(delayMillis))
-                .withTailFromEnd(end)
-                .withReOpen(reOpen)
-                .withBufferSize(bufferSize)
-                .build();
+        return builder()
+                .setFile(file)
+                .setTailerListener(listener)
+                .setCharset(charset)
+                .setDelayDuration(Duration.ofMillis(delayMillis))
+                .setTailFromEnd(end)
+                .setReOpen(reOpen)
+                .setBufferSize(bufferSize)
+                .get();
         //@formatter:on
     }
 
@@ -487,11 +479,16 @@ public class Tailer implements Runnable, AutoCloseable {
      * @param file the file to follow.
      * @param listener the TailerListener to use.
      * @return The new tailer.
-     * @deprecated Use {@link Builder}.
+     * @deprecated Use {@link #builder()}.
      */
     @Deprecated
     public static Tailer create(final File file, final TailerListener listener) {
-        return new Builder(file, listener).build();
+        //@formatter:off
+        return builder()
+                .setFile(file)
+                .setTailerListener(listener)
+                .get();
+        //@formatter:on
     }
 
     /**
@@ -501,14 +498,16 @@ public class Tailer implements Runnable, AutoCloseable {
      * @param listener the TailerListener to use.
      * @param delayMillis the delay between checks of the file for new content in milliseconds.
      * @return The new tailer.
-     * @deprecated Use {@link Builder}.
+     * @deprecated Use {@link #builder()}.
      */
     @Deprecated
     public static Tailer create(final File file, final TailerListener listener, final long delayMillis) {
         //@formatter:off
-        return new Builder(file, listener)
-                .withDelayDuration(Duration.ofMillis(delayMillis))
-                .build();
+        return builder()
+                .setFile(file)
+                .setTailerListener(listener)
+                .setDelayDuration(Duration.ofMillis(delayMillis))
+                .get();
         //@formatter:on
     }
 
@@ -520,15 +519,17 @@ public class Tailer implements Runnable, AutoCloseable {
      * @param delayMillis the delay between checks of the file for new content in milliseconds.
      * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
      * @return The new tailer.
-     * @deprecated Use {@link Builder}.
+     * @deprecated Use {@link #builder()}.
      */
     @Deprecated
     public static Tailer create(final File file, final TailerListener listener, final long delayMillis, final boolean end) {
         //@formatter:off
-        return new Builder(file, listener)
-                .withDelayDuration(Duration.ofMillis(delayMillis))
-                .withTailFromEnd(end)
-                .build();
+        return builder()
+                .setFile(file)
+                .setTailerListener(listener)
+                .setDelayDuration(Duration.ofMillis(delayMillis))
+                .setTailFromEnd(end)
+                .get();
         //@formatter:on
     }
 
@@ -541,16 +542,18 @@ public class Tailer implements Runnable, AutoCloseable {
      * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
      * @param reOpen whether to close/reopen the file between chunks.
      * @return The new tailer.
-     * @deprecated Use {@link Builder}.
+     * @deprecated Use {@link #builder()}.
      */
     @Deprecated
     public static Tailer create(final File file, final TailerListener listener, final long delayMillis, final boolean end, final boolean reOpen) {
         //@formatter:off
-        return new Builder(file, listener)
-                .withDelayDuration(Duration.ofMillis(delayMillis))
-                .withTailFromEnd(end)
-                .withReOpen(reOpen)
-                .build();
+        return builder()
+                .setFile(file)
+                .setTailerListener(listener)
+                .setDelayDuration(Duration.ofMillis(delayMillis))
+                .setTailFromEnd(end)
+                .setReOpen(reOpen)
+                .get();
         //@formatter:on
     }
 
@@ -564,18 +567,20 @@ public class Tailer implements Runnable, AutoCloseable {
      * @param reOpen whether to close/reopen the file between chunks.
      * @param bufferSize buffer size.
      * @return The new tailer.
-     * @deprecated Use {@link Builder}.
+     * @deprecated Use {@link #builder()}.
      */
     @Deprecated
     public static Tailer create(final File file, final TailerListener listener, final long delayMillis, final boolean end, final boolean reOpen,
         final int bufferSize) {
         //@formatter:off
-        return new Builder(file, listener)
-                .withDelayDuration(Duration.ofMillis(delayMillis))
-                .withTailFromEnd(end)
-                .withReOpen(reOpen)
-                .withBufferSize(bufferSize)
-                .build();
+        return builder()
+                .setFile(file)
+                .setTailerListener(listener)
+                .setDelayDuration(Duration.ofMillis(delayMillis))
+                .setTailFromEnd(end)
+                .setReOpen(reOpen)
+                .setBufferSize(bufferSize)
+                .get();
         //@formatter:on
     }
 
@@ -588,16 +593,18 @@ public class Tailer implements Runnable, AutoCloseable {
      * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
      * @param bufferSize buffer size.
      * @return The new tailer.
-     * @deprecated Use {@link Builder}.
+     * @deprecated Use {@link #builder()}.
      */
     @Deprecated
     public static Tailer create(final File file, final TailerListener listener, final long delayMillis, final boolean end, final int bufferSize) {
         //@formatter:off
-        return new Builder(file, listener)
-                .withDelayDuration(Duration.ofMillis(delayMillis))
-                .withTailFromEnd(end)
-                .withBufferSize(bufferSize)
-                .build();
+        return builder()
+                .setFile(file)
+                .setTailerListener(listener)
+                .setDelayDuration(Duration.ofMillis(delayMillis))
+                .setTailFromEnd(end)
+                .setBufferSize(bufferSize)
+                .get();
         //@formatter:on
     }
 
@@ -651,7 +658,7 @@ public class Tailer implements Runnable, AutoCloseable {
      * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
      * @param reOpen if true, close and reopen the file between reading chunks
      * @param bufSize Buffer size
-     * @deprecated Use {@link Builder}.
+     * @deprecated Use {@link #builder()}.
      */
     @Deprecated
     public Tailer(final File file, final Charset charset, final TailerListener listener, final long delayMillis, final boolean end, final boolean reOpen,
@@ -664,7 +671,7 @@ public class Tailer implements Runnable, AutoCloseable {
      *
      * @param file The file to follow.
      * @param listener the TailerListener to use.
-     * @deprecated Use {@link Builder}.
+     * @deprecated Use {@link #builder()}.
      */
     @Deprecated
     public Tailer(final File file, final TailerListener listener) {
@@ -677,7 +684,7 @@ public class Tailer implements Runnable, AutoCloseable {
      * @param file the file to follow.
      * @param listener the TailerListener to use.
      * @param delayMillis the delay between checks of the file for new content in milliseconds.
-     * @deprecated Use {@link Builder}.
+     * @deprecated Use {@link #builder()}.
      */
     @Deprecated
     public Tailer(final File file, final TailerListener listener, final long delayMillis) {
@@ -691,7 +698,7 @@ public class Tailer implements Runnable, AutoCloseable {
      * @param listener the TailerListener to use.
      * @param delayMillis the delay between checks of the file for new content in milliseconds.
      * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
-     * @deprecated Use {@link Builder}.
+     * @deprecated Use {@link #builder()}.
      */
     @Deprecated
     public Tailer(final File file, final TailerListener listener, final long delayMillis, final boolean end) {
@@ -706,7 +713,7 @@ public class Tailer implements Runnable, AutoCloseable {
      * @param delayMillis the delay between checks of the file for new content in milliseconds.
      * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
      * @param reOpen if true, close and reopen the file between reading chunks
-     * @deprecated Use {@link Builder}.
+     * @deprecated Use {@link #builder()}.
      */
     @Deprecated
     public Tailer(final File file, final TailerListener listener, final long delayMillis, final boolean end, final boolean reOpen) {
@@ -722,7 +729,7 @@ public class Tailer implements Runnable, AutoCloseable {
      * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
      * @param reOpen if true, close and reopen the file between reading chunks
      * @param bufferSize Buffer size
-     * @deprecated Use {@link Builder}.
+     * @deprecated Use {@link #builder()}.
      */
     @Deprecated
     public Tailer(final File file, final TailerListener listener, final long delayMillis, final boolean end, final boolean reOpen, final int bufferSize) {
@@ -737,7 +744,7 @@ public class Tailer implements Runnable, AutoCloseable {
      * @param delayMillis the delay between checks of the file for new content in milliseconds.
      * @param end Set to true to tail from the end of the file, false to tail from the beginning of the file.
      * @param bufferSize Buffer size
-     * @deprecated Use {@link Builder}.
+     * @deprecated Use {@link #builder()}.
      */
     @Deprecated
     public Tailer(final File file, final TailerListener listener, final long delayMillis, final boolean end, final int bufferSize) {
@@ -757,13 +764,13 @@ public class Tailer implements Runnable, AutoCloseable {
      */
     private Tailer(final Tailable tailable, final Charset charset, final TailerListener listener, final Duration delayDuration, final boolean end,
         final boolean reOpen, final int bufferSize) {
-        this.tailable = tailable;
+        this.tailable = Objects.requireNonNull(tailable, "tailable");
+        this.listener = Objects.requireNonNull(listener, "listener");
         this.delayDuration = delayDuration;
         this.tailAtEnd = end;
         this.inbuf = IOUtils.byteArray(bufferSize);
 
         // Save and prepare the listener
-        this.listener = listener;
         listener.init(this);
         this.reOpen = reOpen;
         this.charset = charset;
diff --git a/src/main/java/org/apache/commons/io/input/XmlStreamReader.java b/src/main/java/org/apache/commons/io/input/XmlStreamReader.java
index 7ca6c19b..65c117a1 100644
--- a/src/main/java/org/apache/commons/io/input/XmlStreamReader.java
+++ b/src/main/java/org/apache/commons/io/input/XmlStreamReader.java
@@ -38,35 +38,29 @@ import java.util.regex.Pattern;
 
 import org.apache.commons.io.ByteOrderMark;
 import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.build.AbstractStreamBuilder;
 import org.apache.commons.io.function.IOConsumer;
+import org.apache.commons.io.output.XmlStreamWriter;
 
 /**
- * Character stream that handles all the necessary Voodoo to figure out the
- * charset encoding of the XML document within the stream.
+ * Character stream that handles all the necessary Voodoo to figure out the charset encoding of the XML document within the stream.
  * <p>
- * IMPORTANT: This class is not related in any way to the org.xml.sax.XMLReader.
- * This one IS a character stream.
+ * IMPORTANT: This class is not related in any way to the org.xml.sax.XMLReader. This one IS a character stream.
  * </p>
  * <p>
- * All this has to be done without consuming characters from the stream, if not
- * the XML parser will not recognized the document as a valid XML. This is not
- * 100% true, but it's close enough (UTF-8 BOM is not handled by all parsers
- * right now, XmlStreamReader handles it and things work in all parsers).
+ * All this has to be done without consuming characters from the stream, if not the XML parser will not recognized the document as a valid XML. This is not 100%
+ * true, but it's close enough (UTF-8 BOM is not handled by all parsers right now, XmlStreamReader handles it and things work in all parsers).
  * </p>
  * <p>
- * The XmlStreamReader class handles the charset encoding of XML documents in
- * Files, raw streams and HTTP streams by offering a wide set of constructors.
+ * The XmlStreamReader class handles the charset encoding of XML documents in Files, raw streams and HTTP streams by offering a wide set of constructors.
  * </p>
  * <p>
- * By default the charset encoding detection is lenient, the constructor with
- * the lenient flag can be used for a script (following HTTP MIME and XML
- * specifications). All this is nicely explained by Mark Pilgrim in his blog, <a
- * href="http://diveintomark.org/archives/2004/02/13/xml-media-types">
- * Determining the character encoding of a feed</a>.
+ * By default the charset encoding detection is lenient, the constructor with the lenient flag can be used for a script (following HTTP MIME and XML
+ * specifications). All this is nicely explained by Mark Pilgrim in his blog, <a href="http://diveintomark.org/archives/2004/02/13/xml-media-types"> Determining
+ * the character encoding of a feed</a>.
  * </p>
  * <p>
- * Originally developed for <a href="http://rome.dev.java.net">ROME</a> under
- * Apache License 2.0.
+ * Originally developed for <a href="http://rome.dev.java.net">ROME</a> under Apache License 2.0.
  * </p>
  *
  * @see org.apache.commons.io.output.XmlStreamWriter
@@ -74,6 +68,83 @@ import org.apache.commons.io.function.IOConsumer;
  */
 public class XmlStreamReader extends Reader {
 
+    /**
+     * Builds a new {@link XmlStreamWriter} instance.
+     *
+     * Constructs a Reader using an InputStream and the associated content-type header. This constructor is lenient regarding the encoding detection.
+     * <p>
+     * First it checks if the stream has BOM. If there is not BOM checks the content-type encoding. If there is not content-type encoding checks the XML prolog
+     * encoding. If there is not XML prolog encoding uses the default encoding mandated by the content-type MIME type.
+     * </p>
+     * <p>
+     * If lenient detection is indicated and the detection above fails as per specifications it then attempts the following:
+     * </p>
+     * <p>
+     * If the content type was 'text/html' it replaces it with 'text/xml' and tries the detection again.
+     * </p>
+     * <p>
+     * Else if the XML prolog had a charset encoding that encoding is used.
+     * </p>
+     * <p>
+     * Else if the content type had a charset encoding that encoding is used.
+     * </p>
+     * <p>
+     * Else 'UTF-8' is used.
+     * </p>
+     * <p>
+     * If lenient detection is indicated an XmlStreamReaderException is never thrown.
+     * </p>
+     * <p>
+     * For example:
+     * </p>
+     * <pre>{@code
+     * XmlStreamReader r = XmlStreamReader.builder()
+     *   .setPath(path)
+     *   .setCharset(StandardCharsets.UTF_8)
+     *   .get()}
+     * </pre>
+     * <p>
+     *
+     * @since 2.12.02
+     */
+    public static class Builder extends AbstractStreamBuilder<XmlStreamReader, Builder> {
+
+        private boolean lenient = true;
+        private String httpContentType;
+
+        public Builder setHttpContentType(final String httpContentType) {
+            this.httpContentType = httpContentType;
+            return this;
+        }
+
+        @SuppressWarnings("resource")
+        @Override
+        public XmlStreamReader get() throws IOException {
+            final String defaultEncoding = getCharset().equals(getCharsetDefault()) ? null : getCharset().name();
+            // @formatter:off
+            return httpContentType == null
+                    ? new XmlStreamReader(getOrigin().getInputStream(), lenient, defaultEncoding)
+                    : new XmlStreamReader(getOrigin().getInputStream(), httpContentType, lenient, defaultEncoding);
+            // @formatter:on
+        }
+
+        public Builder setLenient(final boolean lenient) {
+            this.lenient = lenient;
+            return this;
+        }
+
+    }
+
+    /**
+     * Constructs a new {@link Builder}.
+     *
+     * @return a new {@link Builder}.
+     * @since 2.12.0
+     */
+    public static Builder builder() {
+        return new Builder();
+    }
+
     private static final String UTF_8 = StandardCharsets.UTF_8.name();
 
     private static final String US_ASCII = StandardCharsets.US_ASCII.name();
@@ -92,54 +163,35 @@ public class XmlStreamReader extends Reader {
 
     private static final String EBCDIC = "CP1047";
 
-    private static final ByteOrderMark[] BOMS = {
-        ByteOrderMark.UTF_8,
-        ByteOrderMark.UTF_16BE,
-        ByteOrderMark.UTF_16LE,
-        ByteOrderMark.UTF_32BE,
-        ByteOrderMark.UTF_32LE
-    };
+    private static final ByteOrderMark[] BOMS = { ByteOrderMark.UTF_8, ByteOrderMark.UTF_16BE, ByteOrderMark.UTF_16LE, ByteOrderMark.UTF_32BE,
+            ByteOrderMark.UTF_32LE };
 
     /** UTF_16LE and UTF_32LE have the same two starting BOM bytes. */
-    private static final ByteOrderMark[] XML_GUESS_BYTES = {
-        new ByteOrderMark(UTF_8,    0x3C, 0x3F, 0x78, 0x6D),
-        new ByteOrderMark(UTF_16BE, 0x00, 0x3C, 0x00, 0x3F),
-        new ByteOrderMark(UTF_16LE, 0x3C, 0x00, 0x3F, 0x00),
-        new ByteOrderMark(UTF_32BE, 0x00, 0x00, 0x00, 0x3C,
-                0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x6D),
-        new ByteOrderMark(UTF_32LE, 0x3C, 0x00, 0x00, 0x00,
-                0x3F, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x6D, 0x00, 0x00, 0x00),
-        new ByteOrderMark(EBCDIC,   0x4C, 0x6F, 0xA7, 0x94)
-    };
-
-    private static final Pattern CHARSET_PATTERN = Pattern
-            .compile("charset=[\"']?([.[^; \"']]*)[\"']?");
+    private static final ByteOrderMark[] XML_GUESS_BYTES = { new ByteOrderMark(UTF_8, 0x3C, 0x3F, 0x78, 0x6D),
+            new ByteOrderMark(UTF_16BE, 0x00, 0x3C, 0x00, 0x3F), new ByteOrderMark(UTF_16LE, 0x3C, 0x00, 0x3F, 0x00),
+            new ByteOrderMark(UTF_32BE, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x6D),
+            new ByteOrderMark(UTF_32LE, 0x3C, 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x6D, 0x00, 0x00, 0x00),
+            new ByteOrderMark(EBCDIC, 0x4C, 0x6F, 0xA7, 0x94) };
+
+    private static final Pattern CHARSET_PATTERN = Pattern.compile("charset=[\"']?([.[^; \"']]*)[\"']?");
 
     /**
      * Pattern capturing the encoding of the "xml" processing instruction.
      */
-    public static final Pattern ENCODING_PATTERN = Pattern.compile(
-            "<\\?xml.*encoding[\\s]*=[\\s]*((?:\".[^\"]*\")|(?:'.[^']*'))",
-            Pattern.MULTILINE);
+    public static final Pattern ENCODING_PATTERN = Pattern.compile("<\\?xml.*encoding[\\s]*=[\\s]*((?:\".[^\"]*\")|(?:'.[^']*'))", Pattern.MULTILINE);
 
-    private static final String RAW_EX_1 =
-        "Invalid encoding, BOM [{0}] XML guess [{1}] XML prolog [{2}] encoding mismatch";
+    private static final String RAW_EX_1 = "Invalid encoding, BOM [{0}] XML guess [{1}] XML prolog [{2}] encoding mismatch";
 
-    private static final String RAW_EX_2 =
-        "Invalid encoding, BOM [{0}] XML guess [{1}] XML prolog [{2}] unknown BOM";
+    private static final String RAW_EX_2 = "Invalid encoding, BOM [{0}] XML guess [{1}] XML prolog [{2}] unknown BOM";
 
-    private static final String HTTP_EX_1 =
-        "Invalid encoding, CT-MIME [{0}] CT-Enc [{1}] BOM [{2}] XML guess [{3}] XML prolog [{4}], BOM must be NULL";
+    private static final String HTTP_EX_1 = "Invalid encoding, CT-MIME [{0}] CT-Enc [{1}] BOM [{2}] XML guess [{3}] XML prolog [{4}], BOM must be NULL";
 
-    private static final String HTTP_EX_2 =
-        "Invalid encoding, CT-MIME [{0}] CT-Enc [{1}] BOM [{2}] XML guess [{3}] XML prolog [{4}], encoding mismatch";
+    private static final String HTTP_EX_2 = "Invalid encoding, CT-MIME [{0}] CT-Enc [{1}] BOM [{2}] XML guess [{3}] XML prolog [{4}], encoding mismatch";
 
-    private static final String HTTP_EX_3 =
-        "Invalid encoding, CT-MIME [{0}] CT-Enc [{1}] BOM [{2}] XML guess [{3}] XML prolog [{4}], Invalid MIME";
+    private static final String HTTP_EX_3 = "Invalid encoding, CT-MIME [{0}] CT-Enc [{1}] BOM [{2}] XML guess [{3}] XML prolog [{4}], Invalid MIME";
 
     /**
-     * Gets the charset parameter value, NULL if not present, NULL if
-     * httpContentType is NULL.
+     * Gets the charset parameter value, NULL if not present, NULL if httpContentType is NULL.
      *
      * @param httpContentType the HTTP content type
      * @return The content type encoding (upcased)
@@ -182,12 +234,11 @@ public class XmlStreamReader extends Reader {
      * Gets the encoding declared in the <?xml encoding=...?>, NULL if none.
      *
      * @param inputStream InputStream to create the reader from.
-     * @param guessedEnc guessed encoding
+     * @param guessedEnc  guessed encoding
      * @return the encoding declared in the <?xml encoding=...?>
      * @throws IOException thrown if there is a problem reading the stream.
      */
-    private static String getXmlProlog(final InputStream inputStream, final String guessedEnc)
-            throws IOException {
+    private static String getXmlProlog(final InputStream inputStream, final String guessedEnc) throws IOException {
         String encoding = null;
         if (guessedEnc != null) {
             final byte[] bytes = IOUtils.byteArray();
@@ -208,9 +259,7 @@ public class XmlStreamReader extends Reader {
                 if (c == -1) {
                     throw new IOException("Unexpected end of XML stream");
                 }
-                throw new IOException(
-                        "XML prolog or ROOT element not found on first "
-                                + offset + " bytes");
+                throw new IOException("XML prolog or ROOT element not found on first " + offset + " bytes");
             }
             final int bytesRead = offset;
             if (bytesRead > 0) {
@@ -232,29 +281,21 @@ public class XmlStreamReader extends Reader {
      * Tests if the MIME type belongs to the APPLICATION XML family.
      *
      * @param mime The mime type
-     * @return true if the mime type belongs to the APPLICATION XML family,
-     * otherwise false
+     * @return true if the mime type belongs to the APPLICATION XML family, otherwise false
      */
     static boolean isAppXml(final String mime) {
-        return mime != null &&
-               (mime.equals("application/xml") ||
-                mime.equals("application/xml-dtd") ||
-                mime.equals("application/xml-external-parsed-entity") ||
-                mime.startsWith("application/") && mime.endsWith("+xml"));
+        return mime != null && (mime.equals("application/xml") || mime.equals("application/xml-dtd") || mime.equals("application/xml-external-parsed-entity")
+                || mime.startsWith("application/") && mime.endsWith("+xml"));
     }
 
     /**
      * Tests if the MIME type belongs to the TEXT XML family.
      *
      * @param mime The mime type
-     * @return true if the mime type belongs to the TEXT XML family,
-     * otherwise false
+     * @return true if the mime type belongs to the TEXT XML family, otherwise false
      */
     static boolean isTextXml(final String mime) {
-        return mime != null &&
-              (mime.equals("text/xml") ||
-               mime.equals("text/xml-external-parsed-entity") ||
-               mime.startsWith("text/") && mime.endsWith("+xml"));
+        return mime != null && (mime.equals("text/xml") || mime.equals("text/xml-external-parsed-entity") || mime.startsWith("text/") && mime.endsWith("+xml"));
     }
 
     private final Reader reader;
@@ -266,17 +307,17 @@ public class XmlStreamReader extends Reader {
     /**
      * Constructs a Reader for a File.
      * <p>
-     * It looks for the UTF-8 BOM first, if none sniffs the XML prolog charset,
-     * if this is also missing defaults to UTF-8.
+     * It looks for the UTF-8 BOM first, if none sniffs the XML prolog charset, if this is also missing defaults to UTF-8.
      * </p>
      * <p>
-     * It does a lenient charset encoding detection, check the constructor with
-     * the lenient parameter for details.
+     * It does a lenient charset encoding detection, check the constructor with the lenient parameter for details.
      * </p>
      *
      * @param file File to create a Reader from.
      * @throws IOException thrown if there is a problem reading the file.
+     * @deprecated Use {@link #builder()}
      */
+    @Deprecated
     public XmlStreamReader(final File file) throws IOException {
         this(Objects.requireNonNull(file, "file").toPath());
     }
@@ -287,13 +328,14 @@ public class XmlStreamReader extends Reader {
      * It follows the same logic used for files.
      * </p>
      * <p>
-     * It does a lenient charset encoding detection, check the constructor with
-     * the lenient parameter for details.
+     * It does a lenient charset encoding detection, check the constructor with the lenient parameter for details.
      * </p>
      *
      * @param inputStream InputStream to create a Reader from.
      * @throws IOException thrown if there is a problem reading the stream.
+     * @deprecated Use {@link #builder()}
      */
+    @Deprecated
     public XmlStreamReader(final InputStream inputStream) throws IOException {
         this(inputStream, true);
     }
@@ -304,12 +346,10 @@ public class XmlStreamReader extends Reader {
      * It follows the same logic used for files.
      * </p>
      * <p>
-     * If lenient detection is indicated and the detection above fails as per
-     * specifications it then attempts the following:
+     * If lenient detection is indicated and the detection above fails as per specifications it then attempts the following:
      * </p>
      * <p>
-     * If the content type was 'text/html' it replaces it with 'text/xml' and
-     * tries the detection again.
+     * If the content type was 'text/html' it replaces it with 'text/xml' and tries the detection again.
      * </p>
      * <p>
      * Else if the XML prolog had a charset encoding that encoding is used.
@@ -321,17 +361,16 @@ public class XmlStreamReader extends Reader {
      * Else 'UTF-8' is used.
      * </p>
      * <p>
-     * If lenient detection is indicated an XmlStreamReaderException is never
-     * thrown.
+     * If lenient detection is indicated an XmlStreamReaderException is never thrown.
      * </p>
      *
      * @param inputStream InputStream to create a Reader from.
-     * @param lenient indicates if the charset encoding detection should be
-     *        relaxed.
-     * @throws IOException thrown if there is a problem reading the stream.
-     * @throws XmlStreamReaderException thrown if the charset encoding could not
-     *         be determined according to the specs.
+     * @param lenient     indicates if the charset encoding detection should be relaxed.
+     * @throws IOException              thrown if there is a problem reading the stream.
+     * @throws XmlStreamReaderException thrown if the charset encoding could not be determined according to the specs.
+     * @deprecated Use {@link #builder()}
      */
+    @Deprecated
     public XmlStreamReader(final InputStream inputStream, final boolean lenient) throws IOException {
         this(inputStream, lenient, null);
     }
@@ -342,12 +381,10 @@ public class XmlStreamReader extends Reader {
      * It follows the same logic used for files.
      * </p>
      * <p>
-     * If lenient detection is indicated and the detection above fails as per
-     * specifications it then attempts the following:
+     * If lenient detection is indicated and the detection above fails as per specifications it then attempts the following:
      * </p>
      * <p>
-     * If the content type was 'text/html' it replaces it with 'text/xml' and
-     * tries the detection again.
+     * If the content type was 'text/html' it replaces it with 'text/xml' and tries the detection again.
      * </p>
      * <p>
      * Else if the XML prolog had a charset encoding that encoding is used.
@@ -359,21 +396,19 @@ public class XmlStreamReader extends Reader {
      * Else 'UTF-8' is used.
      * </p>
      * <p>
-     * If lenient detection is indicated an XmlStreamReaderException is never
-     * thrown.
+     * If lenient detection is indicated an XmlStreamReaderException is never thrown.
      * </p>
      *
-     * @param inputStream InputStream to create a Reader from.
-     * @param lenient indicates if the charset encoding detection should be
-     *        relaxed.
+     * @param inputStream     InputStream to create a Reader from.
+     * @param lenient         indicates if the charset encoding detection should be relaxed.
      * @param defaultEncoding The default encoding
-     * @throws IOException thrown if there is a problem reading the stream.
-     * @throws XmlStreamReaderException thrown if the charset encoding could not
-     *         be determined according to the specs.
+     * @throws IOException              thrown if there is a problem reading the stream.
+     * @throws XmlStreamReaderException thrown if the charset encoding could not be determined according to the specs.
+     * @deprecated Use {@link #builder()}
      */
+    @Deprecated
     @SuppressWarnings("resource") // InputStream is managed through a InputStreamReader in this instance.
-    public XmlStreamReader(final InputStream inputStream, final boolean lenient, final String defaultEncoding)
-            throws IOException {
+    public XmlStreamReader(final InputStream inputStream, final boolean lenient, final String defaultEncoding) throws IOException {
         Objects.requireNonNull(inputStream, "inputStream");
         this.defaultEncoding = defaultEncoding;
         final BOMInputStream bom = new BOMInputStream(new BufferedInputStream(inputStream, IOUtils.DEFAULT_BUFFER_SIZE), false, BOMS);
@@ -383,45 +418,36 @@ public class XmlStreamReader extends Reader {
     }
 
     /**
-     * Constructs a Reader using an InputStream and the associated content-type
-     * header.
+     * Constructs a Reader using an InputStream and the associated content-type header.
      * <p>
-     * First it checks if the stream has BOM. If there is not BOM checks the
-     * content-type encoding. If there is not content-type encoding checks the
-     * XML prolog encoding. If there is not XML prolog encoding uses the default
-     * encoding mandated by the content-type MIME type.
+     * First it checks if the stream has BOM. If there is not BOM checks the content-type encoding. If there is not content-type encoding checks the XML prolog
+     * encoding. If there is not XML prolog encoding uses the default encoding mandated by the content-type MIME type.
      * </p>
      * <p>
-     * It does a lenient charset encoding detection, check the constructor with
-     * the lenient parameter for details.
+     * It does a lenient charset encoding detection, check the constructor with the lenient parameter for details.
      * </p>
      *
-     * @param inputStream InputStream to create the reader from.
-     * @param httpContentType content-type header to use for the resolution of
-     *        the charset encoding.
+     * @param inputStream     InputStream to create the reader from.
+     * @param httpContentType content-type header to use for the resolution of the charset encoding.
      * @throws IOException thrown if there is a problem reading the file.
+     * @deprecated Use {@link #builder()}
      */
-    public XmlStreamReader(final InputStream inputStream, final String httpContentType)
-            throws IOException {
+    @Deprecated
+    public XmlStreamReader(final InputStream inputStream, final String httpContentType) throws IOException {
         this(inputStream, httpContentType, true);
     }
 
     /**
-     * Constructs a Reader using an InputStream and the associated content-type
-     * header. This constructor is lenient regarding the encoding detection.
+     * Constructs a Reader using an InputStream and the associated content-type header. This constructor is lenient regarding the encoding detection.
      * <p>
-     * First it checks if the stream has BOM. If there is not BOM checks the
-     * content-type encoding. If there is not content-type encoding checks the
-     * XML prolog encoding. If there is not XML prolog encoding uses the default
-     * encoding mandated by the content-type MIME type.
+     * First it checks if the stream has BOM. If there is not BOM checks the content-type encoding. If there is not content-type encoding checks the XML prolog
+     * encoding. If there is not XML prolog encoding uses the default encoding mandated by the content-type MIME type.
      * </p>
      * <p>
-     * If lenient detection is indicated and the detection above fails as per
-     * specifications it then attempts the following:
+     * If lenient detection is indicated and the detection above fails as per specifications it then attempts the following:
      * </p>
      * <p>
-     * If the content type was 'text/html' it replaces it with 'text/xml' and
-     * tries the detection again.
+     * If the content type was 'text/html' it replaces it with 'text/xml' and tries the detection again.
      * </p>
      * <p>
      * Else if the XML prolog had a charset encoding that encoding is used.
@@ -433,40 +459,32 @@ public class XmlStreamReader extends Reader {
      * Else 'UTF-8' is used.
      * </p>
      * <p>
-     * If lenient detection is indicated an XmlStreamReaderException is never
-     * thrown.
+     * If lenient detection is indicated an XmlStreamReaderException is never thrown.
      * </p>
      *
-     * @param inputStream InputStream to create the reader from.
-     * @param httpContentType content-type header to use for the resolution of
-     *        the charset encoding.
-     * @param lenient indicates if the charset encoding detection should be
-     *        relaxed.
-     * @throws IOException thrown if there is a problem reading the file.
-     * @throws XmlStreamReaderException thrown if the charset encoding could not
-     *         be determined according to the specification.
+     * @param inputStream     InputStream to create the reader from.
+     * @param httpContentType content-type header to use for the resolution of the charset encoding.
+     * @param lenient         indicates if the charset encoding detection should be relaxed.
+     * @throws IOException              thrown if there is a problem reading the file.
+     * @throws XmlStreamReaderException thrown if the charset encoding could not be determined according to the specification.
+     * @deprecated Use {@link #builder()}
      */
-    public XmlStreamReader(final InputStream inputStream, final String httpContentType,
-            final boolean lenient) throws IOException {
+    @Deprecated
+    public XmlStreamReader(final InputStream inputStream, final String httpContentType, final boolean lenient) throws IOException {
         this(inputStream, httpContentType, lenient, null);
     }
 
     /**
-     * Constructs a Reader using an InputStream and the associated content-type
-     * header. This constructor is lenient regarding the encoding detection.
+     * Constructs a Reader using an InputStream and the associated content-type header. This constructor is lenient regarding the encoding detection.
      * <p>
-     * First it checks if the stream has BOM. If there is not BOM checks the
-     * content-type encoding. If there is not content-type encoding checks the
-     * XML prolog encoding. If there is not XML prolog encoding uses the default
-     * encoding mandated by the content-type MIME type.
+     * First it checks if the stream has BOM. If there is not BOM checks the content-type encoding. If there is not content-type encoding checks the XML prolog
+     * encoding. If there is not XML prolog encoding uses the default encoding mandated by the content-type MIME type.
      * </p>
      * <p>
-     * If lenient detection is indicated and the detection above fails as per
-     * specifications it then attempts the following:
+     * If lenient detection is indicated and the detection above fails as per specifications it then attempts the following:
      * </p>
      * <p>
-     * If the content type was 'text/html' it replaces it with 'text/xml' and
-     * tries the detection again.
+     * If the content type was 'text/html' it replaces it with 'text/xml' and tries the detection again.
      * </p>
      * <p>
      * Else if the XML prolog had a charset encoding that encoding is used.
@@ -478,23 +496,21 @@ public class XmlStreamReader extends Reader {
      * Else 'UTF-8' is used.
      * </p>
      * <p>
-     * If lenient detection is indicated an XmlStreamReaderException is never
-     * thrown.
+     * If lenient detection is indicated an XmlStreamReaderException is never thrown.
      * </p>
      *
-     * @param inputStream InputStream to create the reader from.
-     * @param httpContentType content-type header to use for the resolution of
-     *        the charset encoding.
-     * @param lenient indicates if the charset encoding detection should be
-     *        relaxed.
+     * @param inputStream     InputStream to create the reader from.
+     * @param httpContentType content-type header to use for the resolution of the charset encoding.
+     * @param lenient         indicates if the charset encoding detection should be relaxed.
      * @param defaultEncoding The default encoding
-     * @throws IOException thrown if there is a problem reading the file.
-     * @throws XmlStreamReaderException thrown if the charset encoding could not
-     *         be determined according to the specification.
+     * @throws IOException              thrown if there is a problem reading the file.
+     * @throws XmlStreamReaderException thrown if the charset encoding could not be determined according to the specification.
+     * @deprecated Use {@link #builder()}
      */
+    @Deprecated
     @SuppressWarnings("resource") // InputStream is managed through a InputStreamReader in this instance.
-    public XmlStreamReader(final InputStream inputStream, final String httpContentType,
-            final boolean lenient, final String defaultEncoding) throws IOException {
+    public XmlStreamReader(final InputStream inputStream, final String httpContentType, final boolean lenient, final String defaultEncoding)
+            throws IOException {
         Objects.requireNonNull(inputStream, "inputStream");
         this.defaultEncoding = defaultEncoding;
         final BOMInputStream bom = new BOMInputStream(new BufferedInputStream(inputStream, IOUtils.DEFAULT_BUFFER_SIZE), false, BOMS);
@@ -506,18 +522,18 @@ public class XmlStreamReader extends Reader {
     /**
      * Constructs a Reader for a File.
      * <p>
-     * It looks for the UTF-8 BOM first, if none sniffs the XML prolog charset,
-     * if this is also missing defaults to UTF-8.
+     * It looks for the UTF-8 BOM first, if none sniffs the XML prolog charset, if this is also missing defaults to UTF-8.
      * </p>
      * <p>
-     * It does a lenient charset encoding detection, check the constructor with
-     * the lenient parameter for details.
+     * It does a lenient charset encoding detection, check the constructor with the lenient parameter for details.
      * </p>
      *
      * @param file File to create a Reader from.
      * @throws IOException thrown if there is a problem reading the file.
      * @since 2.11.0
+     * @deprecated Use {@link #builder()}
      */
+    @Deprecated
     @SuppressWarnings("resource") // InputStream is managed through another reader in this instance.
     public XmlStreamReader(final Path file) throws IOException {
         this(Files.newInputStream(Objects.requireNonNull(file, "file")));
@@ -526,22 +542,17 @@ public class XmlStreamReader extends Reader {
     /**
      * Constructs a Reader using the InputStream of a URL.
      * <p>
-     * If the URL is not of type HTTP and there is not 'content-type' header in
-     * the fetched data it uses the same logic used for Files.
+     * If the URL is not of type HTTP and there is not 'content-type' header in the fetched data it uses the same logic used for Files.
      * </p>
      * <p>
-     * If the URL is a HTTP Url or there is a 'content-type' header in the
-     * fetched data it uses the same logic used for an InputStream with
-     * content-type.
+     * If the URL is a HTTP Url or there is a 'content-type' header in the fetched data it uses the same logic used for an InputStream with content-type.
      * </p>
      * <p>
-     * It does a lenient charset encoding detection, check the constructor with
-     * the lenient parameter for details.
+     * It does a lenient charset encoding detection, check the constructor with the lenient parameter for details.
      * </p>
      *
      * @param url URL to create a Reader from.
-     * @throws IOException thrown if there is a problem reading the stream of
-     *         the URL.
+     * @throws IOException thrown if there is a problem reading the stream of the URL.
      */
     public XmlStreamReader(final URL url) throws IOException {
         this(Objects.requireNonNull(url, "url").openConnection(), null);
@@ -550,24 +561,19 @@ public class XmlStreamReader extends Reader {
     /**
      * Constructs a Reader using the InputStream of a URLConnection.
      * <p>
-     * If the URLConnection is not of type HttpURLConnection and there is not
-     * 'content-type' header in the fetched data it uses the same logic used for
-     * files.
+     * If the URLConnection is not of type HttpURLConnection and there is not 'content-type' header in the fetched data it uses the same logic used for files.
      * </p>
      * <p>
-     * If the URLConnection is a HTTP Url or there is a 'content-type' header in
-     * the fetched data it uses the same logic used for an InputStream with
+     * If the URLConnection is a HTTP Url or there is a 'content-type' header in the fetched data it uses the same logic used for an InputStream with
      * content-type.
      * </p>
      * <p>
-     * It does a lenient charset encoding detection, check the constructor with
-     * the lenient parameter for details.
+     * It does a lenient charset encoding detection, check the constructor with the lenient parameter for details.
      * </p>
      *
-     * @param urlConnection URLConnection to create a Reader from.
+     * @param urlConnection   URLConnection to create a Reader from.
      * @param defaultEncoding The default encoding
-     * @throws IOException thrown if there is a problem reading the stream of
-     *         the URLConnection.
+     * @throws IOException thrown if there is a problem reading the stream of the URLConnection.
      */
     public XmlStreamReader(final URLConnection urlConnection, final String defaultEncoding) throws IOException {
         Objects.requireNonNull(urlConnection, "urlConnection");
@@ -576,8 +582,19 @@ public class XmlStreamReader extends Reader {
         final String contentType = urlConnection.getContentType();
         final InputStream inputStream = urlConnection.getInputStream();
         @SuppressWarnings("resource") // managed by the InputStreamReader tracked by this instance
-        final BOMInputStream bomInput = new BOMInputStream(new BufferedInputStream(inputStream, IOUtils.DEFAULT_BUFFER_SIZE), false, BOMS);
-        final BOMInputStream piInput = new BOMInputStream(bomInput, true, XML_GUESS_BYTES);
+        // @formatter:off
+        final BOMInputStream bomInput = BOMInputStream.builder()
+            .setInputStream(new BufferedInputStream(inputStream, IOUtils.DEFAULT_BUFFER_SIZE))
+            .setInclude(false)
+            .setByteOrderMarks(BOMS)
+            .get();
+        @SuppressWarnings("resource")
+        final BOMInputStream piInput = BOMInputStream.builder()
+            .setInputStream(new BufferedInputStream(bomInput, IOUtils.DEFAULT_BUFFER_SIZE))
+            .setInclude(true)
+            .setByteOrderMarks(XML_GUESS_BYTES)
+            .get();
+        // @formatter:on
         if (urlConnection instanceof HttpURLConnection || contentType != null) {
             this.encoding = processHttpStream(bomInput, piInput, contentType, lenient);
         } else {
@@ -590,17 +607,15 @@ public class XmlStreamReader extends Reader {
      * Calculates the HTTP encoding.
      *
      * @param httpContentType The HTTP content type
-     * @param bomEnc BOM encoding
-     * @param xmlGuessEnc XML Guess encoding
-     * @param xmlEnc XML encoding
-     * @param lenient indicates if the charset encoding detection should be
-     *        relaxed.
+     * @param bomEnc          BOM encoding
+     * @param xmlGuessEnc     XML Guess encoding
+     * @param xmlEnc          XML encoding
+     * @param lenient         indicates if the charset encoding detection should be relaxed.
      * @return the HTTP encoding
      * @throws IOException thrown if there is a problem reading the stream.
      */
-    String calculateHttpEncoding(final String httpContentType,
-            final String bomEnc, final String xmlGuessEnc, final String xmlEnc,
-            final boolean lenient) throws IOException {
+    String calculateHttpEncoding(final String httpContentType, final String bomEnc, final String xmlGuessEnc, final String xmlEnc, final boolean lenient)
+            throws IOException {
 
         // Lenient and has XML encoding
         if (lenient && xmlEnc != null) {
@@ -609,8 +624,8 @@ public class XmlStreamReader extends Reader {
 
         // Determine mime/encoding content types from HTTP Content Type
         final String cTMime = getContentTypeMime(httpContentType);
-        final String cTEnc  = getContentTypeEncoding(httpContentType);
-        final boolean appXml  = isAppXml(cTMime);
+        final String cTEnc = getContentTypeEncoding(httpContentType);
+        final boolean appXml = isAppXml(cTMime);
         final boolean textXml = isTextXml(cTMime);
 
         // Mime type NOT "application/xml" or "text/xml"
@@ -669,22 +684,20 @@ public class XmlStreamReader extends Reader {
     /**
      * Calculate the raw encoding.
      *
-     * @param bomEnc BOM encoding
+     * @param bomEnc      BOM encoding
      * @param xmlGuessEnc XML Guess encoding
-     * @param xmlEnc XML encoding
+     * @param xmlEnc      XML encoding
      * @return the raw encoding
      * @throws IOException thrown if there is a problem reading the stream.
      */
-    String calculateRawEncoding(final String bomEnc, final String xmlGuessEnc,
-            final String xmlEnc) throws IOException {
+    String calculateRawEncoding(final String bomEnc, final String xmlGuessEnc, final String xmlEnc) throws IOException {
 
         // BOM is Null
         if (bomEnc == null) {
             if (xmlGuessEnc == null || xmlEnc == null) {
                 return defaultEncoding == null ? UTF_8 : defaultEncoding;
             }
-            if (xmlEnc.equals(UTF_16) &&
-               (xmlGuessEnc.equals(UTF_16BE) || xmlGuessEnc.equals(UTF_16LE))) {
+            if (xmlEnc.equals(UTF_16) && (xmlGuessEnc.equals(UTF_16BE) || xmlGuessEnc.equals(UTF_16LE))) {
                 return xmlGuessEnc;
             }
             return xmlEnc;
@@ -747,20 +760,17 @@ public class XmlStreamReader extends Reader {
     /**
      * Does lenient detection.
      *
-     * @param httpContentType content-type header to use for the resolution of
-     *        the charset encoding.
-     * @param ex The thrown exception
+     * @param httpContentType content-type header to use for the resolution of the charset encoding.
+     * @param ex              The thrown exception
      * @return the encoding
      * @throws IOException thrown if there is a problem reading the stream.
      */
-    private String doLenientDetection(String httpContentType,
-            XmlStreamReaderException ex) throws IOException {
+    private String doLenientDetection(String httpContentType, XmlStreamReaderException ex) throws IOException {
         if (httpContentType != null && httpContentType.startsWith("text/html")) {
             httpContentType = httpContentType.substring("text/html".length());
             httpContentType = "text/xml" + httpContentType;
             try {
-                return calculateHttpEncoding(httpContentType, ex.getBomEncoding(),
-                        ex.getXmlGuessEncoding(), ex.getXmlEncoding(), true);
+                return calculateHttpEncoding(httpContentType, ex.getBomEncoding(), ex.getXmlGuessEncoding(), ex.getXmlEncoding(), true);
             } catch (final XmlStreamReaderException ex2) {
                 ex = ex2;
             }
@@ -778,10 +788,9 @@ public class XmlStreamReader extends Reader {
     /**
      * Process the raw stream.
      *
-     * @param bom BOMInputStream to detect byte order marks
-     * @param pis BOMInputStream to guess XML encoding
-     * @param lenient indicates if the charset encoding detection should be
-     *        relaxed.
+     * @param bom     BOMInputStream to detect byte order marks
+     * @param pis     BOMInputStream to guess XML encoding
+     * @param lenient indicates if the charset encoding detection should be relaxed.
      * @return the encoding to be used
      * @throws IOException thrown if there is a problem reading the stream.
      */
@@ -800,8 +809,7 @@ public class XmlStreamReader extends Reader {
     }
 
     /**
-     * Gets the default encoding to use if none is set in HTTP content-type,
-     * XML prolog and the rules based on content-type are not adequate.
+     * Gets the default encoding to use if none is set in HTTP content-type, XML prolog and the rules based on content-type are not adequate.
      * <p>
      * If it is NULL the content-type based rules are used.
      * </p>
@@ -824,15 +832,15 @@ public class XmlStreamReader extends Reader {
     /**
      * Processes an HTTP stream.
      *
-     * @param bomInput BOMInputStream to detect byte order marks
-     * @param piInput BOMInputStream to guess XML encoding
+     * @param bomInput        BOMInputStream to detect byte order marks
+     * @param piInput         BOMInputStream to guess XML encoding
      * @param httpContentType The HTTP content type
-     * @param lenient indicates if the charset encoding detection should be relaxed.
+     * @param lenient         indicates if the charset encoding detection should be relaxed.
      * @return the encoding to be used
      * @throws IOException thrown if there is a problem reading the stream.
      */
     private String processHttpStream(final BOMInputStream bomInput, final BOMInputStream piInput, final String httpContentType, final boolean lenient)
-        throws IOException {
+            throws IOException {
         final String bomEnc = bomInput.getBOMCharsetName();
         final String xmlGuessEnc = piInput.getBOMCharsetName();
         final String xmlEnc = getXmlProlog(piInput, xmlGuessEnc);
@@ -849,9 +857,9 @@ public class XmlStreamReader extends Reader {
     /**
      * Reads the underlying reader's {@code read(char[], int, int)} method.
      *
-     * @param buf the buffer to read the characters into
+     * @param buf    the buffer to read the characters into
      * @param offset The start offset
-     * @param len The number of bytes to read
+     * @param len    The number of bytes to read
      * @return the number of characters read or -1 if the end of stream
      * @throws IOException if an I/O error occurs.
      */
diff --git a/src/main/java/org/apache/commons/io/output/DeferredFileOutputStream.java b/src/main/java/org/apache/commons/io/output/DeferredFileOutputStream.java
index 35d5cc64..d8ba8b70 100644
--- a/src/main/java/org/apache/commons/io/output/DeferredFileOutputStream.java
+++ b/src/main/java/org/apache/commons/io/output/DeferredFileOutputStream.java
@@ -25,27 +25,136 @@ import java.nio.file.Path;
 import java.util.Objects;
 import java.util.function.Supplier;
 
+import org.apache.commons.io.build.AbstractStreamBuilder;
 import org.apache.commons.io.file.PathUtils;
 
 /**
- * An output stream which will retain data in memory until a specified threshold is reached, and only then commit it to
- * disk. If the stream is closed before the threshold is reached, the data will not be written to disk at all.
+ * An output stream which will retain data in memory until a specified threshold is reached, and only then commit it to disk. If the stream is closed before the
+ * threshold is reached, the data will not be written to disk at all.
  * <p>
- * This class originated in FileUpload processing. In this use case, you do not know in advance the size of the file
- * being uploaded. If the file is small you want to store it in memory (for speed), but if the file is large you want to
- * store it to file (to avoid memory issues).
+ * This class originated in FileUpload processing. In this use case, you do not know in advance the size of the file being uploaded. If the file is small you
+ * want to store it in memory (for speed), but if the file is large you want to store it to file (to avoid memory issues).
  * </p>
  */
 public class DeferredFileOutputStream extends ThresholdingOutputStream {
 
+    /**
+     * Builds a new {@link DeferredFileOutputStream} instance.
+     * <p>
+     * For example:
+     * </p>
+     * <pre>{@code
+     * DeferredFileOutputStream s = DeferredFileOutputStream.builder()
+     *   .setPath(path)
+     *   .setBufferSize(4096)
+     *   .setDirectory(dir)
+     *   .setOutputFile(outputFile)
+     *   .setPrefix(prefix)
+     *   .setSuffix(suffix)
+     *   .setThreshold(threshold)
+     *   .get()}
+     * </pre>
+     * @since 2.12.02
+     */
+    public static class Builder extends AbstractStreamBuilder<DeferredFileOutputStream, Builder> {
+
+        private int threshold;
+        private File outputFile;
+        private String prefix;
+        private String suffix;
+        private File directory;
+
+        public Builder() {
+            setBufferSizeDefault(AbstractByteArrayOutputStream.DEFAULT_SIZE);
+            setBufferSize(AbstractByteArrayOutputStream.DEFAULT_SIZE);
+        }
+
+        @Override
+        public DeferredFileOutputStream get() {
+            return new DeferredFileOutputStream(threshold, outputFile, prefix, suffix, directory, getBufferSize());
+        }
+
+        /**
+         * Sets the temporary file directory.
+         *
+         * @param directory Temporary file directory.
+         * @return this
+         */
+        public Builder setDirectory(final File directory) {
+            this.directory = directory;
+            return this;
+        }
+
+        /**
+         * Sets the file to which data is saved beyond the threshold.
+         *
+         * @param outputFile The file to which data is saved beyond the threshold.
+         * @return this
+         */
+        public Builder setOutputFile(final File outputFile) {
+            this.outputFile = outputFile;
+            return this;
+        }
+
+        /**
+         * Sets the prefix to use for the temporary file.
+         *
+         * @param prefix Prefix to use for the temporary file.
+         * @return this
+         */
+        public Builder setPrefix(final String prefix) {
+            this.prefix = prefix;
+            return this;
+        }
+
+        /**
+         * Sets the suffix to use for the temporary file.
+         *
+         * @param suffix Suffix to use for the temporary file.
+         * @return this
+         */
+        public Builder setSuffix(final String suffix) {
+            this.suffix = suffix;
+            return this;
+        }
+
+        /**
+         * Sets the number of bytes at which to trigger an event.
+         *
+         * @param threshold The number of bytes at which to trigger an event.
+         * @return this
+         */
+        public Builder setThreshold(final int threshold) {
+            this.threshold = threshold;
+            return this;
+        }
+
+    }
+
+    /**
+     * Constructs a new {@link Builder}.
+     *
+     * @return a new {@link Builder}.
+     * @since 2.12.0
+     */
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    private static int checkBufferSize(final int initialBufferSize) {
+        if (initialBufferSize < 0) {
+            throw new IllegalArgumentException("Initial buffer size must be at least 0.");
+        }
+        return initialBufferSize;
+    }
+
     /**
      * The output stream to which data will be written prior to the threshold being reached.
      */
     private ByteArrayOutputStream memoryOutputStream;
 
     /**
-     * The output stream to which data will be written at any given time. This will always be one of
-     * {@code memoryOutputStream} or {@code diskOutputStream}.
+     * The output stream to which data will be written at any given time. This will always be one of {@code memoryOutputStream} or {@code diskOutputStream}.
      */
     private OutputStream currentOutputStream;
 
@@ -75,94 +184,83 @@ public class DeferredFileOutputStream extends ThresholdingOutputStream {
     private boolean closed;
 
     /**
-     * Constructs an instance of this class which will trigger an event at the specified threshold, and save data to a
-     * file beyond that point. The initial buffer size will default to
-     * {@value AbstractByteArrayOutputStream#DEFAULT_SIZE} bytes which is ByteArrayOutputStream's default buffer size.
+     * Constructs an instance of this class which will trigger an event at the specified threshold, and save data to a file beyond that point. The initial
+     * buffer size will default to {@value AbstractByteArrayOutputStream#DEFAULT_SIZE} bytes which is ByteArrayOutputStream's default buffer size.
      *
-     * @param threshold The number of bytes at which to trigger an event.
+     * @param threshold  The number of bytes at which to trigger an event.
      * @param outputFile The file to which data is saved beyond the threshold.
+     * @deprecated Use {@link #builder()}
      */
+    @Deprecated
     public DeferredFileOutputStream(final int threshold, final File outputFile) {
         this(threshold, outputFile, null, null, null, AbstractByteArrayOutputStream.DEFAULT_SIZE);
     }
 
     /**
-     * Constructs an instance of this class which will trigger an event at the specified threshold, and save data either
-     * to a file beyond that point.
+     * Constructs an instance of this class which will trigger an event at the specified threshold, and save data either to a file beyond that point.
      *
-     * @param threshold The number of bytes at which to trigger an event.
-     * @param outputFile The file to which data is saved beyond the threshold.
-     * @param prefix Prefix to use for the temporary file.
-     * @param suffix Suffix to use for the temporary file.
-     * @param directory Temporary file directory.
+     * @param threshold         The number of bytes at which to trigger an event.
+     * @param outputFile        The file to which data is saved beyond the threshold.
+     * @param prefix            Prefix to use for the temporary file.
+     * @param suffix            Suffix to use for the temporary file.
+     * @param directory         Temporary file directory.
      * @param initialBufferSize The initial size of the in memory buffer.
      */
-    private DeferredFileOutputStream(final int threshold, final File outputFile, final String prefix,
-            final String suffix, final File directory, final int initialBufferSize) {
+    private DeferredFileOutputStream(final int threshold, final File outputFile, final String prefix, final String suffix, final File directory,
+            final int initialBufferSize) {
         super(threshold);
         this.outputPath = toPath(outputFile, null);
         this.prefix = prefix;
         this.suffix = suffix;
         this.directory = toPath(directory, PathUtils::getTempDirectory);
-
-        memoryOutputStream = new ByteArrayOutputStream(initialBufferSize);
+        memoryOutputStream = new ByteArrayOutputStream(checkBufferSize(initialBufferSize));
         currentOutputStream = memoryOutputStream;
     }
 
     /**
-     * Constructs an instance of this class which will trigger an event at the specified threshold, and save data to a
-     * file beyond that point.
+     * Constructs an instance of this class which will trigger an event at the specified threshold, and save data to a file beyond that point.
      *
-     * @param threshold The number of bytes at which to trigger an event.
+     * @param threshold         The number of bytes at which to trigger an event.
      * @param initialBufferSize The initial size of the in memory buffer.
-     * @param outputFile The file to which data is saved beyond the threshold.
-     *
+     * @param outputFile        The file to which data is saved beyond the threshold.
      * @since 2.5
+     * @deprecated Use {@link #builder()}
      */
+    @Deprecated
     public DeferredFileOutputStream(final int threshold, final int initialBufferSize, final File outputFile) {
         this(threshold, outputFile, null, null, null, initialBufferSize);
-        if (initialBufferSize < 0) {
-            throw new IllegalArgumentException("Initial buffer size must be at least 0.");
-        }
     }
 
     /**
-     * Constructs an instance of this class which will trigger an event at the specified threshold, and save data to a
-     * temporary file beyond that point.
+     * Constructs an instance of this class which will trigger an event at the specified threshold, and save data to a temporary file beyond that point.
      *
-     * @param threshold The number of bytes at which to trigger an event.
+     * @param threshold         The number of bytes at which to trigger an event.
      * @param initialBufferSize The initial size of the in memory buffer.
-     * @param prefix Prefix to use for the temporary file.
-     * @param suffix Suffix to use for the temporary file.
-     * @param directory Temporary file directory.
-     *
+     * @param prefix            Prefix to use for the temporary file.
+     * @param suffix            Suffix to use for the temporary file.
+     * @param directory         Temporary file directory.
      * @since 2.5
+     * @deprecated Use {@link #builder()}
      */
-    public DeferredFileOutputStream(final int threshold, final int initialBufferSize, final String prefix,
-        final String suffix, final File directory) {
-        this(threshold, null, prefix, suffix, directory, initialBufferSize);
-        Objects.requireNonNull(prefix, "prefix");
-        if (initialBufferSize < 0) {
-            throw new IllegalArgumentException("Initial buffer size must be at least 0.");
-        }
+    @Deprecated
+    public DeferredFileOutputStream(final int threshold, final int initialBufferSize, final String prefix, final String suffix, final File directory) {
+        this(threshold, null, Objects.requireNonNull(prefix, "prefix"), suffix, directory, initialBufferSize);
     }
 
     /**
-     * Constructs an instance of this class which will trigger an event at the specified threshold, and save data to a
-     * temporary file beyond that point. The initial buffer size will default to 32 bytes which is
-     * ByteArrayOutputStream's default buffer size.
+     * Constructs an instance of this class which will trigger an event at the specified threshold, and save data to a temporary file beyond that point. The
+     * initial buffer size will default to 32 bytes which is ByteArrayOutputStream's default buffer size.
      *
      * @param threshold The number of bytes at which to trigger an event.
-     * @param prefix Prefix to use for the temporary file.
-     * @param suffix Suffix to use for the temporary file.
+     * @param prefix    Prefix to use for the temporary file.
+     * @param suffix    Suffix to use for the temporary file.
      * @param directory Temporary file directory.
-     *
      * @since 1.4
+     * @deprecated Use {@link #builder()}
      */
-    public DeferredFileOutputStream(final int threshold, final String prefix, final String suffix,
-        final File directory) {
-        this(threshold, null, prefix, suffix, directory, AbstractByteArrayOutputStream.DEFAULT_SIZE);
-        Objects.requireNonNull(prefix, "prefix");
+    @Deprecated
+    public DeferredFileOutputStream(final int threshold, final String prefix, final String suffix, final File directory) {
+        this(threshold, null, Objects.requireNonNull(prefix, "prefix"), suffix, directory, AbstractByteArrayOutputStream.DEFAULT_SIZE);
     }
 
     /**
@@ -177,8 +275,8 @@ public class DeferredFileOutputStream extends ThresholdingOutputStream {
     }
 
     /**
-     * Gets the data for this output stream as an array of bytes, assuming that the data has been retained in memory.
-     * If the data was written to disk, this method returns {@code null}.
+     * Gets the data for this output stream as an array of bytes, assuming that the data has been retained in memory. If the data was written to disk, this
+     * method returns {@code null}.
      *
      * @return The data for this output stream, or {@code null} if no such data is available.
      */
@@ -189,11 +287,10 @@ public class DeferredFileOutputStream extends ThresholdingOutputStream {
     /**
      * Gets either the output file specified in the constructor or the temporary file created or null.
      * <p>
-     * If the constructor specifying the file is used then it returns that same output file, even when threshold has not
-     * been reached.
+     * If the constructor specifying the file is used then it returns that same output file, even when threshold has not been reached.
      * <p>
-     * If constructor specifying a temporary file prefix/suffix is used then the temporary file created once the
-     * threshold is reached is returned If the threshold was not reached then {@code null} is returned.
+     * If constructor specifying a temporary file prefix/suffix is used then the temporary file created once the threshold is reached is returned If the
+     * threshold was not reached then {@code null} is returned.
      *
      * @return The file for this output stream, or {@code null} if no such file exists.
      */
@@ -202,8 +299,7 @@ public class DeferredFileOutputStream extends ThresholdingOutputStream {
     }
 
     /**
-     * Gets the current output stream. This may be memory based or disk based, depending on the current state with
-     * respect to the threshold.
+     * Gets the current output stream. This may be memory based or disk based, depending on the current state with respect to the threshold.
      *
      * @return The underlying output stream.
      *
@@ -224,9 +320,8 @@ public class DeferredFileOutputStream extends ThresholdingOutputStream {
     }
 
     /**
-     * Switches the underlying output stream from a memory based stream to one that is backed by disk. This is the point
-     * at which we realize that too much data is being written to keep in memory, so we elect to switch to disk-based
-     * storage.
+     * Switches the underlying output stream from a memory based stream to one that is backed by disk. This is the point at which we realize that too much data
+     * is being written to keep in memory, so we elect to switch to disk-based storage.
      *
      * @throws IOException if an error occurs.
      */
@@ -248,12 +343,9 @@ public class DeferredFileOutputStream extends ThresholdingOutputStream {
     }
 
     /**
-     * Converts the current contents of this byte stream to an {@link InputStream}.
-     * If the data for this output stream has been retained in memory, the
-     * returned stream is backed by buffers of {@code this} stream,
-     * avoiding memory allocation and copy, thus saving space and time.<br>
-     * Otherwise, the returned stream will be one that is created from the data
-     * that has been committed to disk.
+     * Converts the current contents of this byte stream to an {@link InputStream}. If the data for this output stream has been retained in memory, the returned
+     * stream is backed by buffers of {@code this} stream, avoiding memory allocation and copy, thus saving space and time.<br>
+     * Otherwise, the returned stream will be one that is created from the data that has been committed to disk.
      *
      * @return the current contents of this output stream.
      * @throws IOException if this stream is not yet closed or an error occurs.
@@ -284,7 +376,7 @@ public class DeferredFileOutputStream extends ThresholdingOutputStream {
      *
      * @param outputStream output stream to write to.
      * @throws NullPointerException if the OutputStream is {@code null}.
-     * @throws IOException if this stream is not yet closed or an error occurs.
+     * @throws IOException          if this stream is not yet closed or an error occurs.
      */
     public void writeTo(final OutputStream outputStream) throws IOException {
         // we may only need to check if this is closed if we are working with a file
diff --git a/src/main/java/org/apache/commons/io/output/FileWriterWithEncoding.java b/src/main/java/org/apache/commons/io/output/FileWriterWithEncoding.java
index aacde96e..85130422 100644
--- a/src/main/java/org/apache/commons/io/output/FileWriterWithEncoding.java
+++ b/src/main/java/org/apache/commons/io/output/FileWriterWithEncoding.java
@@ -21,7 +21,6 @@ import java.io.FileWriter;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.io.OutputStreamWriter;
-import java.io.Writer;
 import java.nio.charset.Charset;
 import java.nio.charset.CharsetEncoder;
 import java.util.Objects;
@@ -29,36 +28,109 @@ import java.util.Objects;
 import org.apache.commons.io.Charsets;
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.build.AbstractStreamBuilder;
 
 /**
  * Writer of files that allows the encoding to be set.
  * <p>
- * This class provides a simple alternative to {@link FileWriter} that allows an encoding to be set. Unfortunately, it
- * cannot subclass {@link FileWriter}.
+ * This class provides a simple alternative to {@link FileWriter} that allows an encoding to be set. Unfortunately, it cannot subclass {@link FileWriter}.
  * </p>
  * <p>
  * By default, the file will be overwritten, but this may be changed to append.
  * </p>
  * <p>
- * The encoding must be specified using either the name of the {@link Charset}, the {@link Charset}, or a
- * {@link CharsetEncoder}. If the default encoding is required then use the {@link java.io.FileWriter} directly, rather
- * than this implementation.
+ * The encoding must be specified using either the name of the {@link Charset}, the {@link Charset}, or a {@link CharsetEncoder}. If the default encoding is
+ * required then use the {@link java.io.FileWriter} directly, rather than this implementation.
  * </p>
  *
  * @since 1.4
  */
 public class FileWriterWithEncoding extends ProxyWriter {
 
+    /**
+     * Builds a new {@link DeferredFileOutputStream} instance.
+     * <p>
+     * Using a CharsetEncoder:
+     * </p>
+     * <pre>{@code
+     * FileWriterWithEncoding s = FileWriterWithEncoding.builder()
+     *   .setPath(path)
+     *   .setAppend(false)
+     *   .setCharsetEncoder(StandardCharsets.UTF_8.newEncoder())
+     *   .get()}
+     * </pre>
+     * <p>
+     * Using a Charset:
+     * </p>
+     * <pre>{@code
+     * FileWriterWithEncoding s = FileWriterWithEncoding.builder()
+     *   .setPath(path)
+     *   .setAppend(false)
+     *   .setCharsetEncoder(StandardCharsets.UTF_8)
+     *   .get()}
+     * </pre>
+     * @since 2.12.02
+     */
+    public static class Builder extends AbstractStreamBuilder<FileWriterWithEncoding, Builder> {
+
+        private boolean append;
+
+        private CharsetEncoder charsetEncoder = super.getCharset().newEncoder();
+
+        @SuppressWarnings("resource")
+        @Override
+        public FileWriterWithEncoding get() throws IOException {
+            if (charsetEncoder != null && getCharset() != null && !charsetEncoder.charset().equals(getCharset())) {
+                throw new IllegalStateException(String.format("Mismatched Charset(%s) and CharsetEncoder(%s)", getCharset(), charsetEncoder.charset()));
+            }
+            final Object encoder = charsetEncoder != null ? charsetEncoder : getCharset();
+            return new FileWriterWithEncoding(FileWriterWithEncoding.initWriter(getOrigin().getFile(), encoder, append));
+        }
+
+        /**
+         * Sets whether or not to append.
+         *
+         * @param append Whether or not to append.
+         * @return this
+         */
+        public Builder setAppend(final boolean append) {
+            this.append = append;
+            return this;
+        }
+
+        /**
+         * Sets charsetEncoder to use for encoding.
+         *
+         * @param charsetEncoder The charsetEncoder to use for encoding.
+         * @return this
+         */
+        public Builder setCharsetEncoder(final CharsetEncoder charsetEncoder) {
+            this.charsetEncoder = charsetEncoder;
+            return this;
+        }
+
+    }
+
+    /**
+     * Constructs a new {@link Builder}.
+     *
+     * @return Creates a new {@link Builder}.
+     * @since 2.12.0
+     */
+    public static Builder builder() {
+        return new Builder();
+    }
+
     /**
      * Initializes the wrapped file writer. Ensure that a cleanup occurs if the writer creation fails.
      *
-     * @param file the file to be accessed
+     * @param file     the file to be accessed
      * @param encoding the encoding to use - may be Charset, CharsetEncoder or String, null uses the default Charset.
-     * @param append true to append
-     * @return the initialized writer
+     * @param append   true to append
+     * @return a new initialized OutputStreamWriter
      * @throws IOException if an error occurs
      */
-    private static Writer initWriter(final File file, final Object encoding, final boolean append) throws IOException {
+    private static OutputStreamWriter initWriter(final File file, final Object encoding, final boolean append) throws IOException {
         Objects.requireNonNull(file, "file");
         OutputStream outputStream = null;
         final boolean fileExistedAlready = file.exists();
@@ -87,11 +159,13 @@ public class FileWriterWithEncoding extends ProxyWriter {
     /**
      * Constructs a FileWriterWithEncoding with a file encoding.
      *
-     * @param file the file to write to, not null
+     * @param file    the file to write to, not null
      * @param charset the encoding to use, not null
      * @throws NullPointerException if the file or encoding is null
-     * @throws IOException in case of an I/O error
+     * @throws IOException          in case of an I/O error
+     * @deprecated Use {@link #builder()}
      */
+    @Deprecated
     public FileWriterWithEncoding(final File file, final Charset charset) throws IOException {
         this(file, charset, false);
     }
@@ -99,25 +173,29 @@ public class FileWriterWithEncoding extends ProxyWriter {
     /**
      * Constructs a FileWriterWithEncoding with a file encoding.
      *
-     * @param file the file to write to, not null.
+     * @param file     the file to write to, not null.
      * @param encoding the name of the requested charset, null uses the default Charset.
-     * @param append true if content should be appended, false to overwrite.
+     * @param append   true if content should be appended, false to overwrite.
      * @throws NullPointerException if the file is null.
-     * @throws IOException in case of an I/O error.
+     * @throws IOException          in case of an I/O error.
+     * @deprecated Use {@link #builder()}
      */
+    @Deprecated
     @SuppressWarnings("resource") // Call site is responsible for closing a new instance.
     public FileWriterWithEncoding(final File file, final Charset encoding, final boolean append) throws IOException {
-        super(initWriter(file, encoding, append));
+        this(initWriter(file, encoding, append));
     }
 
     /**
      * Constructs a FileWriterWithEncoding with a file encoding.
      *
-     * @param file the file to write to, not null
+     * @param file           the file to write to, not null
      * @param charsetEncoder the encoding to use, not null
      * @throws NullPointerException if the file or encoding is null
-     * @throws IOException in case of an I/O error
+     * @throws IOException          in case of an I/O error
+     * @deprecated Use {@link #builder()}
      */
+    @Deprecated
     public FileWriterWithEncoding(final File file, final CharsetEncoder charsetEncoder) throws IOException {
         this(file, charsetEncoder, false);
     }
@@ -125,25 +203,29 @@ public class FileWriterWithEncoding extends ProxyWriter {
     /**
      * Constructs a FileWriterWithEncoding with a file encoding.
      *
-     * @param file the file to write to, not null.
+     * @param file           the file to write to, not null.
      * @param charsetEncoder the encoding to use, null uses the default Charset.
-     * @param append true if content should be appended, false to overwrite.
+     * @param append         true if content should be appended, false to overwrite.
      * @throws NullPointerException if the file is null.
-     * @throws IOException in case of an I/O error.
+     * @throws IOException          in case of an I/O error.
+     * @deprecated Use {@link #builder()}
      */
+    @Deprecated
     @SuppressWarnings("resource") // Call site is responsible for closing a new instance.
     public FileWriterWithEncoding(final File file, final CharsetEncoder charsetEncoder, final boolean append) throws IOException {
-        super(initWriter(file, charsetEncoder, append));
+        this(initWriter(file, charsetEncoder, append));
     }
 
     /**
      * Constructs a FileWriterWithEncoding with a file encoding.
      *
-     * @param file the file to write to, not null
+     * @param file        the file to write to, not null
      * @param charsetName the name of the requested charset, not null
      * @throws NullPointerException if the file or encoding is null
-     * @throws IOException in case of an I/O error
+     * @throws IOException          in case of an I/O error
+     * @deprecated Use {@link #builder()}
      */
+    @Deprecated
     public FileWriterWithEncoding(final File file, final String charsetName) throws IOException {
         this(file, charsetName, false);
     }
@@ -151,25 +233,33 @@ public class FileWriterWithEncoding extends ProxyWriter {
     /**
      * Constructs a FileWriterWithEncoding with a file encoding.
      *
-     * @param file the file to write to, not null.
+     * @param file        the file to write to, not null.
      * @param charsetName the name of the requested charset, null uses the default Charset.
-     * @param append true if content should be appended, false to overwrite.
+     * @param append      true if content should be appended, false to overwrite.
      * @throws NullPointerException if the file is null.
-     * @throws IOException in case of an I/O error.
+     * @throws IOException          in case of an I/O error.
+     * @deprecated Use {@link #builder()}
      */
+    @Deprecated
     @SuppressWarnings("resource") // Call site is responsible for closing a new instance.
     public FileWriterWithEncoding(final File file, final String charsetName, final boolean append) throws IOException {
-        super(initWriter(file, charsetName, append));
+        this(initWriter(file, charsetName, append));
+    }
+
+    private FileWriterWithEncoding(final OutputStreamWriter outputStreamWriter) {
+        super(outputStreamWriter);
     }
 
     /**
      * Constructs a FileWriterWithEncoding with a file encoding.
      *
      * @param fileName the name of the file to write to, not null
-     * @param charset the charset to use, not null
+     * @param charset  the charset to use, not null
      * @throws NullPointerException if the file name or encoding is null
-     * @throws IOException in case of an I/O error
+     * @throws IOException          in case of an I/O error
+     * @deprecated Use {@link #builder()}
      */
+    @Deprecated
     public FileWriterWithEncoding(final String fileName, final Charset charset) throws IOException {
         this(new File(fileName), charset, false);
     }
@@ -178,11 +268,13 @@ public class FileWriterWithEncoding extends ProxyWriter {
      * Constructs a FileWriterWithEncoding with a file encoding.
      *
      * @param fileName the name of the file to write to, not null
-     * @param charset the encoding to use, not null
-     * @param append true if content should be appended, false to overwrite
+     * @param charset  the encoding to use, not null
+     * @param append   true if content should be appended, false to overwrite
      * @throws NullPointerException if the file name or encoding is null
-     * @throws IOException in case of an I/O error
+     * @throws IOException          in case of an I/O error
+     * @deprecated Use {@link #builder()}
      */
+    @Deprecated
     public FileWriterWithEncoding(final String fileName, final Charset charset, final boolean append) throws IOException {
         this(new File(fileName), charset, append);
     }
@@ -193,8 +285,10 @@ public class FileWriterWithEncoding extends ProxyWriter {
      * @param fileName the name of the file to write to, not null
      * @param encoding the encoding to use, not null
      * @throws NullPointerException if the file name or encoding is null
-     * @throws IOException in case of an I/O error
+     * @throws IOException          in case of an I/O error
+     * @deprecated Use {@link #builder()}
      */
+    @Deprecated
     public FileWriterWithEncoding(final String fileName, final CharsetEncoder encoding) throws IOException {
         this(new File(fileName), encoding, false);
     }
@@ -202,12 +296,14 @@ public class FileWriterWithEncoding extends ProxyWriter {
     /**
      * Constructs a FileWriterWithEncoding with a file encoding.
      *
-     * @param fileName the name of the file to write to, not null
+     * @param fileName       the name of the file to write to, not null
      * @param charsetEncoder the encoding to use, not null
-     * @param append true if content should be appended, false to overwrite
+     * @param append         true if content should be appended, false to overwrite
      * @throws NullPointerException if the file name or encoding is null
-     * @throws IOException in case of an I/O error
+     * @throws IOException          in case of an I/O error
+     * @deprecated Use {@link #builder()}
      */
+    @Deprecated
     public FileWriterWithEncoding(final String fileName, final CharsetEncoder charsetEncoder, final boolean append) throws IOException {
         this(new File(fileName), charsetEncoder, append);
     }
@@ -215,11 +311,13 @@ public class FileWriterWithEncoding extends ProxyWriter {
     /**
      * Constructs a FileWriterWithEncoding with a file encoding.
      *
-     * @param fileName the name of the file to write to, not null
+     * @param fileName    the name of the file to write to, not null
      * @param charsetName the name of the requested charset, not null
      * @throws NullPointerException if the file name or encoding is null
-     * @throws IOException in case of an I/O error
+     * @throws IOException          in case of an I/O error
+     * @deprecated Use {@link #builder()}
      */
+    @Deprecated
     public FileWriterWithEncoding(final String fileName, final String charsetName) throws IOException {
         this(new File(fileName), charsetName, false);
     }
@@ -227,12 +325,14 @@ public class FileWriterWithEncoding extends ProxyWriter {
     /**
      * Constructs a FileWriterWithEncoding with a file encoding.
      *
-     * @param fileName the name of the file to write to, not null
+     * @param fileName    the name of the file to write to, not null
      * @param charsetName the name of the requested charset, not null
-     * @param append true if content should be appended, false to overwrite
+     * @param append      true if content should be appended, false to overwrite
      * @throws NullPointerException if the file name or encoding is null
-     * @throws IOException in case of an I/O error
+     * @throws IOException          in case of an I/O error
+     * @deprecated Use {@link #builder()}
      */
+    @Deprecated
     public FileWriterWithEncoding(final String fileName, final String charsetName, final boolean append) throws IOException {
         this(new File(fileName), charsetName, append);
     }
diff --git a/src/main/java/org/apache/commons/io/output/LockableFileWriter.java b/src/main/java/org/apache/commons/io/output/LockableFileWriter.java
index 4f63cf47..9ebdc934 100644
--- a/src/main/java/org/apache/commons/io/output/LockableFileWriter.java
+++ b/src/main/java/org/apache/commons/io/output/LockableFileWriter.java
@@ -23,37 +23,111 @@ import java.io.IOException;
 import java.io.OutputStreamWriter;
 import java.io.Writer;
 import java.nio.charset.Charset;
+import java.util.Objects;
 
 import org.apache.commons.io.Charsets;
 import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.build.AbstractOrigin;
+import org.apache.commons.io.build.AbstractOriginSupplier;
+import org.apache.commons.io.build.AbstractStreamBuilder;
 
 /**
- * FileWriter that will create and honor lock files to allow simple
- * cross thread file lock handling.
+ * FileWriter that will create and honor lock files to allow simple cross thread file lock handling.
  * <p>
- * This class provides a simple alternative to {@link FileWriter}
- * that will use a lock file to prevent duplicate writes.
+ * This class provides a simple alternative to {@link FileWriter} that will use a lock file to prevent duplicate writes.
  * </p>
  * <p>
- * <b>Note:</b> The lock file is deleted when {@link #close()} is called
- * - or if the main file cannot be opened initially.
- * In the (unlikely) event that the lock file cannot be deleted,
- * an exception is thrown.
+ * <b>Note:</b> The lock file is deleted when {@link #close()} is called - or if the main file cannot be opened initially. In the (unlikely) event that the lock
+ * file cannot be deleted, an exception is thrown.
  * </p>
  * <p>
- * By default, the file will be overwritten, but this may be changed to append.
- * The lock directory may be specified, but defaults to the system property
- * {@code java.io.tmpdir}.
- * The encoding may also be specified, and defaults to the platform default.
+ * By default, the file will be overwritten, but this may be changed to append. The lock directory may be specified, but defaults to the system property
+ * {@code java.io.tmpdir}. The encoding may also be specified, and defaults to the platform default.
  * </p>
  */
 public class LockableFileWriter extends Writer {
-    // Cannot extend ProxyWriter, as requires writer to be
-    // known when super() is called
+
+    /**
+     * Builds a new {@link LockableFileWriter} instance.
+     * <p>
+     * Using a CharsetEncoder:
+     * </p>
+     * <pre>{@code
+     * LockableFileWriter w = LockableFileWriter.builder()
+     *   .setPath(path)
+     *   .setAppend(false)
+     *   .setLockDirectory("Some/Directory")
+     *   .get()}
+     * </pre>
+     * <p>
+     * @since 2.12.02
+     */
+    public static class Builder extends AbstractStreamBuilder<LockableFileWriter, Builder> {
+
+        private boolean append;
+        private AbstractOrigin<?, ?> lockDirectory = AbstractOriginSupplier.newFileOrigin(FileUtils.getTempDirectoryPath());
+
+        public Builder() {
+            setBufferSizeDefault(AbstractByteArrayOutputStream.DEFAULT_SIZE);
+            setBufferSize(AbstractByteArrayOutputStream.DEFAULT_SIZE);
+        }
+
+        @Override
+        public LockableFileWriter get() throws IOException {
+            return new LockableFileWriter(getOrigin().getFile(), getCharset(), append, lockDirectory.getFile().toString());
+        }
+
+        /**
+         * Sets whether to append (true) or overwrite (false).
+         *
+         * @param append whether to append (true) or overwrite (false).
+         * @return this
+         */
+        public Builder setAppend(final boolean append) {
+            this.append = append;
+            return this;
+        }
+
+        /**
+         * Sets the directory in which the lock file should be held.
+         *
+         * @param lockDirectory the directory in which the lock file should be held.
+         * @return this
+         */
+        public Builder setLockDirectory(final File lockDirectory) {
+            this.lockDirectory = AbstractOriginSupplier.newFileOrigin(lockDirectory != null ? lockDirectory : FileUtils.getTempDirectory());
+            return this;
+        }
+
+        /**
+         * Sets the directory in which the lock file should be held.
+         *
+         * @param lockDirectory the directory in which the lock file should be held.
+         * @return this
+         */
+        public Builder setLockDirectory(final String lockDirectory) {
+            this.lockDirectory = AbstractOriginSupplier.newFileOrigin(lockDirectory != null ? lockDirectory : FileUtils.getTempDirectoryPath());
+            return this;
+        }
+
+    }
 
     /** The extension for the lock file. */
     private static final String LCK = ".lck";
 
+    // Cannot extend ProxyWriter, as requires writer to be
+    // known when super() is called
+
+    /**
+     * Constructs a new {@link Builder}.
+     *
+     * @return a new {@link Builder}.
+     * @since 2.12.0
+     */
+    public static Builder builder() {
+        return new Builder();
+    }
+
     /** The writer to decorate. */
     private final Writer out;
 
@@ -61,13 +135,14 @@ public class LockableFileWriter extends Writer {
     private final File lockFile;
 
     /**
-     * Constructs a LockableFileWriter.
-     * If the file exists, it is overwritten.
+     * Constructs a LockableFileWriter. If the file exists, it is overwritten.
      *
-     * @param file  the file to write to, not null
+     * @param file the file to write to, not null
      * @throws NullPointerException if the file is null
-     * @throws IOException in case of an I/O error
+     * @throws IOException          in case of an I/O error
+     * @deprecated Use {@link #builder()}
      */
+    @Deprecated
     public LockableFileWriter(final File file) throws IOException {
         this(file, false, null);
     }
@@ -75,11 +150,13 @@ public class LockableFileWriter extends Writer {
     /**
      * Constructs a LockableFileWriter.
      *
-     * @param file  the file to write to, not null
-     * @param append  true if content should be appended, false to overwrite
+     * @param file   the file to write to, not null
+     * @param append true if content should be appended, false to overwrite
      * @throws NullPointerException if the file is null
-     * @throws IOException in case of an I/O error
+     * @throws IOException          in case of an I/O error
+     * @deprecated Use {@link #builder()}
      */
+    @Deprecated
     public LockableFileWriter(final File file, final boolean append) throws IOException {
         this(file, append, null);
     }
@@ -87,11 +164,11 @@ public class LockableFileWriter extends Writer {
     /**
      * Constructs a LockableFileWriter.
      *
-     * @param file  the file to write to, not null
+     * @param file    the file to write to, not null
      * @param append  true if content should be appended, false to overwrite
-     * @param lockDir  the directory in which the lock file should be held
+     * @param lockDir the directory in which the lock file should be held
      * @throws NullPointerException if the file is null
-     * @throws IOException in case of an I/O error
+     * @throws IOException          in case of an I/O error
      * @deprecated 2.5 use {@link #LockableFileWriter(File, Charset, boolean, String)} instead
      */
     @Deprecated
@@ -102,12 +179,14 @@ public class LockableFileWriter extends Writer {
     /**
      * Constructs a LockableFileWriter with a file encoding.
      *
-     * @param file  the file to write to, not null
-     * @param charset  the charset to use, null means platform default
+     * @param file    the file to write to, not null
+     * @param charset the charset to use, null means platform default
      * @throws NullPointerException if the file is null
-     * @throws IOException in case of an I/O error
+     * @throws IOException          in case of an I/O error
      * @since 2.3
+     * @deprecated Use {@link #builder()}
      */
+    @Deprecated
     public LockableFileWriter(final File file, final Charset charset) throws IOException {
         this(file, charset, false, null);
     }
@@ -115,17 +194,19 @@ public class LockableFileWriter extends Writer {
     /**
      * Constructs a LockableFileWriter with a file encoding.
      *
-     * @param file  the file to write to, not null
-     * @param charset  the name of the requested charset, null means platform default
+     * @param file    the file to write to, not null
+     * @param charset the name of the requested charset, null means platform default
      * @param append  true if content should be appended, false to overwrite
-     * @param lockDir  the directory in which the lock file should be held
+     * @param lockDir the directory in which the lock file should be held
      * @throws NullPointerException if the file is null
-     * @throws IOException in case of an I/O error
+     * @throws IOException          in case of an I/O error
      * @since 2.3
+     * @deprecated Use {@link #builder()}
      */
+    @Deprecated
     public LockableFileWriter(final File file, final Charset charset, final boolean append, String lockDir) throws IOException {
         // init file to create/append
-        final File absFile = file.getAbsoluteFile();
+        final File absFile = Objects.requireNonNull(file, "file").getAbsoluteFile();
         if (absFile.getParentFile() != null) {
             FileUtils.forceMkdir(absFile.getParentFile());
         }
@@ -152,14 +233,15 @@ public class LockableFileWriter extends Writer {
     /**
      * Constructs a LockableFileWriter with a file encoding.
      *
-     * @param file  the file to write to, not null
-     * @param charsetName  the name of the requested charset, null means platform default
-     * @throws NullPointerException if the file is null
-     * @throws IOException in case of an I/O error
-     * @throws java.nio.charset.UnsupportedCharsetException
-     *             thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not
-     *             supported.
+     * @param file        the file to write to, not null
+     * @param charsetName the name of the requested charset, null means platform default
+     * @throws NullPointerException                         if the file is null
+     * @throws IOException                                  in case of an I/O error
+     * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not
+     *                                                      supported.
+     * @deprecated Use {@link #builder()}
      */
+    @Deprecated
     public LockableFileWriter(final File file, final String charsetName) throws IOException {
         this(file, charsetName, false, null);
     }
@@ -167,29 +249,30 @@ public class LockableFileWriter extends Writer {
     /**
      * Constructs a LockableFileWriter with a file encoding.
      *
-     * @param file  the file to write to, not null
-     * @param charsetName  the encoding to use, null means platform default
-     * @param append  true if content should be appended, false to overwrite
-     * @param lockDir  the directory in which the lock file should be held
-     * @throws NullPointerException if the file is null
-     * @throws IOException in case of an I/O error
-     * @throws java.nio.charset.UnsupportedCharsetException
-     *             thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not
-     *             supported.
+     * @param file        the file to write to, not null
+     * @param charsetName the encoding to use, null means platform default
+     * @param append      true if content should be appended, false to overwrite
+     * @param lockDir     the directory in which the lock file should be held
+     * @throws NullPointerException                         if the file is null
+     * @throws IOException                                  in case of an I/O error
+     * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not
+     *                                                      supported.
+     * @deprecated Use {@link #builder()}
      */
-    public LockableFileWriter(final File file, final String charsetName, final boolean append,
-            final String lockDir) throws IOException {
+    @Deprecated
+    public LockableFileWriter(final File file, final String charsetName, final boolean append, final String lockDir) throws IOException {
         this(file, Charsets.toCharset(charsetName), append, lockDir);
     }
 
     /**
-     * Constructs a LockableFileWriter.
-     * If the file exists, it is overwritten.
+     * Constructs a LockableFileWriter. If the file exists, it is overwritten.
      *
-     * @param fileName  the file to write to, not null
+     * @param fileName the file to write to, not null
      * @throws NullPointerException if the file is null
-     * @throws IOException in case of an I/O error
+     * @throws IOException          in case of an I/O error
+     * @deprecated Use {@link #builder()}
      */
+    @Deprecated
     public LockableFileWriter(final String fileName) throws IOException {
         this(fileName, false, null);
     }
@@ -197,11 +280,13 @@ public class LockableFileWriter extends Writer {
     /**
      * Constructs a LockableFileWriter.
      *
-     * @param fileName  file to write to, not null
-     * @param append  true if content should be appended, false to overwrite
+     * @param fileName file to write to, not null
+     * @param append   true if content should be appended, false to overwrite
      * @throws NullPointerException if the file is null
-     * @throws IOException in case of an I/O error
+     * @throws IOException          in case of an I/O error
+     * @deprecated Use {@link #builder()}
      */
+    @Deprecated
     public LockableFileWriter(final String fileName, final boolean append) throws IOException {
         this(fileName, append, null);
     }
@@ -209,12 +294,14 @@ public class LockableFileWriter extends Writer {
     /**
      * Constructs a LockableFileWriter.
      *
-     * @param fileName  the file to write to, not null
-     * @param append  true if content should be appended, false to overwrite
+     * @param fileName the file to write to, not null
+     * @param append   true if content should be appended, false to overwrite
      * @param lockDir  the directory in which the lock file should be held
      * @throws NullPointerException if the file is null
-     * @throws IOException in case of an I/O error
+     * @throws IOException          in case of an I/O error
+     * @deprecated Use {@link #builder()}
      */
+    @Deprecated
     public LockableFileWriter(final String fileName, final boolean append, final String lockDir) throws IOException {
         this(new File(fileName), append, lockDir);
     }
@@ -249,6 +336,7 @@ public class LockableFileWriter extends Writer {
 
     /**
      * Flushes the stream.
+     *
      * @throws IOException if an I/O error occurs.
      */
     @Override
@@ -257,11 +345,10 @@ public class LockableFileWriter extends Writer {
     }
 
     /**
-     * Initializes the wrapped file writer.
-     * Ensure that a cleanup occurs if the writer creation fails.
+     * Initializes the wrapped file writer. Ensure that a cleanup occurs if the writer creation fails.
      *
-     * @param file  the file to be accessed
-     * @param charset  the charset to use
+     * @param file    the file to be accessed
+     * @param charset the charset to use
      * @param append  true to append
      * @return The initialized writer
      * @throws IOException if an error occurs
@@ -283,7 +370,7 @@ public class LockableFileWriter extends Writer {
     /**
      * Tests that we can write to the lock directory.
      *
-     * @param lockDir  the File representing the lock directory
+     * @param lockDir the File representing the lock directory
      * @throws IOException if we cannot write to the lock directory
      * @throws IOException if we cannot find the lock file
      */
@@ -298,6 +385,7 @@ public class LockableFileWriter extends Writer {
 
     /**
      * Writes the characters from an array.
+     *
      * @param cbuf the characters to write
      * @throws IOException if an I/O error occurs.
      */
@@ -308,9 +396,10 @@ public class LockableFileWriter extends Writer {
 
     /**
      * Writes the specified characters from an array.
+     *
      * @param cbuf the characters to write
-     * @param off The start offset
-     * @param len The number of characters to write
+     * @param off  The start offset
+     * @param len  The number of characters to write
      * @throws IOException if an I/O error occurs.
      */
     @Override
@@ -320,6 +409,7 @@ public class LockableFileWriter extends Writer {
 
     /**
      * Writes a character.
+     *
      * @param c the character to write
      * @throws IOException if an I/O error occurs.
      */
@@ -330,6 +420,7 @@ public class LockableFileWriter extends Writer {
 
     /**
      * Writes the characters from a string.
+     *
      * @param str the string to write
      * @throws IOException if an I/O error occurs.
      */
@@ -340,6 +431,7 @@ public class LockableFileWriter extends Writer {
 
     /**
      * Writes the specified characters from a string.
+     *
      * @param str the string to write
      * @param off The start offset
      * @param len The number of characters to write
diff --git a/src/main/java/org/apache/commons/io/output/WriterOutputStream.java b/src/main/java/org/apache/commons/io/output/WriterOutputStream.java
index 7ccb4817..06d5ccd6 100644
--- a/src/main/java/org/apache/commons/io/output/WriterOutputStream.java
+++ b/src/main/java/org/apache/commons/io/output/WriterOutputStream.java
@@ -29,45 +29,37 @@ import java.nio.charset.StandardCharsets;
 
 import org.apache.commons.io.Charsets;
 import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.build.AbstractStreamBuilder;
 import org.apache.commons.io.charset.CharsetDecoders;
 
 /**
- * {@link OutputStream} implementation that transforms a byte stream to a
- * character stream using a specified charset encoding and writes the resulting
- * stream to a {@link Writer}. The stream is transformed using a
- * {@link CharsetDecoder} object, guaranteeing that all charset
- * encodings supported by the JRE are handled correctly.
+ * {@link OutputStream} implementation that transforms a byte stream to a character stream using a specified charset encoding and writes the resulting stream to
+ * a {@link Writer}. The stream is transformed using a {@link CharsetDecoder} object, guaranteeing that all charset encodings supported by the JRE are handled
+ * correctly.
  * <p>
- * The output of the {@link CharsetDecoder} is buffered using a fixed size buffer.
- * This implies that the data is written to the underlying {@link Writer} in chunks
- * that are no larger than the size of this buffer. By default, the buffer is
- * flushed only when it overflows or when {@link #flush()} or {@link #close()}
- * is called. In general there is therefore no need to wrap the underlying {@link Writer}
- * in a {@link java.io.BufferedWriter}. {@link WriterOutputStream} can also
- * be instructed to flush the buffer after each write operation. In this case, all
- * available data is written immediately to the underlying {@link Writer}, implying that
- * the current position of the {@link Writer} is correlated to the current position
- * of the {@link WriterOutputStream}.
+ * The output of the {@link CharsetDecoder} is buffered using a fixed size buffer. This implies that the data is written to the underlying {@link Writer} in
+ * chunks that are no larger than the size of this buffer. By default, the buffer is flushed only when it overflows or when {@link #flush()} or {@link #close()}
+ * is called. In general there is therefore no need to wrap the underlying {@link Writer} in a {@link java.io.BufferedWriter}. {@link WriterOutputStream} can
+ * also be instructed to flush the buffer after each write operation. In this case, all available data is written immediately to the underlying {@link Writer},
+ * implying that the current position of the {@link Writer} is correlated to the current position of the {@link WriterOutputStream}.
  * <p>
- * {@link WriterOutputStream} implements the inverse transformation of {@link java.io.OutputStreamWriter};
- * in the following example, writing to {@code out2} would have the same result as writing to
- * {@code out} directly (provided that the byte sequence is legal with respect to the
- * charset encoding):
+ * {@link WriterOutputStream} implements the inverse transformation of {@link java.io.OutputStreamWriter}; in the following example, writing to {@code out2}
+ * would have the same result as writing to {@code out} directly (provided that the byte sequence is legal with respect to the charset encoding):
+ *
  * <pre>
  * OutputStream out = ...
  * Charset cs = ...
  * OutputStreamWriter writer = new OutputStreamWriter(out, cs);
- * WriterOutputStream out2 = new WriterOutputStream(writer, cs);</pre>
- * {@link WriterOutputStream} implements the same transformation as {@link java.io.InputStreamReader},
- * except that the control flow is reversed: both classes transform a byte stream
- * into a character stream, but {@link java.io.InputStreamReader} pulls data from the underlying stream,
- * while {@link WriterOutputStream} pushes it to the underlying stream.
+ * WriterOutputStream out2 = new WriterOutputStream(writer, cs);
+ * </pre>
+ *
+ * {@link WriterOutputStream} implements the same transformation as {@link java.io.InputStreamReader}, except that the control flow is reversed: both classes
+ * transform a byte stream into a character stream, but {@link java.io.InputStreamReader} pulls data from the underlying stream, while
+ * {@link WriterOutputStream} pushes it to the underlying stream.
  * <p>
- * Note that while there are use cases where there is no alternative to using
- * this class, very often the need to use this class is an indication of a flaw
- * in the design of the code. This class is typically used in situations where an existing
- * API only accepts an {@link OutputStream} object, but where the stream is known to represent
- * character data that must be decoded for further use.
+ * Note that while there are use cases where there is no alternative to using this class, very often the need to use this class is an indication of a flaw in
+ * the design of the code. This class is typically used in situations where an existing API only accepts an {@link OutputStream} object, but where the stream is
+ * known to represent character data that must be decoded for further use.
  * </p>
  * <p>
  * Instances of {@link WriterOutputStream} are not thread safe.
@@ -77,14 +69,99 @@ import org.apache.commons.io.charset.CharsetDecoders;
  * @since 2.0
  */
 public class WriterOutputStream extends OutputStream {
+
+    /**
+     * Builds a new {@link WriterOutputStream} instance.
+     * <p>
+     * For example:
+     * </p>
+     * <pre>{@code
+     * WriterOutputStream s = WriterOutputStream.builder()
+     *   .setPath(path)
+     *   .setBufferSize(8192)
+     *   .setCharset(StandardCharsets.UTF_8)
+     *   .setWriteImmediately(false)
+     *   .get()}
+     * </pre>
+     * <p>
+     * @since 2.12.0
+     */
+    public static class Builder extends AbstractStreamBuilder<WriterOutputStream, Builder> {
+
+        private CharsetDecoder charsetDecoder;
+        private boolean writeImmediately;
+
+        public Builder() {
+            this.charsetDecoder = getCharset().newDecoder();
+        }
+
+        @SuppressWarnings("resource")
+        @Override
+        public WriterOutputStream get() throws IOException {
+            return new WriterOutputStream(getOrigin().getWriter(getCharset()), charsetDecoder, getBufferSize(), writeImmediately);
+        }
+
+        @Override
+        public Builder setCharset(final Charset charset) {
+            super.setCharset(charset);
+            this.charsetDecoder = getCharset().newDecoder();
+            return this;
+        }
+
+        @Override
+        public Builder setCharset(final String charset) {
+            super.setCharset(charset);
+            this.charsetDecoder = getCharset().newDecoder();
+            return this;
+        }
+
+        /**
+         * Sets the charset decoder.
+         *
+         * @param charsetDecoder the charset decoder.
+         * @return this
+         */
+        public Builder setCharsetDecoder(final CharsetDecoder charsetDecoder) {
+            this.charsetDecoder = charsetDecoder != null ? charsetDecoder : getCharsetDefault().newDecoder();
+            super.setCharset(this.charsetDecoder.charset());
+            return this;
+        }
+
+        /**
+         * Sets whether the output buffer will be flushed after each write operation ({@code true}), i.e. all available data will be written to the underlying
+         * {@link Writer} immediately. If {@code false}, the output buffer will only be flushed when it overflows or when {@link #flush()} or {@link #close()}
+         * is called.
+         *
+         * @param writeImmediately If {@code true} the output buffer will be flushed after each write operation, i.e. all available data will be written to the
+         *                         underlying {@link Writer} immediately. If {@code false}, the output buffer will only be flushed when it overflows or when
+         *                         {@link #flush()} or {@link #close()} is called.
+         * @return this
+         */
+        public Builder setWriteImmediately(final boolean writeImmediately) {
+            this.writeImmediately = writeImmediately;
+            return this;
+        }
+
+    }
+
     private static final int BUFFER_SIZE = IOUtils.DEFAULT_BUFFER_SIZE;
 
+    /**
+     * Constructs a new {@link Builder}.
+     *
+     * @return a new {@link Builder}.
+     * @since 2.12.0
+     */
+    public static Builder builder() {
+        return new Builder();
+    }
+
     /**
      * Check if the JDK in use properly supports the given charset.
      *
      * @param charset the charset to check the support for
      */
-    private static void checkIbmJdkWithBrokenUTF16(final Charset charset){
+    private static void checkIbmJdkWithBrokenUTF16(final Charset charset) {
         if (!StandardCharsets.UTF_16.name().equals(charset.name())) {
             return;
         }
@@ -100,45 +177,41 @@ public class WriterOutputStream extends OutputStream {
             bb2.flip();
             try {
                 charsetDecoder2.decode(bb2, cb2, i == len - 1);
-            } catch ( final IllegalArgumentException e){
-                throw new UnsupportedOperationException("UTF-16 requested when running on an IBM JDK with broken UTF-16 support. " +
-                        "Please find a JDK that supports UTF-16 if you intend to use UF-16 with WriterOutputStream");
+            } catch (final IllegalArgumentException e) {
+                throw new UnsupportedOperationException("UTF-16 requested when running on an IBM JDK with broken UTF-16 support. "
+                        + "Please find a JDK that supports UTF-16 if you intend to use UF-16 with WriterOutputStream");
             }
             bb2.compact();
         }
         cb2.rewind();
-        if (!TEST_STRING_2.equals(cb2.toString())){
-            throw new UnsupportedOperationException("UTF-16 requested when running on an IBM JDK with broken UTF-16 support. " +
-                    "Please find a JDK that supports UTF-16 if you intend to use UF-16 with WriterOutputStream");
+        if (!TEST_STRING_2.equals(cb2.toString())) {
+            throw new UnsupportedOperationException("UTF-16 requested when running on an IBM JDK with broken UTF-16 support. "
+                    + "Please find a JDK that supports UTF-16 if you intend to use UF-16 with WriterOutputStream");
         }
 
     }
+
     private final Writer writer;
     private final CharsetDecoder decoder;
 
     private final boolean writeImmediately;
 
     /**
-     * ByteBuffer used as input for the decoder. This buffer can be small
-     * as it is used only to transfer the received data to the
-     * decoder.
+     * ByteBuffer used as input for the decoder. This buffer can be small as it is used only to transfer the received data to the decoder.
      */
     private final ByteBuffer decoderIn = ByteBuffer.allocate(128);
 
     /**
-     * CharBuffer used as output for the decoder. It should be
-     * somewhat larger as we write from this buffer to the
-     * underlying Writer.
+     * CharBuffer used as output for the decoder. It should be somewhat larger as we write from this buffer to the underlying Writer.
      */
     private final CharBuffer decoderOut;
 
     /**
-     * Constructs a new {@link WriterOutputStream} that uses the default character encoding and with a default output
-     * buffer size of {@value #BUFFER_SIZE} characters. The output buffer will only be flushed when it overflows or when
-     * {@link #flush()} or {@link #close()} is called.
+     * Constructs a new {@link WriterOutputStream} that uses the default character encoding and with a default output buffer size of {@value #BUFFER_SIZE}
+     * characters. The output buffer will only be flushed when it overflows or when {@link #flush()} or {@link #close()} is called.
      *
      * @param writer the target {@link Writer}
-     * @deprecated 2.5 use {@link #WriterOutputStream(Writer, Charset)} instead
+     * @deprecated Use {@link #builder()} instead
      */
     @Deprecated
     public WriterOutputStream(final Writer writer) {
@@ -146,13 +219,14 @@ public class WriterOutputStream extends OutputStream {
     }
 
     /**
-     * Constructs a new {@link WriterOutputStream} with a default output buffer size of {@value #BUFFER_SIZE}
-     * characters. The output buffer will only be flushed when it overflows or when {@link #flush()} or {@link #close()}
-     * is called.
+     * Constructs a new {@link WriterOutputStream} with a default output buffer size of {@value #BUFFER_SIZE} characters. The output buffer will only be flushed
+     * when it overflows or when {@link #flush()} or {@link #close()} is called.
      *
-     * @param writer the target {@link Writer}
+     * @param writer  the target {@link Writer}
      * @param charset the charset encoding
+     * @deprecated Use {@link #builder()} instead
      */
+    @Deprecated
     public WriterOutputStream(final Writer writer, final Charset charset) {
         this(writer, charset, BUFFER_SIZE, false);
     }
@@ -160,15 +234,15 @@ public class WriterOutputStream extends OutputStream {
     /**
      * Constructs a new {@link WriterOutputStream}.
      *
-     * @param writer the target {@link Writer}
-     * @param charset the charset encoding
-     * @param bufferSize the size of the output buffer in number of characters
-     * @param writeImmediately If {@code true} the output buffer will be flushed after each
-     *                         write operation, i.e. all available data will be written to the
-     *                         underlying {@link Writer} immediately. If {@code false}, the
-     *                         output buffer will only be flushed when it overflows or when
+     * @param writer           the target {@link Writer}
+     * @param charset          the charset encoding
+     * @param bufferSize       the size of the output buffer in number of characters
+     * @param writeImmediately If {@code true} the output buffer will be flushed after each write operation, i.e. all available data will be written to the
+     *                         underlying {@link Writer} immediately. If {@code false}, the output buffer will only be flushed when it overflows or when
      *                         {@link #flush()} or {@link #close()} is called.
+     * @deprecated Use {@link #builder()} instead
      */
+    @Deprecated
     public WriterOutputStream(final Writer writer, final Charset charset, final int bufferSize, final boolean writeImmediately) {
         // @formatter:off
         this(writer,
@@ -182,14 +256,15 @@ public class WriterOutputStream extends OutputStream {
     }
 
     /**
-     * Constructs a new {@link WriterOutputStream} with a default output buffer size of {@value #BUFFER_SIZE}
-     * characters. The output buffer will only be flushed when it overflows or when {@link #flush()} or {@link #close()}
-     * is called.
+     * Constructs a new {@link WriterOutputStream} with a default output buffer size of {@value #BUFFER_SIZE} characters. The output buffer will only be flushed
+     * when it overflows or when {@link #flush()} or {@link #close()} is called.
      *
-     * @param writer the target {@link Writer}
+     * @param writer  the target {@link Writer}
      * @param decoder the charset decoder
      * @since 2.1
+     * @deprecated Use {@link #builder()} instead
      */
+    @Deprecated
     public WriterOutputStream(final Writer writer, final CharsetDecoder decoder) {
         this(writer, decoder, BUFFER_SIZE, false);
     }
@@ -197,32 +272,33 @@ public class WriterOutputStream extends OutputStream {
     /**
      * Constructs a new {@link WriterOutputStream}.
      *
-     * @param writer the target {@link Writer}
-     * @param decoder the charset decoder
-     * @param bufferSize the size of the output buffer in number of characters
-     * @param writeImmediately If {@code true} the output buffer will be flushed after each
-     *                         write operation, i.e. all available data will be written to the
-     *                         underlying {@link Writer} immediately. If {@code false}, the
-     *                         output buffer will only be flushed when it overflows or when
+     * @param writer           the target {@link Writer}
+     * @param decoder          the charset decoder
+     * @param bufferSize       the size of the output buffer in number of characters
+     * @param writeImmediately If {@code true} the output buffer will be flushed after each write operation, i.e. all available data will be written to the
+     *                         underlying {@link Writer} immediately. If {@code false}, the output buffer will only be flushed when it overflows or when
      *                         {@link #flush()} or {@link #close()} is called.
      * @since 2.1
+     * @deprecated Use {@link #builder()} instead
      */
+    @Deprecated
     public WriterOutputStream(final Writer writer, final CharsetDecoder decoder, final int bufferSize, final boolean writeImmediately) {
         checkIbmJdkWithBrokenUTF16(CharsetDecoders.toCharsetDecoder(decoder).charset());
         this.writer = writer;
         this.decoder = CharsetDecoders.toCharsetDecoder(decoder);
         this.writeImmediately = writeImmediately;
-        decoderOut = CharBuffer.allocate(bufferSize);
+        this.decoderOut = CharBuffer.allocate(bufferSize);
     }
 
     /**
-     * Constructs a new {@link WriterOutputStream} with a default output buffer size of {@value #BUFFER_SIZE}
-     * characters. The output buffer will only be flushed when it overflows or when {@link #flush()} or {@link #close()}
-     * is called.
+     * Constructs a new {@link WriterOutputStream} with a default output buffer size of {@value #BUFFER_SIZE} characters. The output buffer will only be flushed
+     * when it overflows or when {@link #flush()} or {@link #close()} is called.
      *
-     * @param writer the target {@link Writer}
+     * @param writer      the target {@link Writer}
      * @param charsetName the name of the charset encoding
+     * @deprecated Use {@link #builder()} instead
      */
+    @Deprecated
     public WriterOutputStream(final Writer writer, final String charsetName) {
         this(writer, charsetName, BUFFER_SIZE, false);
     }
@@ -230,24 +306,23 @@ public class WriterOutputStream extends OutputStream {
     /**
      * Constructs a new {@link WriterOutputStream}.
      *
-     * @param writer the target {@link Writer}
-     * @param charsetName the name of the charset encoding
-     * @param bufferSize the size of the output buffer in number of characters
-     * @param writeImmediately If {@code true} the output buffer will be flushed after each
-     *                         write operation, i.e. all available data will be written to the
-     *                         underlying {@link Writer} immediately. If {@code false}, the
-     *                         output buffer will only be flushed when it overflows or when
+     * @param writer           the target {@link Writer}
+     * @param charsetName      the name of the charset encoding
+     * @param bufferSize       the size of the output buffer in number of characters
+     * @param writeImmediately If {@code true} the output buffer will be flushed after each write operation, i.e. all available data will be written to the
+     *                         underlying {@link Writer} immediately. If {@code false}, the output buffer will only be flushed when it overflows or when
      *                         {@link #flush()} or {@link #close()} is called.
+     * @deprecated Use {@link #builder()} instead
      */
-    public WriterOutputStream(final Writer writer, final String charsetName, final int bufferSize,
-                              final boolean writeImmediately) {
+    @Deprecated
+    public WriterOutputStream(final Writer writer, final String charsetName, final int bufferSize, final boolean writeImmediately) {
         this(writer, Charsets.toCharset(charsetName), bufferSize, writeImmediately);
     }
 
     /**
-     * Close the stream. Any remaining content accumulated in the output buffer
-     * will be written to the underlying {@link Writer}. After that
+     * Close the stream. Any remaining content accumulated in the output buffer will be written to the underlying {@link Writer}. After that
      * {@link Writer#close()} will be called.
+     *
      * @throws IOException if an I/O error occurs.
      */
     @Override
@@ -258,9 +333,9 @@ public class WriterOutputStream extends OutputStream {
     }
 
     /**
-     * Flush the stream. Any remaining content accumulated in the output buffer
-     * will be written to the underlying {@link Writer}. After that
+     * Flush the stream. Any remaining content accumulated in the output buffer will be written to the underlying {@link Writer}. After that
      * {@link Writer#flush()} will be called.
+     *
      * @throws IOException if an I/O error occurs.
      */
     @Override
@@ -321,7 +396,7 @@ public class WriterOutputStream extends OutputStream {
     /**
      * Write bytes from the specified byte array to the stream.
      *
-     * @param b the byte array containing the bytes to write
+     * @param b   the byte array containing the bytes to write
      * @param off the start offset in the byte array
      * @param len the number of bytes to write
      * @throws IOException if an I/O error occurs.
@@ -348,6 +423,6 @@ public class WriterOutputStream extends OutputStream {
      */
     @Override
     public void write(final int b) throws IOException {
-        write(new byte[] {(byte) b}, 0, 1);
+        write(new byte[] { (byte) b }, 0, 1);
     }
 }
diff --git a/src/main/java/org/apache/commons/io/output/XmlStreamWriter.java b/src/main/java/org/apache/commons/io/output/XmlStreamWriter.java
index ab55e093..10d2b80d 100644
--- a/src/main/java/org/apache/commons/io/output/XmlStreamWriter.java
+++ b/src/main/java/org/apache/commons/io/output/XmlStreamWriter.java
@@ -32,10 +32,11 @@ import java.util.regex.Matcher;
 
 import org.apache.commons.io.Charsets;
 import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.build.AbstractStreamBuilder;
 import org.apache.commons.io.input.XmlStreamReader;
 
 /**
- * Character stream that handles all the necessary Voodoo to figure out the
+ * Character stream that handles all the necessary work to figure out the
  * charset encoding of the XML document written to the stream.
  *
  * @see XmlStreamReader
@@ -43,6 +44,45 @@ import org.apache.commons.io.input.XmlStreamReader;
  */
 public class XmlStreamWriter extends Writer {
 
+    /**
+     * Builds a new {@link XmlStreamWriter} instance.
+     * <p>
+     * For example:
+     * </p>
+     * <pre>{@code
+     * WriterOutputStream w = WriterOutputStream.builder()
+     *   .setPath(path)
+     *   .setCharset(StandardCharsets.UTF_8)
+     *   .get()}
+     * </pre>
+     * <p>
+     * @since 2.12.02
+     */
+    public static class Builder extends AbstractStreamBuilder<XmlStreamWriter, Builder> {
+
+        public Builder() {
+            setCharsetDefault(StandardCharsets.UTF_8);
+            setCharset(StandardCharsets.UTF_8);
+        }
+
+        @SuppressWarnings("resource")
+        @Override
+        public XmlStreamWriter get() throws IOException {
+            return new XmlStreamWriter(getOrigin().getOutputStream(), getCharset());
+        }
+
+    }
+
+    /**
+     * Constructs a new {@link Builder}.
+     *
+     * @return a new {@link Builder}.
+     * @since 2.12.0
+     */
+    public static Builder builder() {
+        return new Builder();
+    }
+
     private static final int BUFFER_SIZE = IOUtils.DEFAULT_BUFFER_SIZE;
 
     private final OutputStream out;
@@ -62,7 +102,9 @@ public class XmlStreamWriter extends Writer {
      * @param file The file to write to
      * @throws FileNotFoundException if there is an error creating or
      * opening the file
+     * @deprecated Use {@link #builder()}
      */
+    @Deprecated
     public XmlStreamWriter(final File file) throws FileNotFoundException {
         this(file, null);
     }
@@ -75,7 +117,9 @@ public class XmlStreamWriter extends Writer {
      * @param defaultEncoding The default encoding if not encoding could be detected
      * @throws FileNotFoundException if there is an error creating or
      * opening the file
+     * @deprecated Use {@link #builder()}
      */
+    @Deprecated
     @SuppressWarnings("resource")
     public XmlStreamWriter(final File file, final String defaultEncoding) throws FileNotFoundException {
         this(new FileOutputStream(file), defaultEncoding);
@@ -86,7 +130,9 @@ public class XmlStreamWriter extends Writer {
      * with a default encoding of UTF-8.
      *
      * @param out The output stream
+     * @deprecated Use {@link #builder()}
      */
+    @Deprecated
     public XmlStreamWriter(final OutputStream out) {
         this(out, StandardCharsets.UTF_8);
     }
@@ -97,9 +143,8 @@ public class XmlStreamWriter extends Writer {
      *
      * @param out The output stream
      * @param defaultEncoding The default encoding if not encoding could be detected
-     * @since 2.12.0
      */
-    public XmlStreamWriter(final OutputStream out, final Charset defaultEncoding) {
+    private XmlStreamWriter(final OutputStream out, final Charset defaultEncoding) {
         this.out = out;
         this.defaultCharset = Objects.requireNonNull(defaultEncoding);
     }
@@ -110,7 +155,9 @@ public class XmlStreamWriter extends Writer {
      *
      * @param out The output stream
      * @param defaultEncoding The default encoding if not encoding could be detected
+     * @deprecated Use {@link #builder()}
      */
+    @Deprecated
     public XmlStreamWriter(final OutputStream out, final String defaultEncoding) {
         this(out, Charsets.toCharset(defaultEncoding, StandardCharsets.UTF_8));
     }
diff --git a/src/test/java/org/apache/commons/io/FileUtilsTest.java b/src/test/java/org/apache/commons/io/FileUtilsTest.java
index 6aa926e6..1c5474f6 100644
--- a/src/test/java/org/apache/commons/io/FileUtilsTest.java
+++ b/src/test/java/org/apache/commons/io/FileUtilsTest.java
@@ -265,7 +265,7 @@ public class FileUtilsTest extends AbstractTempDirTest {
         return files.stream().map(f -> {
             try {
                 return f.getCanonicalPath();
-            } catch (IOException e) {
+            } catch (final IOException e) {
                 throw new RuntimeException(e);
             }
         }).collect(Collectors.toSet());
@@ -410,6 +410,13 @@ public class FileUtilsTest extends AbstractTempDirTest {
         assertThrows(IllegalArgumentException.class, () -> FileUtils.openOutputStream(directory));
     }
 
+    /**
+     * Requires admin privileges on Windows.
+     *
+     * @throws Exception For example java.nio.file.FileSystemException:
+     *                   C:\Users\you\AppData\Local\Temp\junit2324629522183300191\FileUtilsTest8613879743106252609\symlinked-dir: A required privilege is
+     *                   not held by the client.
+     */
     @Test
     public void test_openOutputStream_intoExistingSymlinkedDir() throws Exception {
         final Path symlinkedDir = createTempSymlinkedRelativeDir();
@@ -1841,7 +1848,7 @@ public class FileUtilsTest extends AbstractTempDirTest {
             final String notSubSubFileName = "not.txt";
             TestUtils.generateTestData(new File(notSubSubDir, notSubSubFileName), 1);
 
-            final WildcardFileFilter allFilesFileFilter = new WildcardFileFilter("*.*");
+            final WildcardFileFilter allFilesFileFilter = WildcardFileFilter.builder().setWildcards("*.*").get();
             final NameFileFilter dirFilter = new NameFileFilter("subSubDir");
             iterator = FileUtils.iterateFiles(subDir, allFilesFileFilter, dirFilter);
 
@@ -1890,9 +1897,9 @@ public class FileUtilsTest extends AbstractTempDirTest {
         assertTrue(subDir3.mkdir());
         assertTrue(subDir4.mkdir());
         final File someFile = new File(subDir2, "a.txt");
-        final WildcardFileFilter fileFilterAllFiles = new WildcardFileFilter("*.*");
-        final WildcardFileFilter fileFilterAllDirs = new WildcardFileFilter("*");
-        final WildcardFileFilter fileFilterExtTxt = new WildcardFileFilter("*.txt");
+        final WildcardFileFilter fileFilterAllFiles =  WildcardFileFilter.builder().setWildcards("*.*").get();
+        final WildcardFileFilter fileFilterAllDirs = WildcardFileFilter.builder().setWildcards("*").get();
+        final WildcardFileFilter fileFilterExtTxt = WildcardFileFilter.builder().setWildcards("*.txt").get();
         try {
             try (OutputStream output = new BufferedOutputStream(Files.newOutputStream(someFile.toPath()))) {
                 TestUtils.generateTestData(output, 100);
@@ -1929,7 +1936,7 @@ public class FileUtilsTest extends AbstractTempDirTest {
         assertTrue(new File(directory, "TEST").mkdir());
         assertTrue(new File(directory, "test.txt").createNewFile());
 
-        final IOFileFilter filter = new WildcardFileFilter("*", IOCase.INSENSITIVE);
+        final IOFileFilter filter = WildcardFileFilter.builder().setWildcards("*").setIoCase(IOCase.INSENSITIVE).get();
         FileUtils.iterateFiles(directory, filter, null).forEachRemaining(file -> assertFalse(file.isDirectory(), file::getAbsolutePath));
     }
 
@@ -1942,8 +1949,8 @@ public class FileUtilsTest extends AbstractTempDirTest {
         subDir2.mkdir();
         try {
 
-            final String[] expectedFileNames = {"a.txt", "b.txt", "c.txt", "d.txt", "e.txt", "f.txt"};
-            final int[] fileSizes = {123, 234, 345, 456, 678, 789};
+            final String[] expectedFileNames = { "a.txt", "b.txt", "c.txt", "d.txt", "e.txt", "f.txt" };
+            final int[] fileSizes = { 123, 234, 345, 456, 678, 789 };
 
             for (int i = 0; i < expectedFileNames.length; ++i) {
                 final File theFile = new File(subDir, expectedFileNames[i]);
@@ -1955,7 +1962,11 @@ public class FileUtilsTest extends AbstractTempDirTest {
                 }
             }
 
-            final Collection<File> actualFiles = FileUtils.listFiles(subDir, new WildcardFileFilter("*.*"), new WildcardFileFilter("*"));
+            // @formatter:off
+            final Collection<File> actualFiles = FileUtils.listFiles(subDir,
+                    WildcardFileFilter.builder().setWildcards("*.*").get(),
+                    WildcardFileFilter.builder().setWildcards("*").get());
+            // @formatter:on
 
             final int count = actualFiles.size();
             final Object[] fileObjs = actualFiles.toArray();
@@ -1986,7 +1997,7 @@ public class FileUtilsTest extends AbstractTempDirTest {
         assertTrue(new File(directory, "TEST").mkdir());
         assertTrue(new File(directory, "test.txt").createNewFile());
 
-        final IOFileFilter filter = new WildcardFileFilter("*", IOCase.INSENSITIVE);
+        final IOFileFilter filter = WildcardFileFilter.builder().setWildcards("*").setIoCase(IOCase.INSENSITIVE).get();
         for (final File file : FileUtils.listFiles(directory, filter, null)) {
             assertFalse(file.isDirectory(), file::getAbsolutePath);
         }
@@ -2012,8 +2023,11 @@ public class FileUtilsTest extends AbstractTempDirTest {
             final File subDir3 = new File(subDir2, "subdir3");
             subDir3.mkdir();
 
-            final Collection<File> files = FileUtils.listFilesAndDirs(subDir1, new WildcardFileFilter("*.*"),
-                new WildcardFileFilter("*"));
+            // @formatter:off
+            final Collection<File> files = FileUtils.listFilesAndDirs(subDir1,
+                    WildcardFileFilter.builder().setWildcards("*.*").get(),
+                    WildcardFileFilter.builder().setWildcards("*").get());
+            // @formatter:on
 
             assertEquals(4, files.size());
             assertTrue(files.contains(subDir1), "Should contain the directory.");
@@ -2492,6 +2506,13 @@ public class FileUtilsTest extends AbstractTempDirTest {
                 "Unexpected directory size");
     }
 
+    /**
+     * Requires admin privileges on Windows.
+     *
+     * @throws Exception For example java.nio.file.FileSystemException:
+     *                   C:\Users\you\AppData\Local\Temp\junit2324629522183300191\FileUtilsTest8613879743106252609\symlinked-dir: A required privilege is
+     *                   not held by the client.
+     */
     @Test
     public void testSizeOfDirectory() throws Exception {
         final File file = new File(tempDirFile, getName());
@@ -2517,6 +2538,13 @@ public class FileUtilsTest extends AbstractTempDirTest {
         assertEquals(TEST_DIRECTORY_SIZE, FileUtils.sizeOfDirectory(file), "Unexpected directory size");
     }
 
+    /**
+     * Requires admin privileges on Windows.
+     *
+     * @throws Exception For example java.nio.file.FileSystemException:
+     *                   C:\Users\you\AppData\Local\Temp\junit2324629522183300191\FileUtilsTest8613879743106252609\symlinked-dir: A required privilege is
+     *                   not held by the client.
+     */
     @Test
     public void testSizeOfDirectoryAsBigInteger() throws Exception {
         final File file = new File(tempDirFile, getName());
@@ -3031,6 +3059,13 @@ public class FileUtilsTest extends AbstractTempDirTest {
         TestUtils.assertEqualContent(text, file);
     }
 
+    /**
+     * Requires admin privileges on Windows.
+     *
+     * @throws Exception For example java.nio.file.FileSystemException:
+     *                   C:\Users\you\AppData\Local\Temp\junit2324629522183300191\FileUtilsTest8613879743106252609\symlinked-dir: A required privilege is
+     *                   not held by the client.
+     */
     @Test
     public void testWriteStringToFileIntoSymlinkedDir() throws Exception {
         final Path symlinkDir = createTempSymlinkedRelativeDir();
diff --git a/src/test/java/org/apache/commons/io/filefilter/WildcardFileFilterTest.java b/src/test/java/org/apache/commons/io/filefilter/WildcardFileFilterTest.java
index f3c8ed61..b9ab69ce 100644
--- a/src/test/java/org/apache/commons/io/filefilter/WildcardFileFilterTest.java
+++ b/src/test/java/org/apache/commons/io/filefilter/WildcardFileFilterTest.java
@@ -46,24 +46,28 @@ public class WildcardFileFilterTest extends AbstractFilterTest {
         assertFiltering(filter, new File("log.txt"), true);
         assertFiltering(filter, new File("log.TXT"), false);
         //
+        filter = WildcardFileFilter.builder().setWildcards("*.txt").setIoCase(IOCase.SENSITIVE).get();
+        assertFiltering(filter, new File("log.txt"), true);
+        assertFiltering(filter, new File("log.TXT"), false);
+        //
         assertFiltering(filter, new File("log.txt").toPath(), true);
         assertFiltering(filter, new File("log.TXT").toPath(), false);
 
-        filter = new WildcardFileFilter("*.txt", IOCase.INSENSITIVE);
+        filter = WildcardFileFilter.builder().setWildcards("*.txt").setIoCase(IOCase.INSENSITIVE).get();
         assertFiltering(filter, new File("log.txt"), true);
         assertFiltering(filter, new File("log.TXT"), true);
         //
         assertFiltering(filter, new File("log.txt").toPath(), true);
         assertFiltering(filter, new File("log.TXT").toPath(), true);
 
-        filter = new WildcardFileFilter("*.txt", IOCase.SYSTEM);
+        filter = WildcardFileFilter.builder().setWildcards("*.txt").setIoCase(IOCase.SYSTEM).get();
         assertFiltering(filter, new File("log.txt"), true);
         assertFiltering(filter, new File("log.TXT"), WINDOWS);
         //
         assertFiltering(filter, new File("log.txt").toPath(), true);
         assertFiltering(filter, new File("log.TXT").toPath(), WINDOWS);
 
-        filter = new WildcardFileFilter("*.txt", null);
+        filter = WildcardFileFilter.builder().setWildcards("*.txt").setIoCase(null).get();
         assertFiltering(filter, new File("log.txt"), true);
         assertFiltering(filter, new File("log.TXT"), false);
         //
@@ -78,29 +82,38 @@ public class WildcardFileFilterTest extends AbstractFilterTest {
         assertFiltering(filter, new File("Test.java").toPath(), true);
         assertFiltering(filter, new File("Test.class").toPath(), true);
         assertFiltering(filter, new File("Test.jsp").toPath(), false);
+        //
+        filter = WildcardFileFilter.builder().setWildcards("*.java", "*.class").get();
+        assertFiltering(filter, new File("Test.java"), true);
+        assertFiltering(filter, new File("Test.class"), true);
+        assertFiltering(filter, new File("Test.jsp"), false);
+        //
+        assertFiltering(filter, new File("Test.java").toPath(), true);
+        assertFiltering(filter, new File("Test.class").toPath(), true);
+        assertFiltering(filter, new File("Test.jsp").toPath(), false);
 
-        filter = new WildcardFileFilter(new String[] {"*.java", "*.class"}, IOCase.SENSITIVE);
+        filter = WildcardFileFilter.builder().setWildcards("*.java", "*.class").setIoCase(IOCase.SENSITIVE).get();
         assertFiltering(filter, new File("Test.java"), true);
         assertFiltering(filter, new File("Test.JAVA"), false);
         //
         assertFiltering(filter, new File("Test.java").toPath(), true);
         assertFiltering(filter, new File("Test.JAVA").toPath(), false);
 
-        filter = new WildcardFileFilter(new String[] {"*.java", "*.class"}, IOCase.INSENSITIVE);
+        filter = WildcardFileFilter.builder().setWildcards("*.java", "*.class").setIoCase(IOCase.INSENSITIVE).get();
         assertFiltering(filter, new File("Test.java"), true);
         assertFiltering(filter, new File("Test.JAVA"), true);
         //
         assertFiltering(filter, new File("Test.java").toPath(), true);
         assertFiltering(filter, new File("Test.JAVA").toPath(), true);
 
-        filter = new WildcardFileFilter(new String[] {"*.java", "*.class"}, IOCase.SYSTEM);
+        filter = WildcardFileFilter.builder().setWildcards("*.java", "*.class").setIoCase(IOCase.SYSTEM).get();
         assertFiltering(filter, new File("Test.java"), true);
         assertFiltering(filter, new File("Test.JAVA"), WINDOWS);
         //
         assertFiltering(filter, new File("Test.java").toPath(), true);
         assertFiltering(filter, new File("Test.JAVA").toPath(), WINDOWS);
 
-        filter = new WildcardFileFilter(new String[] {"*.java", "*.class"}, null);
+        filter = WildcardFileFilter.builder().setWildcards("*.java", "*.class").setIoCase(null).get();
         assertFiltering(filter, new File("Test.java"), true);
         assertFiltering(filter, new File("Test.JAVA"), false);
         //
@@ -108,7 +121,7 @@ public class WildcardFileFilterTest extends AbstractFilterTest {
         assertFiltering(filter, new File("Test.JAVA").toPath(), false);
 
         final List<String> patternList = Arrays.asList("*.txt", "*.xml", "*.gif");
-        final IOFileFilter listFilter = new WildcardFileFilter(patternList);
+        final IOFileFilter listFilter = WildcardFileFilter.builder().setWildcards(patternList).get();
         assertFiltering(listFilter, new File("Test.txt"), true);
         assertFiltering(listFilter, new File("Test.xml"), true);
         assertFiltering(listFilter, new File("Test.gif"), true);
diff --git a/src/test/java/org/apache/commons/io/function/IOBaseStreamTest.java b/src/test/java/org/apache/commons/io/function/IOBaseStreamTest.java
index a77e55f2..45f68c95 100644
--- a/src/test/java/org/apache/commons/io/function/IOBaseStreamTest.java
+++ b/src/test/java/org/apache/commons/io/function/IOBaseStreamTest.java
@@ -20,9 +20,9 @@ package org.apache.commons.io.function;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.junit.jupiter.api.Assertions.assertSame;
 
 import java.io.IOException;
 import java.nio.file.Path;
diff --git a/src/test/java/org/apache/commons/io/function/IOPredicateTest.java b/src/test/java/org/apache/commons/io/function/IOPredicateTest.java
index a5047a79..18c9640c 100644
--- a/src/test/java/org/apache/commons/io/function/IOPredicateTest.java
+++ b/src/test/java/org/apache/commons/io/function/IOPredicateTest.java
@@ -17,10 +17,10 @@
 
 package org.apache.commons.io.function;
 
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.io.IOException;
diff --git a/src/test/java/org/apache/commons/io/input/BOMInputStreamTest.java b/src/test/java/org/apache/commons/io/input/BOMInputStreamTest.java
index 26bc933b..decc6929 100644
--- a/src/test/java/org/apache/commons/io/input/BOMInputStreamTest.java
+++ b/src/test/java/org/apache/commons/io/input/BOMInputStreamTest.java
@@ -140,7 +140,7 @@ public class BOMInputStreamTest {
     /**
      *  Creates the underlying data stream, with or without BOM.
      */
-    private InputStream createUtf8DataStream(final byte[] baseData, final boolean addBOM) {
+    private InputStream createUtf8Input(final byte[] baseData, final boolean addBOM) {
         byte[] data = baseData;
         if (addBOM) {
             data = new byte[baseData.length + 3];
@@ -185,8 +185,8 @@ public class BOMInputStreamTest {
     private void readBOMInputStreamTwice(final String resource) throws Exception {
         try (InputStream inputStream = this.getClass().getResourceAsStream(resource)) {
             assertNotNull(inputStream);
-            try (BOMInputStream bomInputStream = new BOMInputStream(inputStream)) {
-                bomInputStream.mark(1000000);
+            try (BOMInputStream bomInputStream = BOMInputStream.builder().setInputStream(inputStream).get()) {
+                bomInputStream.mark(1_000_000);
 
                 this.readFile(bomInputStream);
                 bomInputStream.reset();
@@ -206,8 +206,8 @@ public class BOMInputStreamTest {
 
     @Test
     public void skipReturnValueWithBom() throws IOException {
-        final byte[] baseData = { (byte) 0x31, (byte) 0x32, (byte) 0x33 };
-        try (BOMInputStream is1 = new BOMInputStream(createUtf8DataStream(baseData, true))) {
+        final byte[] data = { (byte) 0x31, (byte) 0x32, (byte) 0x33 };
+        try (BOMInputStream is1 = BOMInputStream.builder().setInputStream(createUtf8Input(data, true)).get()) {
             assertEquals(2, is1.skip(2));
             assertEquals((byte) 0x33, is1.read());
         }
@@ -215,8 +215,8 @@ public class BOMInputStreamTest {
 
     @Test
     public void skipReturnValueWithoutBom() throws IOException {
-        final byte[] baseData = { (byte) 0x31, (byte) 0x32, (byte) 0x33 };
-        try (BOMInputStream is2 = new BOMInputStream(createUtf8DataStream(baseData, false))) {
+        final byte[] data = { (byte) 0x31, (byte) 0x32, (byte) 0x33 };
+        try (BOMInputStream is2 = BOMInputStream.builder().setInputStream(createUtf8Input(data, false)).get()) {
             assertEquals(2, is2.skip(2)); // IO-428
             assertEquals((byte) 0x33, is2.read());
         }
@@ -225,7 +225,7 @@ public class BOMInputStreamTest {
     @Test
     public void testAvailableWithBOM() throws Exception {
         final byte[] data = { 'A', 'B', 'C', 'D' };
-        try (InputStream in = new BOMInputStream(createUtf8DataStream(data, true))) {
+        try (InputStream in = BOMInputStream.builder().setInputStream(createUtf8Input(data, true)).get()) {
             assertEquals(7, in.available());
         }
     }
@@ -233,7 +233,7 @@ public class BOMInputStreamTest {
     @Test
     public void testAvailableWithoutBOM() throws Exception {
         final byte[] data = { 'A', 'B', 'C', 'D' };
-        try (InputStream in = new BOMInputStream(createUtf8DataStream(data, false))) {
+        try (InputStream in = BOMInputStream.builder().setInputStream(createUtf8Input(data, false)).get()) {
             assertEquals(4, in.available());
         }
     }
@@ -252,7 +252,7 @@ public class BOMInputStreamTest {
     @Test
     public void testEmptyBufferWithBOM() throws Exception {
         final byte[] data = {};
-        try (InputStream in = new BOMInputStream(createUtf8DataStream(data, true))) {
+        try (InputStream in = BOMInputStream.builder().setInputStream(createUtf8Input(data, true)).get()) {
             final byte[] buf = new byte[1024];
             assertEquals(-1, in.read(buf));
         }
@@ -261,7 +261,7 @@ public class BOMInputStreamTest {
     @Test
     public void testEmptyBufferWithoutBOM() throws Exception {
         final byte[] data = {};
-        try (InputStream in = new BOMInputStream(createUtf8DataStream(data, false))) {
+        try (InputStream in = BOMInputStream.builder().setInputStream(createUtf8Input(data, false)).get()) {
             final byte[] buf = new byte[1024];
             assertEquals(-1, in.read(buf));
         }
@@ -270,7 +270,7 @@ public class BOMInputStreamTest {
     @Test
     public void testGetBOMFirstThenRead() throws Exception {
         final byte[] data = { 'A', 'B', 'C' };
-        try (BOMInputStream in = new BOMInputStream(createUtf8DataStream(data, true))) {
+        try (BOMInputStream in = BOMInputStream.builder().setInputStream(createUtf8Input(data, true)).get()) {
             assertEquals(ByteOrderMark.UTF_8, in.getBOM(), "getBOM");
             assertTrue(in.hasBOM(), "hasBOM()");
             assertTrue(in.hasBOM(ByteOrderMark.UTF_8), "hasBOM(UTF-8)");
@@ -284,7 +284,7 @@ public class BOMInputStreamTest {
     @Test
     public void testGetBOMFirstThenReadInclude() throws Exception {
         final byte[] data = { 'A', 'B', 'C' };
-        try (BOMInputStream in = new BOMInputStream(createUtf8DataStream(data, true), true)) {
+        try (BOMInputStream in = BOMInputStream.builder().setInputStream(createUtf8Input(data, true)).setInclude(true).get()) {
             assertTrue(in.hasBOM(), "hasBOM()");
             assertTrue(in.hasBOM(ByteOrderMark.UTF_8), "hasBOM(UTF-8)");
             assertEquals(ByteOrderMark.UTF_8, in.getBOM(), "getBOM");
@@ -301,7 +301,7 @@ public class BOMInputStreamTest {
     @Test
     public void testLargeBufferWithBOM() throws Exception {
         final byte[] data = { 'A', 'B', 'C' };
-        try (InputStream in = new BOMInputStream(createUtf8DataStream(data, true))) {
+        try (InputStream in = BOMInputStream.builder().setInputStream(createUtf8Input(data, true)).get()) {
             final byte[] buf = new byte[1024];
             assertData(data, buf, in.read(buf));
         }
@@ -310,7 +310,7 @@ public class BOMInputStreamTest {
     @Test
     public void testLargeBufferWithoutBOM() throws Exception {
         final byte[] data = { 'A', 'B', 'C' };
-        try (InputStream in = new BOMInputStream(createUtf8DataStream(data, false))) {
+        try (InputStream in = BOMInputStream.builder().setInputStream(createUtf8Input(data, false)).get()) {
             final byte[] buf = new byte[1024];
             assertData(data, buf, in.read(buf));
         }
@@ -319,7 +319,7 @@ public class BOMInputStreamTest {
     @Test
     public void testLeadingNonBOMBufferedRead() throws Exception {
         final byte[] data = { (byte) 0xEF, (byte) 0xAB, (byte) 0xCD };
-        try (InputStream in = new BOMInputStream(createUtf8DataStream(data, false))) {
+        try (InputStream in = BOMInputStream.builder().setInputStream(createUtf8Input(data, false)).get()) {
             final byte[] buf = new byte[1024];
             assertData(data, buf, in.read(buf));
         }
@@ -328,7 +328,7 @@ public class BOMInputStreamTest {
     @Test
     public void testLeadingNonBOMSingleRead() throws Exception {
         final byte[] data = { (byte) 0xEF, (byte) 0xAB, (byte) 0xCD };
-        try (InputStream in = new BOMInputStream(createUtf8DataStream(data, false))) {
+        try (InputStream in = BOMInputStream.builder().setInputStream(createUtf8Input(data, false)).get()) {
             assertEquals(0xEF, in.read());
             assertEquals(0xAB, in.read());
             assertEquals(0xCD, in.read());
@@ -339,7 +339,7 @@ public class BOMInputStreamTest {
     @Test
     public void testMarkResetAfterReadWithBOM() throws Exception {
         final byte[] data = { 'A', 'B', 'C', 'D' };
-        try (InputStream in = new BOMInputStream(createUtf8DataStream(data, true))) {
+        try (InputStream in = BOMInputStream.builder().setInputStream(createUtf8Input(data, true)).get()) {
             assertTrue(in.markSupported());
 
             in.read();
@@ -355,7 +355,7 @@ public class BOMInputStreamTest {
     @Test
     public void testMarkResetAfterReadWithoutBOM() throws Exception {
         final byte[] data = { 'A', 'B', 'C', 'D' };
-        try (InputStream in = new BOMInputStream(createUtf8DataStream(data, false))) {
+        try (InputStream in = BOMInputStream.builder().setInputStream(createUtf8Input(data, false)).get()) {
             assertTrue(in.markSupported());
 
             in.read();
@@ -371,7 +371,7 @@ public class BOMInputStreamTest {
     @Test
     public void testMarkResetBeforeReadWithBOM() throws Exception {
         final byte[] data = { 'A', 'B', 'C', 'D' };
-        try (InputStream in = new BOMInputStream(createUtf8DataStream(data, true))) {
+        try (InputStream in = BOMInputStream.builder().setInputStream(createUtf8Input(data, true)).get()) {
             assertTrue(in.markSupported());
 
             in.mark(10);
@@ -386,7 +386,7 @@ public class BOMInputStreamTest {
     @Test
     public void testMarkResetBeforeReadWithoutBOM() throws Exception {
         final byte[] data = { 'A', 'B', 'C', 'D' };
-        try (InputStream in = new BOMInputStream(createUtf8DataStream(data, false))) {
+        try (InputStream in = BOMInputStream.builder().setInputStream(createUtf8Input(data, false)).get()) {
             assertTrue(in.markSupported());
 
             in.mark(10);
@@ -401,14 +401,27 @@ public class BOMInputStreamTest {
     @Test
     public void testNoBoms() throws Exception {
         final byte[] data = { 'A', 'B', 'C' };
-        assertThrows(IllegalArgumentException.class, () -> new BOMInputStream(createUtf8DataStream(data, true), false, (ByteOrderMark[])null).close());
-        assertThrows(IllegalArgumentException.class, () -> new BOMInputStream(createUtf8DataStream(data, true), false, new ByteOrderMark[0]).close());
+        assertThrows(IllegalArgumentException.class, () -> new BOMInputStream(createUtf8Input(data, true), false, (ByteOrderMark[]) null).close());
+        assertThrows(IllegalArgumentException.class, () -> new BOMInputStream(createUtf8Input(data, true), false, new ByteOrderMark[0]).close());
+        //
+        assertThrows(IllegalArgumentException.class, () -> BOMInputStream.builder()
+                .setInputStream(createUtf8Input(data, true))
+                .setInclude(true)
+                .setByteOrderMarks((ByteOrderMark[]) null)
+                .get()
+                .close());
+        assertThrows(IllegalArgumentException.class, () -> BOMInputStream.builder()
+                .setInputStream(createUtf8Input(data, true))
+                .setInclude(true)
+                .setByteOrderMarks(new ByteOrderMark[0])
+                .get()
+                .close());
     }
 
     @Test
     public void testReadEmpty() throws Exception {
         final byte[] data = {};
-        try (BOMInputStream in = new BOMInputStream(createUtf8DataStream(data, false))) {
+        try (BOMInputStream in = BOMInputStream.builder().setInputStream(createUtf8Input(data, false)).get()) {
             assertEquals(-1, in.read());
             assertFalse(in.hasBOM(), "hasBOM()");
             assertFalse(in.hasBOM(ByteOrderMark.UTF_8), "hasBOM(UTF-8)");
@@ -419,7 +432,7 @@ public class BOMInputStreamTest {
     @Test
     public void testReadSmall() throws Exception {
         final byte[] data = { 'A', 'B' };
-        try (BOMInputStream in = new BOMInputStream(createUtf8DataStream(data, false))) {
+        try (BOMInputStream in = BOMInputStream.builder().setInputStream(createUtf8Input(data, false)).get()) {
             assertEquals('A', in.read());
             assertEquals('B', in.read());
             assertEquals(-1, in.read());
@@ -442,7 +455,7 @@ public class BOMInputStreamTest {
     @Test
     public void testReadWithBOMInclude() throws Exception {
         final byte[] data = { 'A', 'B', 'C' };
-        try (BOMInputStream in = new BOMInputStream(createUtf8DataStream(data, true), true)) {
+        try (BOMInputStream in = new BOMInputStream(createUtf8Input(data, true), true)) {
             assertEquals(0xEF, in.read());
             assertEquals(0xBB, in.read());
             assertEquals(0xBF, in.read());
@@ -547,7 +560,7 @@ public class BOMInputStreamTest {
     @Test
     public void testReadWithBOMUtf8() throws Exception {
         final byte[] data = "ABC".getBytes(StandardCharsets.UTF_8);
-        try (BOMInputStream in = new BOMInputStream(createUtf8DataStream(data, true), ByteOrderMark.UTF_8)) {
+        try (BOMInputStream in = new BOMInputStream(createUtf8Input(data, true), ByteOrderMark.UTF_8)) {
             assertEquals('A', in.read());
             assertEquals('B', in.read());
             assertEquals('C', in.read());
@@ -562,7 +575,7 @@ public class BOMInputStreamTest {
     @Test
     public void testReadWithMultipleBOM() throws Exception {
         final byte[] data = { 'A', 'B', 'C' };
-        try (BOMInputStream in = new BOMInputStream(createUtf8DataStream(data, true), ByteOrderMark.UTF_16BE,
+        try (BOMInputStream in = new BOMInputStream(createUtf8Input(data, true), ByteOrderMark.UTF_16BE,
                 ByteOrderMark.UTF_8)) {
             assertEquals('A', in.read());
             assertEquals('B', in.read());
@@ -578,7 +591,7 @@ public class BOMInputStreamTest {
     @Test
     public void testReadWithoutBOM() throws Exception {
         final byte[] data = { 'A', 'B', 'C' };
-        try (BOMInputStream in = new BOMInputStream(createUtf8DataStream(data, false))) {
+        try (BOMInputStream in = BOMInputStream.builder().setInputStream(createUtf8Input(data, false)).get()) {
             assertEquals('A', in.read());
             assertEquals('B', in.read());
             assertEquals('C', in.read());
@@ -664,10 +677,10 @@ public class BOMInputStreamTest {
     @Test
     public void testReadXmlWithBOMUtf8() throws Exception {
         final byte[] data = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><X/>".getBytes(StandardCharsets.UTF_8);
-        try (BOMInputStream in = new BOMInputStream(createUtf8DataStream(data, true))) {
+        try (BOMInputStream in = BOMInputStream.builder().setInputStream(createUtf8Input(data, true)).get()) {
             parseXml(in);
         }
-        parseXml(createUtf8DataStream(data, true));
+        parseXml(createUtf8Input(data, true));
     }
 
 
@@ -675,7 +688,7 @@ public class BOMInputStreamTest {
     public void testReadXmlWithoutBOMUtf32Be() throws Exception {
         assumeTrue(jvmAndSaxBothSupportCharset("UTF_32BE"), "JVM and SAX need to support UTF_32BE for this");
         final byte[] data = "<?xml version=\"1.0\" encoding=\"UTF_32BE\"?><X/>".getBytes("UTF_32BE");
-        try (BOMInputStream in = new BOMInputStream(createUtf32BeDataStream(data, false))) {
+        try (BOMInputStream in = BOMInputStream.builder().setInputStream(createUtf8Input(data, false)).get()) {
             parseXml(in);
         }
         parseXml(createUtf32BeDataStream(data, false));
@@ -685,7 +698,7 @@ public class BOMInputStreamTest {
     public void testReadXmlWithoutBOMUtf32Le() throws Exception {
         assumeTrue(jvmAndSaxBothSupportCharset("UTF_32LE"), "JVM and SAX need to support UTF_32LE for this");
         final byte[] data = "<?xml version=\"1.0\" encoding=\"UTF-32LE\"?><X/>".getBytes("UTF_32LE");
-        try (BOMInputStream in = new BOMInputStream(createUtf32LeDataStream(data, false))) {
+        try (BOMInputStream in = BOMInputStream.builder().setInputStream(createUtf8Input(data, false)).get()) {
             parseXml(in);
         }
         parseXml(createUtf32BeDataStream(data, false));
@@ -694,7 +707,7 @@ public class BOMInputStreamTest {
     @Test
     public void testSkipWithBOM() throws Exception {
         final byte[] data = { 'A', 'B', 'C', 'D' };
-        try (InputStream in = new BOMInputStream(createUtf8DataStream(data, true))) {
+        try (InputStream in = BOMInputStream.builder().setInputStream(createUtf8Input(data, true)).get()) {
             in.skip(2L);
             assertEquals('C', in.read());
         }
@@ -703,7 +716,7 @@ public class BOMInputStreamTest {
     @Test
     public void testSkipWithoutBOM() throws Exception {
         final byte[] data = { 'A', 'B', 'C', 'D' };
-        try (InputStream in = new BOMInputStream(createUtf8DataStream(data, false))) {
+        try (InputStream in = BOMInputStream.builder().setInputStream(createUtf8Input(data, false)).get()) {
             in.skip(2L);
             assertEquals('C', in.read());
         }
@@ -712,7 +725,7 @@ public class BOMInputStreamTest {
     @Test
     public void testSmallBufferWithBOM() throws Exception {
         final byte[] data = { 'A', 'B', 'C' };
-        try (InputStream in = new BOMInputStream(createUtf8DataStream(data, true))) {
+        try (InputStream in = BOMInputStream.builder().setInputStream(createUtf8Input(data, true)).get()) {
             final byte[] buf = new byte[1024];
             assertData(new byte[] { 'A', 'B' }, buf, in.read(buf, 0, 2));
             assertData(new byte[] { 'C' }, buf, in.read(buf, 0, 2));
@@ -722,7 +735,7 @@ public class BOMInputStreamTest {
     @Test
     public void testSmallBufferWithoutBOM() throws Exception {
         final byte[] data = { 'A', 'B', 'C' };
-        try (InputStream in = new BOMInputStream(createUtf8DataStream(data, false))) {
+        try (InputStream in = BOMInputStream.builder().setInputStream(createUtf8Input(data, false)).get()) {
             final byte[] buf = new byte[1024];
             assertData(new byte[] { 'A', 'B' }, buf, in.read(buf, 0, 2));
             assertData(new byte[] { 'C' }, buf, in.read(buf, 0, 2));
@@ -732,7 +745,7 @@ public class BOMInputStreamTest {
     @Test
     // make sure that our support code works as expected
     public void testSupportCode() throws Exception {
-        try (InputStream in = createUtf8DataStream(new byte[] { 'A', 'B' }, true)) {
+        try (InputStream in = createUtf8Input(new byte[] { 'A', 'B' }, true)) {
             final byte[] buf = new byte[1024];
             final int len = in.read(buf);
             assertEquals(5, len);
diff --git a/src/test/java/org/apache/commons/io/input/BufferedFileChannelInputStreamTest.java b/src/test/java/org/apache/commons/io/input/BufferedFileChannelInputStreamTest.java
index 90ab736f..758d96c3 100644
--- a/src/test/java/org/apache/commons/io/input/BufferedFileChannelInputStreamTest.java
+++ b/src/test/java/org/apache/commons/io/input/BufferedFileChannelInputStreamTest.java
@@ -24,8 +24,8 @@ import org.junit.jupiter.api.BeforeEach;
 /**
  * Tests functionality of {@link BufferedFileChannelInputStream}.
  *
- * This class was ported and adapted from Apache Spark commit 933dc6cb7b3de1d8ccaf73d124d6eb95b947ed19 where it was
- * called {@code BufferedFileChannelInputStreamSuite}.
+ * This class was ported and adapted from Apache Spark commit 933dc6cb7b3de1d8ccaf73d124d6eb95b947ed19 where it was called
+ * {@code BufferedFileChannelInputStreamSuite}.
  */
 public class BufferedFileChannelInputStreamTest extends AbstractInputStreamTest {
 
@@ -37,7 +37,10 @@ public class BufferedFileChannelInputStreamTest extends AbstractInputStreamTest
         // @formatter:off
         inputStreams = new InputStream[] {
             new BufferedFileChannelInputStream(inputFile), // default
-            new BufferedFileChannelInputStream(inputFile, 123) // small, unaligned buffer
+            new BufferedFileChannelInputStream(inputFile, 123), // small, unaligned buffer size
+            BufferedFileChannelInputStream.builder().setPath(inputFile).get(), // default
+            BufferedFileChannelInputStream.builder().setPath(inputFile).setBufferSize(123).get(), // small, unaligned buffer size
+            BufferedFileChannelInputStream.builder().setURI(inputFile.toUri()).setBufferSize(1024).get() // URI and buffer size
         };
         //@formatter:on
     }
diff --git a/src/test/java/org/apache/commons/io/input/MemoryMappedFileInputStreamTest.java b/src/test/java/org/apache/commons/io/input/MemoryMappedFileInputStreamTest.java
index dae929ff..9eabf6a3 100644
--- a/src/test/java/org/apache/commons/io/input/MemoryMappedFileInputStreamTest.java
+++ b/src/test/java/org/apache/commons/io/input/MemoryMappedFileInputStreamTest.java
@@ -231,7 +231,50 @@ public class MemoryMappedFileInputStreamTest {
     }
 
     @Test
-    void testSmallFile() throws IOException {
+    void testSkipToEndOfCurrentBufferBuilder() throws IOException {
+        // setup
+        final Path file = createTestFile(100);
+        final byte[] expectedData = Files.readAllBytes(file);
+
+        // test
+        try (MemoryMappedFileInputStream inputStream = MemoryMappedFileInputStream.builder().setPath(file).setBufferSize(10).get()) {
+            assertEquals(10, inputStream.getBufferSize());
+            IOUtils.toByteArray(inputStream, 5);
+            assertEquals(5, inputStream.skip(5));
+            final byte[] data = IOUtils.toByteArray(inputStream);
+            // verify
+            assertArrayEquals(Arrays.copyOfRange(expectedData, 10, expectedData.length), data);
+        }
+    }
+
+    @Test
+    void testSmallFileBuilder() throws IOException {
+        // setup
+        final Path file = createTestFile(100);
+        final byte[] expectedData = Files.readAllBytes(file);
+
+        // test
+        try (InputStream inputStream = MemoryMappedFileInputStream.builder().setFile(file.toFile()).get()) {
+            // verify
+            assertArrayEquals(expectedData, IOUtils.toByteArray(inputStream));
+        }
+    }
+
+    @Test
+    void testSmallPathBuilder() throws IOException {
+        // setup
+        final Path file = createTestFile(100);
+        final byte[] expectedData = Files.readAllBytes(file);
+
+        // test
+        try (InputStream inputStream = MemoryMappedFileInputStream.builder().setPath(file).get()) {
+            // verify
+            assertArrayEquals(expectedData, IOUtils.toByteArray(inputStream));
+        }
+    }
+
+    @Test
+    void testSmallPath() throws IOException {
         // setup
         final Path file = createTestFile(100);
         final byte[] expectedData = Files.readAllBytes(file);
diff --git a/src/test/java/org/apache/commons/io/input/MessageDigestCalculatingInputStreamTest.java b/src/test/java/org/apache/commons/io/input/MessageDigestCalculatingInputStreamTest.java
index e0e611f2..6effb6a4 100644
--- a/src/test/java/org/apache/commons/io/input/MessageDigestCalculatingInputStreamTest.java
+++ b/src/test/java/org/apache/commons/io/input/MessageDigestCalculatingInputStreamTest.java
@@ -42,8 +42,12 @@ public class MessageDigestCalculatingInputStreamTest {
             final byte[] buffer = generateRandomByteStream(i);
             final MessageDigest messageDigest = MessageDigestCalculatingInputStream.getDefaultMessageDigest();
             final byte[] expect = messageDigest.digest(buffer);
-            try (MessageDigestCalculatingInputStream messageDigestInputStream = new MessageDigestCalculatingInputStream(
-                new ByteArrayInputStream(buffer))) {
+            try (MessageDigestCalculatingInputStream messageDigestInputStream = new MessageDigestCalculatingInputStream(new ByteArrayInputStream(buffer))) {
+                messageDigestInputStream.consume();
+                assertArrayEquals(expect, messageDigestInputStream.getMessageDigest().digest());
+            }
+            try (MessageDigestCalculatingInputStream messageDigestInputStream = MessageDigestCalculatingInputStream.builder()
+                    .setInputStream(new ByteArrayInputStream(buffer)).get()) {
                 messageDigestInputStream.consume();
                 assertArrayEquals(expect, messageDigestInputStream.getMessageDigest().digest());
             }
diff --git a/src/test/java/org/apache/commons/io/input/RandomAccessFileInputStreamTest.java b/src/test/java/org/apache/commons/io/input/RandomAccessFileInputStreamTest.java
index 45e1d019..d2f7605d 100644
--- a/src/test/java/org/apache/commons/io/input/RandomAccessFileInputStreamTest.java
+++ b/src/test/java/org/apache/commons/io/input/RandomAccessFileInputStreamTest.java
@@ -20,13 +20,16 @@ package org.apache.commons.io.input;
 import static org.junit.jupiter.api.Assertions.assertArrayEquals;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
+import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.RandomAccessFile;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.Paths;
 
 import org.apache.commons.io.RandomAccessFileMode;
 import org.junit.jupiter.api.Test;
@@ -56,38 +59,80 @@ public class RandomAccessFileInputStreamTest {
         }
     }
 
+    @SuppressWarnings("resource") // instance variable access
     @Test
     public void testConstructorCloseOnCloseFalse() throws IOException {
         try (RandomAccessFile file = createRandomAccessFile()) {
             try (RandomAccessFileInputStream inputStream = new RandomAccessFileInputStream(file, false)) {
                 assertFalse(inputStream.isCloseOnClose());
+                assertNotEquals(-1, inputStream.getRandomAccessFile().read());
             }
             file.read();
         }
     }
 
+    @SuppressWarnings("resource") // instance variable access
     @Test
     public void testConstructorCloseOnCloseTrue() throws IOException {
         try (RandomAccessFile file = createRandomAccessFile()) {
             try (RandomAccessFileInputStream inputStream = new RandomAccessFileInputStream(file, true)) {
                 assertTrue(inputStream.isCloseOnClose());
+                assertNotEquals(-1, inputStream.getRandomAccessFile().read());
             }
             assertThrows(IOException.class, file::read);
         }
     }
 
+    @SuppressWarnings("resource") // instance variable access
     @Test
-    public void testConstructorFile() throws IOException {
+    public void testConstructorRandomAccessFile() throws IOException {
         try (RandomAccessFile file = createRandomAccessFile()) {
             try (RandomAccessFileInputStream inputStream = new RandomAccessFileInputStream(file)) {
                 assertFalse(inputStream.isCloseOnClose());
+                assertNotEquals(-1, inputStream.getRandomAccessFile().read());
             }
             file.read();
         }
     }
 
+    @SuppressWarnings("resource") // instance variable access
     @Test
-    public void testConstructorFileNull() {
+    public void testBuilderRandomAccessFile() throws IOException {
+        try (RandomAccessFile file = createRandomAccessFile()) {
+            try (RandomAccessFileInputStream inputStream = RandomAccessFileInputStream.builder().setRandomAccessFile(file).get()) {
+                assertFalse(inputStream.isCloseOnClose());
+                assertNotEquals(-1, inputStream.getRandomAccessFile().read());
+            }
+            file.read();
+        }
+    }
+
+    @SuppressWarnings("resource") // instance variable access
+    @Test
+    public void testBuilderPath() throws IOException {
+        try (RandomAccessFile file = createRandomAccessFile()) {
+            try (RandomAccessFileInputStream inputStream = RandomAccessFileInputStream.builder().setPath(Paths.get(DATA_FILE)).get()) {
+                assertFalse(inputStream.isCloseOnClose());
+                assertNotEquals(-1, inputStream.getRandomAccessFile().read());
+            }
+            file.read();
+        }
+    }
+
+    @SuppressWarnings("resource") // instance variable access
+    @Test
+    public void testBuilderFile() throws IOException {
+        try (RandomAccessFile file = createRandomAccessFile()) {
+            try (RandomAccessFileInputStream inputStream = RandomAccessFileInputStream.builder().setFile(new File(DATA_FILE)).get()) {
+                assertFalse(inputStream.isCloseOnClose());
+                assertNotEquals(-1, inputStream.getRandomAccessFile().read());
+            }
+            file.read();
+        }
+    }
+
+    @Test
+    public void testConstructorRandomAccessFileNull() {
         assertThrows(NullPointerException.class, () -> new RandomAccessFileInputStream(null));
     }
 
diff --git a/src/test/java/org/apache/commons/io/input/ReadAheadInputStreamTest.java b/src/test/java/org/apache/commons/io/input/ReadAheadInputStreamTest.java
index cf1c643d..125a8681 100644
--- a/src/test/java/org/apache/commons/io/input/ReadAheadInputStreamTest.java
+++ b/src/test/java/org/apache/commons/io/input/ReadAheadInputStreamTest.java
@@ -24,8 +24,7 @@ import org.junit.jupiter.api.BeforeEach;
 /**
  * Tests {@link ReadAheadInputStream}.
  *
- * This class was ported and adapted from Apache Spark commit 933dc6cb7b3de1d8ccaf73d124d6eb95b947ed19 where it was
- * called {@code ReadAheadInputStreamSuite}.
+ * This class was ported and adapted from Apache Spark commit 933dc6cb7b3de1d8ccaf73d124d6eb95b947ed19 where it was called {@code ReadAheadInputStreamSuite}.
  */
 public class ReadAheadInputStreamTest extends AbstractInputStreamTest {
 
@@ -35,15 +34,26 @@ public class ReadAheadInputStreamTest extends AbstractInputStreamTest {
     public void setUp() throws IOException {
         super.setUp();
         inputStreams = new InputStream[] {
-            // Tests equal and aligned buffers of wrapped an outer stream.
-            new ReadAheadInputStream(new BufferedFileChannelInputStream(inputFile, 8 * 1024), 8 * 1024),
-            // Tests aligned buffers, wrapped bigger than outer.
-            new ReadAheadInputStream(new BufferedFileChannelInputStream(inputFile, 3 * 1024), 2 * 1024),
-            // Tests aligned buffers, wrapped smaller than outer.
-            new ReadAheadInputStream(new BufferedFileChannelInputStream(inputFile, 2 * 1024), 3 * 1024),
-            // Tests unaligned buffers, wrapped bigger than outer.
-            new ReadAheadInputStream(new BufferedFileChannelInputStream(inputFile, 321), 123),
-            // Tests unaligned buffers, wrapped smaller than outer.
-            new ReadAheadInputStream(new BufferedFileChannelInputStream(inputFile, 123), 321)};
+                // Tests equal and aligned buffers of wrapped an outer stream.
+                new ReadAheadInputStream(new BufferedFileChannelInputStream(inputFile, 8 * 1024), 8 * 1024),
+                // Tests aligned buffers, wrapped bigger than outer.
+                new ReadAheadInputStream(new BufferedFileChannelInputStream(inputFile, 3 * 1024), 2 * 1024),
+                // Tests aligned buffers, wrapped smaller than outer.
+                new ReadAheadInputStream(new BufferedFileChannelInputStream(inputFile, 2 * 1024), 3 * 1024),
+                // Tests unaligned buffers, wrapped bigger than outer.
+                new ReadAheadInputStream(new BufferedFileChannelInputStream(inputFile, 321), 123),
+                // Tests unaligned buffers, wrapped smaller than outer.
+                new ReadAheadInputStream(new BufferedFileChannelInputStream(inputFile, 123), 321),
+                //
+                // Tests equal and aligned buffers of wrapped an outer stream.
+                ReadAheadInputStream.builder().setInputStream(new BufferedFileChannelInputStream(inputFile, 8 * 1024)).setBufferSize(8 * 1024).get(),
+                // Tests aligned buffers, wrapped bigger than outer.
+                ReadAheadInputStream.builder().setInputStream(new BufferedFileChannelInputStream(inputFile, 3 * 1024)).setBufferSize(2 * 1024).get(),
+                // Tests aligned buffers, wrapped smaller than outer.
+                ReadAheadInputStream.builder().setInputStream(new BufferedFileChannelInputStream(inputFile, 2 * 1024)).setBufferSize(3 * 1024).get(),
+                // Tests unaligned buffers, wrapped bigger than outer.
+                ReadAheadInputStream.builder().setInputStream(new BufferedFileChannelInputStream(inputFile, 321)).setBufferSize(123).get(),
+                // Tests unaligned buffers, wrapped smaller than outer.
+                ReadAheadInputStream.builder().setInputStream(new BufferedFileChannelInputStream(inputFile, 123)).setBufferSize(321).get() };
     }
 }
diff --git a/src/test/java/org/apache/commons/io/input/ReaderInputStreamTest.java b/src/test/java/org/apache/commons/io/input/ReaderInputStreamTest.java
index b305ccf0..345c519e 100644
--- a/src/test/java/org/apache/commons/io/input/ReaderInputStreamTest.java
+++ b/src/test/java/org/apache/commons/io/input/ReaderInputStreamTest.java
@@ -16,6 +16,7 @@
  */
 package org.apache.commons.io.input;
 
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -71,9 +72,21 @@ public class ReaderInputStreamTest {
     @Timeout(value = 500, unit = TimeUnit.MILLISECONDS)
     public void testBufferSmallest() throws IOException {
         final Charset charset = StandardCharsets.UTF_8;
-        try (InputStream in = new ReaderInputStream(new StringReader("\uD800"), charset, (int) ReaderInputStream.minBufferSize(charset.newEncoder()))) {
+        // @formatter:off
+        try (InputStream in = new ReaderInputStream(
+                new StringReader("\uD800"),
+                charset, (int)
+                ReaderInputStream.minBufferSize(charset.newEncoder()))) {
+            in.read();
+        }
+        try (InputStream in = ReaderInputStream.builder()
+                .setReader(new StringReader("\uD800"))
+                .setCharset(charset)
+                .setBufferSize((int) ReaderInputStream.minBufferSize(charset.newEncoder()))
+                .get()) {
             in.read();
         }
+        // @formatter:on
     }
 
     @Test
@@ -89,8 +102,10 @@ public class ReaderInputStreamTest {
         final Charset charset = Charset.forName(charsetName);
         final byte[] expected = data.getBytes(charset);
         try (InputStream in = new ReaderInputStream(new StringReader(data), charset)) {
-            final byte[] actual = IOUtils.toByteArray(in);
-            assertEquals(Arrays.toString(expected), Arrays.toString(actual));
+            assertEquals(Arrays.toString(expected), Arrays.toString(IOUtils.toByteArray(in)));
+        }
+        try (InputStream in = ReaderInputStream.builder().setReader(new StringReader(data)).setCharset(charset).get()) {
+            assertEquals(Arrays.toString(expected), Arrays.toString(IOUtils.toByteArray(in)));
         }
     }
 
@@ -100,7 +115,7 @@ public class ReaderInputStreamTest {
     @Test
     public void testCharsetMismatchInfiniteLoop() throws IOException {
         // Input is UTF-8 bytes: 0xE0 0xB2 0xA0
-        final char[] inputChars = {(char) 0xE0, (char) 0xB2, (char) 0xA0};
+        final char[] inputChars = { (char) 0xE0, (char) 0xB2, (char) 0xA0 };
         // Charset charset = Charset.forName("UTF-8"); // works
         final Charset charset = StandardCharsets.US_ASCII; // infinite loop
         try (ReaderInputStream stream = new ReaderInputStream(new CharArrayReader(inputChars), charset)) {
@@ -108,6 +123,24 @@ public class ReaderInputStreamTest {
         }
     }
 
+    @Test
+    @Timeout(value = 500, unit = TimeUnit.MILLISECONDS)
+    public void testCodingError() throws IOException {
+        // Encoder which throws on malformed or unmappable input
+        CharsetEncoder encoder = StandardCharsets.UTF_8.newEncoder();
+        try (final ReaderInputStream in = new ReaderInputStream(new StringReader("\uD800"), encoder)) {
+            // Does not throws an exception because the input is an underflow and not an error
+            assertDoesNotThrow(() -> in.read());
+            // assertThrows(IllegalStateException.class, () -> in.read());
+        }
+        encoder = StandardCharsets.UTF_8.newEncoder();
+        try (final ReaderInputStream in = ReaderInputStream.builder().setReader(new StringReader("\uD800")).setCharsetEncoder(encoder).get()) {
+            // TODO WIP
+            assertDoesNotThrow(() -> in.read());
+            // assertThrows(IllegalStateException.class, () -> in.read());
+        }
+    }
+
     /**
      * Tests IO-717 to avoid infinite loops.
      *
@@ -118,7 +151,11 @@ public class ReaderInputStreamTest {
     public void testCodingErrorAction() throws IOException {
         final Charset charset = StandardCharsets.UTF_8;
         final CharsetEncoder encoder = charset.newEncoder().onMalformedInput(CodingErrorAction.REPORT);
-        try (InputStream in = new ReaderInputStream(new StringReader("\uD800aa"), encoder, (int) ReaderInputStream.minBufferSize(charset.newEncoder()))) {
+        try (InputStream in = new ReaderInputStream(new StringReader("\uD800aa"), encoder, (int) ReaderInputStream.minBufferSize(encoder))) {
+            assertThrows(CharacterCodingException.class, in::read);
+        }
+        try (InputStream in = ReaderInputStream.builder().setReader(new StringReader("\uD800aa")).setCharsetEncoder(encoder)
+                .setBufferSize((int) ReaderInputStream.minBufferSize(charset.newEncoder())).get()) {
             assertThrows(CharacterCodingException.class, in::read);
         }
     }
@@ -149,8 +186,13 @@ public class ReaderInputStreamTest {
     @Timeout(value = 500, unit = TimeUnit.MILLISECONDS)
     public void testConstructNullCharsetNameEncoder() throws IOException {
         final Charset charset = Charset.defaultCharset();
-        final String encoder = null;
-        try (ReaderInputStream in = new ReaderInputStream(new StringReader("ABC"), encoder, (int) ReaderInputStream.minBufferSize(charset.newEncoder()))) {
+        final String charsetName = null;
+        try (ReaderInputStream in = new ReaderInputStream(new StringReader("ABC"), charsetName, (int) ReaderInputStream.minBufferSize(charset.newEncoder()))) {
+            IOUtils.toByteArray(in);
+            assertEquals(Charset.defaultCharset(), in.getCharsetEncoder().charset());
+        }
+        try (ReaderInputStream in = ReaderInputStream.builder().setReader(new StringReader("ABC")).setCharset(charsetName)
+                .setBufferSize((int) ReaderInputStream.minBufferSize(charset.newEncoder())).get()) {
             IOUtils.toByteArray(in);
             assertEquals(Charset.defaultCharset(), in.getCharsetEncoder().charset());
         }
@@ -171,14 +213,21 @@ public class ReaderInputStreamTest {
     public void testReadZero() throws Exception {
         final String inStr = "test";
         try (ReaderInputStream inputStream = new ReaderInputStream(new StringReader(inStr))) {
-            final byte[] bytes = new byte[30];
-            assertEquals(0, inputStream.read(bytes, 0, 0));
-            assertEquals(inStr.length(), inputStream.read(bytes, 0, inStr.length() + 1));
-            // Should always return 0 for length == 0
-            assertEquals(0, inputStream.read(bytes, 0, 0));
+            testReadZero(inStr, inputStream);
+        }
+        try (ReaderInputStream inputStream = ReaderInputStream.builder().setReader(new StringReader(inStr)).get()) {
+            testReadZero(inStr, inputStream);
         }
     }
 
+    private void testReadZero(final String inStr, final ReaderInputStream inputStream) throws IOException {
+        final byte[] bytes = new byte[30];
+        assertEquals(0, inputStream.read(bytes, 0, 0));
+        assertEquals(inStr.length(), inputStream.read(bytes, 0, inStr.length() + 1));
+        // Should always return 0 for length == 0
+        assertEquals(0, inputStream.read(bytes, 0, 0));
+    }
+
     @SuppressWarnings("deprecation")
     @Test
     public void testReadZeroEmptyString() throws Exception {
@@ -210,24 +259,31 @@ public class ReaderInputStreamTest {
     private void testWithBufferedRead(final String testString, final String charsetName) throws IOException {
         final byte[] expected = testString.getBytes(charsetName);
         try (ReaderInputStream in = new ReaderInputStream(new StringReader(testString), charsetName)) {
-            final byte[] buffer = new byte[128];
-            int offset = 0;
-            while (true) {
-                int bufferOffset = random.nextInt(64);
-                final int bufferLength = random.nextInt(64);
-                int read = in.read(buffer, bufferOffset, bufferLength);
-                if (read == -1) {
-                    assertEquals(offset, expected.length);
-                    break;
-                }
-                assertTrue(read <= bufferLength);
-                while (read > 0) {
-                    assertTrue(offset < expected.length);
-                    assertEquals(expected[offset], buffer[bufferOffset]);
-                    offset++;
-                    bufferOffset++;
-                    read--;
-                }
+            testWithBufferedRead(expected, in);
+        }
+        try (ReaderInputStream in = ReaderInputStream.builder().setReader(new StringReader(testString)).setCharset(charsetName).get()) {
+            testWithBufferedRead(expected, in);
+        }
+    }
+
+    private void testWithBufferedRead(final byte[] expected, final ReaderInputStream in) throws IOException {
+        final byte[] buffer = new byte[128];
+        int offset = 0;
+        while (true) {
+            int bufferOffset = random.nextInt(64);
+            final int bufferLength = random.nextInt(64);
+            int read = in.read(buffer, bufferOffset, bufferLength);
+            if (read == -1) {
+                assertEquals(offset, expected.length);
+                break;
+            }
+            assertTrue(read <= bufferLength);
+            while (read > 0) {
+                assertTrue(offset < expected.length);
+                assertEquals(expected[offset], buffer[bufferOffset]);
+                offset++;
+                bufferOffset++;
+                read--;
             }
         }
     }
diff --git a/src/test/java/org/apache/commons/io/input/ReversedLinesFileReaderTestParamBlockSize.java b/src/test/java/org/apache/commons/io/input/ReversedLinesFileReaderTestParamBlockSize.java
index aa84f61a..d3023ee1 100644
--- a/src/test/java/org/apache/commons/io/input/ReversedLinesFileReaderTestParamBlockSize.java
+++ b/src/test/java/org/apache/commons/io/input/ReversedLinesFileReaderTestParamBlockSize.java
@@ -107,10 +107,14 @@ public class ReversedLinesFileReaderTestParamBlockSize {
 
     @ParameterizedTest(name = "BlockSize={0}")
     @MethodSource("blockSizes")
-    public void testEmptyFile(final int testParamBlockSize) throws URISyntaxException, IOException {
+    public void testEmptyFile(final int blockSize) throws URISyntaxException, IOException {
         final File testFileEmpty = TestResources.getFile("/test-file-empty.bin");
-        reversedLinesFileReader = new ReversedLinesFileReader(testFileEmpty, testParamBlockSize, UTF_8);
-        assertNull(reversedLinesFileReader.readLine());
+        try (ReversedLinesFileReader reader = new ReversedLinesFileReader(testFileEmpty, blockSize, UTF_8)) {
+            assertNull(reader.readLine());
+        }
+        try (ReversedLinesFileReader reader = ReversedLinesFileReader.builder().setFile(testFileEmpty).setBufferSize(blockSize).setCharset(UTF_8).get()) {
+            assertNull(reader.readLine());
+        }
     }
 
     @Test
diff --git a/src/test/java/org/apache/commons/io/input/ReversedLinesFileReaderTestParamFile.java b/src/test/java/org/apache/commons/io/input/ReversedLinesFileReaderTestParamFile.java
index 7009fe48..8c9d0c5c 100644
--- a/src/test/java/org/apache/commons/io/input/ReversedLinesFileReaderTestParamFile.java
+++ b/src/test/java/org/apache/commons/io/input/ReversedLinesFileReaderTestParamFile.java
@@ -88,7 +88,7 @@ public class ReversedLinesFileReaderTestParamFile {
     @ParameterizedTest(name = "{0}, encoding={1}, blockSize={2}, useNonDefaultFileSystem={3}, isResource={4}")
     @MethodSource
     public void testDataIntegrityWithBufferedReader(final String fileName, final String charsetName, final Integer blockSize,
-        final boolean useNonDefaultFileSystem, final boolean isResource) throws IOException, URISyntaxException {
+            final boolean useNonDefaultFileSystem, final boolean isResource) throws IOException, URISyntaxException {
 
         Path filePath = isResource ? TestResources.getPath(fileName) : Paths.get(fileName);
         FileSystem fileSystem = null;
@@ -100,28 +100,36 @@ public class ReversedLinesFileReaderTestParamFile {
         // We want to test null Charset in the ReversedLinesFileReaderconstructor.
         final Charset charset = charsetName != null ? Charset.forName(charsetName) : null;
         try (ReversedLinesFileReader reversedLinesFileReader = blockSize == null ? new ReversedLinesFileReader(filePath, charset)
-            : new ReversedLinesFileReader(filePath, blockSize, charset)) {
+                : new ReversedLinesFileReader(filePath, blockSize, charset)) {
+            testDataIntegrityWithBufferedReader(filePath, fileSystem, charset, reversedLinesFileReader);
+        }
+        try (ReversedLinesFileReader reversedLinesFileReader = ReversedLinesFileReader.builder().setPath(filePath).setBufferSize(blockSize).setCharset(charset)
+                .get()) {
+            testDataIntegrityWithBufferedReader(filePath, fileSystem, charset, reversedLinesFileReader);
+        }
+    }
 
-            final Stack<String> lineStack = new Stack<>();
-            String line;
+    private void testDataIntegrityWithBufferedReader(Path filePath, FileSystem fileSystem, final Charset charset,
+            ReversedLinesFileReader reversedLinesFileReader) throws IOException {
+        final Stack<String> lineStack = new Stack<>();
+        String line;
 
-            try (BufferedReader bufferedReader = Files.newBufferedReader(filePath, Charsets.toCharset(charset))) {
-                // read all lines in normal order
-                while ((line = bufferedReader.readLine()) != null) {
-                    lineStack.push(line);
-                }
+        try (BufferedReader bufferedReader = Files.newBufferedReader(filePath, Charsets.toCharset(charset))) {
+            // read all lines in normal order
+            while ((line = bufferedReader.readLine()) != null) {
+                lineStack.push(line);
             }
+        }
 
-            // read in reverse order and compare with lines from stack
-            while ((line = reversedLinesFileReader.readLine()) != null) {
-                final String lineFromBufferedReader = lineStack.pop();
-                assertEquals(lineFromBufferedReader, line);
-            }
-            assertEquals(0, lineStack.size(), "Stack should be empty");
+        // read in reverse order and compare with lines from stack
+        while ((line = reversedLinesFileReader.readLine()) != null) {
+            final String lineFromBufferedReader = lineStack.pop();
+            assertEquals(lineFromBufferedReader, line);
+        }
+        assertEquals(0, lineStack.size(), "Stack should be empty");
 
-            if (fileSystem != null) {
-                fileSystem.close();
-            }
+        if (fileSystem != null) {
+            fileSystem.close();
         }
     }
 }
diff --git a/src/test/java/org/apache/commons/io/input/SequenceReaderTest.java b/src/test/java/org/apache/commons/io/input/SequenceReaderTest.java
index 4ce2014e..43efd071 100644
--- a/src/test/java/org/apache/commons/io/input/SequenceReaderTest.java
+++ b/src/test/java/org/apache/commons/io/input/SequenceReaderTest.java
@@ -28,6 +28,7 @@ import java.io.StringReader;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+
 import org.apache.commons.lang3.StringUtils;
 import org.junit.jupiter.api.Test;
 
diff --git a/src/test/java/org/apache/commons/io/input/TailerTest.java b/src/test/java/org/apache/commons/io/input/TailerTest.java
index c7d34fa4..7f1b88f3 100644
--- a/src/test/java/org/apache/commons/io/input/TailerTest.java
+++ b/src/test/java/org/apache/commons/io/input/TailerTest.java
@@ -245,7 +245,7 @@ public class TailerTest {
         final File file = new File(temporaryFolder, "tailer-create-with-delay-and-from-start-with-reopen-and-buffersize-and-charset.txt");
         createFile(file, 0);
         final TestTailerListener listener = new TestTailerListener(1);
-        try (Tailer tailer = new Tailer.Builder(new NonStandardTailable(file), listener).build()) {
+        try (Tailer tailer = Tailer.builder().setTailable(new NonStandardTailable(file)).setTailerListener(listener).get()) {
             assertTrue(tailer.getTailable() instanceof NonStandardTailable);
             validateTailer(listener, file);
         }
@@ -541,6 +541,9 @@ public class TailerTest {
             TestUtils.sleep(idle);
         }
         TestUtils.sleep(delay + idle);
+        if (listener.exception != null) {
+            listener.exception.printStackTrace();
+        }
         assertNull(listener.exception, "Should not generate Exception");
         assertEquals(1, listener.initialized, "Expected init to be called");
         assertTrue(listener.notFound > 0, "fileNotFound should be called");
diff --git a/src/test/java/org/apache/commons/io/input/XmlStreamReaderTest.java b/src/test/java/org/apache/commons/io/input/XmlStreamReaderTest.java
index 312577c7..ebff2f13 100644
--- a/src/test/java/org/apache/commons/io/input/XmlStreamReaderTest.java
+++ b/src/test/java/org/apache/commons/io/input/XmlStreamReaderTest.java
@@ -161,21 +161,36 @@ public class XmlStreamReaderTest {
         return new ByteArrayInputStream(baos.toByteArray());
     }
 
-    public void testAlternateDefaultEncoding(final String cT, final String bomEnc, final String streamEnc, final String prologEnc, final String alternateEnc)
-        throws Exception {
+    public void testAlternateDefaultEncoding(final String contentType, final String bomEnc, final String streamEnc, final String prologEnc,
+            final String alternateEnc) throws Exception {
         try (InputStream is = getXmlInputStream(bomEnc, prologEnc == null ? XML1 : XML3, streamEnc, prologEnc);
-            XmlStreamReader xmlReader = new XmlStreamReader(is, cT, false, alternateEnc)) {
-            assertEquals(xmlReader.getDefaultEncoding(), alternateEnc);
-            if (!streamEnc.equals(UTF_16)) {
-                // we can not assert things here because UTF-8, US-ASCII and
-                // ISO-8859-1 look alike for the chars used for detection
-                // (niallp 2010-10-06 - I re-instated the check below - the tests(6) passed)
-                final String enc = alternateEnc != null ? alternateEnc : streamEnc;
-                assertEquals(xmlReader.getEncoding(), enc);
-            } else {
-                // String enc = (alternateEnc != null) ? alternateEnc : streamEnc;
-                assertEquals(xmlReader.getEncoding().substring(0, streamEnc.length()), streamEnc);
-            }
+                XmlStreamReader xmlReader = new XmlStreamReader(is, contentType, false, alternateEnc)) {
+            testAlternateDefaultEncoding(streamEnc, alternateEnc, xmlReader);
+        }
+        try (InputStream is = getXmlInputStream(bomEnc, prologEnc == null ? XML1 : XML3, streamEnc, prologEnc);
+        // @formatter:off
+            XmlStreamReader xmlReader = XmlStreamReader.builder()
+                    .setInputStream(is)
+                    .setHttpContentType(contentType)
+                    .setLenient(false)
+                    .setCharset(alternateEnc)
+                    .get()) {
+            // @formatter:on
+            testAlternateDefaultEncoding(streamEnc, alternateEnc, xmlReader);
+        }
+    }
+
+    private void testAlternateDefaultEncoding(final String streamEnc, final String alternateEnc, XmlStreamReader xmlReader) {
+        assertEquals(xmlReader.getDefaultEncoding(), alternateEnc);
+        if (!streamEnc.equals(UTF_16)) {
+            // we can not assert things here because UTF-8, US-ASCII and
+            // ISO-8859-1 look alike for the chars used for detection
+            // (niallp 2010-10-06 - I re-instated the check below - the tests(6) passed)
+            final String enc = alternateEnc != null ? alternateEnc : streamEnc;
+            assertEquals(xmlReader.getEncoding(), enc);
+        } else {
+            // String enc = (alternateEnc != null) ? alternateEnc : streamEnc;
+            assertEquals(xmlReader.getEncoding().substring(0, streamEnc.length()), streamEnc);
         }
     }
 
@@ -184,6 +199,9 @@ public class XmlStreamReaderTest {
         try (XmlStreamReader reader = new XmlStreamReader(new File("pom.xml"))) {
             // do nothing
         }
+        try (XmlStreamReader reader = XmlStreamReader.builder().setFile("pom.xml").get()) {
+            // do nothing
+        }
     }
 
     @Test
@@ -193,7 +211,12 @@ public class XmlStreamReaderTest {
 
     @Test
     protected void testConstructorInputStreamInput() throws IOException {
-        try (XmlStreamReader reader = new XmlStreamReader(Files.newInputStream(Paths.get("pom.xml")))) {
+        final Path path = Paths.get("pom.xml");
+        try (XmlStreamReader reader = new XmlStreamReader(Files.newInputStream(path))) {
+            // do nothing
+        }
+        try (@SuppressWarnings("resource")
+        XmlStreamReader reader = XmlStreamReader.builder().setInputStream(Files.newInputStream(path)).get()) {
             // do nothing
         }
     }
@@ -203,10 +226,14 @@ public class XmlStreamReaderTest {
         assertThrows(NullPointerException.class, () -> new XmlStreamReader((InputStream) null));
     }
 
+    @Test
     protected void testConstructorPathInput() throws IOException {
         try (XmlStreamReader reader = new XmlStreamReader(Paths.get("pom.xml"))) {
             // do nothing
         }
+        try (XmlStreamReader reader = XmlStreamReader.builder().setPath("pom.xml").get()) {
+            // do nothing
+        }
     }
 
     @Test
@@ -234,14 +261,24 @@ public class XmlStreamReaderTest {
     }
 
     @Test
-    protected void testConstructorURLInputNull() throws IOException {
+    protected void testConstructorURLInputNull() {
         assertThrows(NullPointerException.class, () -> new XmlStreamReader((URL) null));
     }
 
     @Test
     public void testEncodingAttributeXML() throws Exception {
         try (InputStream is = new ByteArrayInputStream(ENCODING_ATTRIBUTE_XML.getBytes(StandardCharsets.UTF_8));
-            XmlStreamReader xmlReader = new XmlStreamReader(is, "", true)) {
+                XmlStreamReader xmlReader = new XmlStreamReader(is, "", true)) {
+            assertEquals(xmlReader.getEncoding(), UTF_8);
+        }
+        try (InputStream is = new ByteArrayInputStream(ENCODING_ATTRIBUTE_XML.getBytes(StandardCharsets.UTF_8));
+                // @formatter:off
+                XmlStreamReader xmlReader = XmlStreamReader.builder()
+                    .setInputStream(is)
+                    .setHttpContentType("")
+                    .setLenient(true)
+                    .get()) {
+            // @formatter:on
             assertEquals(xmlReader.getEncoding(), UTF_8);
         }
     }
@@ -397,12 +434,13 @@ public class XmlStreamReaderTest {
         }
     }
 
+    @SuppressWarnings("resource")
     protected void testRawBomInvalid(final String bomEnc, final String streamEnc,
         final String prologEnc) throws Exception {
         final InputStream is = getXmlInputStream(bomEnc, XML3, streamEnc, prologEnc);
         XmlStreamReader xmlReader = null;
         try {
-            xmlReader = new XmlStreamReader(is, false);
+            xmlReader = XmlStreamReader.builder().setInputStream(is).setLenient(false).get();
             final String foundEnc = xmlReader.getEncoding();
             fail("Expected IOException for BOM " + bomEnc + ", streamEnc " + streamEnc + " and prologEnc " + prologEnc
                 + ": found " + foundEnc);
diff --git a/src/test/java/org/apache/commons/io/output/DeferredFileOutputStreamTest.java b/src/test/java/org/apache/commons/io/output/DeferredFileOutputStreamTest.java
index 55842052..8b2f27c0 100644
--- a/src/test/java/org/apache/commons/io/output/DeferredFileOutputStreamTest.java
+++ b/src/test/java/org/apache/commons/io/output/DeferredFileOutputStreamTest.java
@@ -41,7 +41,7 @@ import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.MethodSource;
 
 /**
- * Unit tests for the {@code DeferredFileOutputStream} class.
+ * Tests {@code DeferredFileOutputStream}.
  */
 public class DeferredFileOutputStreamTest {
 
@@ -71,7 +71,13 @@ public class DeferredFileOutputStreamTest {
         // Ensure that the test starts from a clean base.
         testFile.delete();
 
-        final DeferredFileOutputStream dfos = new DeferredFileOutputStream(testBytes.length - 5, initialBufferSize, testFile);
+        // @formatter:off
+        final DeferredFileOutputStream dfos = DeferredFileOutputStream.builder()
+                .setThreshold(testBytes.length - 5)
+                .setBufferSize(initialBufferSize)
+                .setOutputFile(testFile)
+                .get();
+        // @formatter:on
         dfos.write(testBytes, 0, testBytes.length);
         dfos.close();
         assertFalse(dfos.isInMemory());
@@ -93,8 +99,13 @@ public class DeferredFileOutputStreamTest {
     public void testAboveThresholdGetInputStream(final int initialBufferSize, final @TempDir Path tempDir) throws IOException {
         final File testFile = tempDir.resolve("testAboveThreshold.dat").toFile();
 
-        final DeferredFileOutputStream dfos = new DeferredFileOutputStream(testBytes.length - 5, initialBufferSize,
-            testFile);
+        // @formatter:off
+        final DeferredFileOutputStream dfos = DeferredFileOutputStream.builder()
+                .setThreshold(testBytes.length - 5)
+                .setBufferSize(initialBufferSize)
+                .setOutputFile(testFile)
+                .get();
+        // @formatter:on
         dfos.write(testBytes, 0, testBytes.length);
         dfos.close();
         assertFalse(dfos.isInMemory());
@@ -113,7 +124,12 @@ public class DeferredFileOutputStreamTest {
     @ParameterizedTest(name = "initialBufferSize = {0}")
     @MethodSource("data")
     public void testAtThreshold(final int initialBufferSize) throws IOException {
-        final DeferredFileOutputStream dfos = new DeferredFileOutputStream(testBytes.length, initialBufferSize, null);
+        // @formatter:off
+        final DeferredFileOutputStream dfos = DeferredFileOutputStream.builder()
+                .setThreshold(testBytes.length)
+                .setBufferSize(initialBufferSize)
+                .get();
+        // @formatter:on
         dfos.write(testBytes, 0, testBytes.length);
         dfos.close();
         assertTrue(dfos.isInMemory());
@@ -130,7 +146,12 @@ public class DeferredFileOutputStreamTest {
     @ParameterizedTest(name = "initialBufferSize = {0}")
     @MethodSource("data")
     public void testBelowThreshold(final int initialBufferSize) throws IOException {
-        final DeferredFileOutputStream dfos = new DeferredFileOutputStream(testBytes.length + 42, initialBufferSize, null);
+        // @formatter:off
+        final DeferredFileOutputStream dfos = DeferredFileOutputStream.builder()
+                .setThreshold(testBytes.length + 42)
+                .setBufferSize(initialBufferSize)
+                .get();
+        // @formatter:on
         dfos.write(testBytes, 0, testBytes.length);
         dfos.close();
         assertTrue(dfos.isInMemory());
@@ -147,8 +168,12 @@ public class DeferredFileOutputStreamTest {
     @ParameterizedTest(name = "initialBufferSize = {0}")
     @MethodSource("data")
     public void testBelowThresholdGetInputStream(final int initialBufferSize) throws IOException {
-        final DeferredFileOutputStream dfos = new DeferredFileOutputStream(testBytes.length + 42, initialBufferSize,
-            null);
+        // @formatter:off
+        final DeferredFileOutputStream dfos = DeferredFileOutputStream.builder()
+                .setThreshold(testBytes.length + 42)
+                .setBufferSize(initialBufferSize)
+                .get();
+        // @formatter:on
         dfos.write(testBytes, 0, testBytes.length);
         dfos.close();
         assertTrue(dfos.isInMemory());
@@ -168,7 +193,15 @@ public class DeferredFileOutputStreamTest {
         final String prefix = "commons-io-test";
         final String suffix = ".out";
         final File tempDir = FileUtils.current();
-        final DeferredFileOutputStream dfos = new DeferredFileOutputStream(testBytes.length - 5, initialBufferSize, prefix, suffix, tempDir);
+        // @formatter:off
+        final DeferredFileOutputStream dfos = DeferredFileOutputStream.builder()
+                .setThreshold(testBytes.length - 5)
+                .setBufferSize(initialBufferSize)
+                .setPrefix(prefix)
+                .setSuffix(suffix)
+                .setDirectory(tempDir)
+                .get();
+        // @formatter:on
         assertNull(dfos.getFile(), "Check file is null-A");
         dfos.write(testBytes, 0, testBytes.length);
         dfos.close();
@@ -197,7 +230,15 @@ public class DeferredFileOutputStreamTest {
         final String prefix = "commons-io-test";
         final String suffix = null;
         final File tempDir = null;
-        final DeferredFileOutputStream dfos = new DeferredFileOutputStream(testBytes.length - 5, initialBufferSize, prefix, suffix, tempDir);
+        // @formatter:off
+        final DeferredFileOutputStream dfos = DeferredFileOutputStream.builder()
+                .setThreshold(testBytes.length - 5)
+                .setBufferSize(initialBufferSize)
+                .setPrefix(prefix)
+                .setSuffix(suffix)
+                .setDirectory(tempDir)
+                .get();
+        // @formatter:on
         assertNull(dfos.getFile(), "Check file is null-A");
         dfos.write(testBytes, 0, testBytes.length);
         dfos.close();
@@ -243,7 +284,7 @@ public class DeferredFileOutputStreamTest {
         final String prefix = null;
         final String suffix = ".out";
         final File tempDir = FileUtils.current();
-        assertThrows(NullPointerException.class, () -> new DeferredFileOutputStream(testBytes.length - 5, prefix, suffix, tempDir).close());
+        assertThrows(NullPointerException.class, () -> new DeferredFileOutputStream(testBytes.length - 5, prefix, suffix, tempDir));
     }
 
     /**
@@ -259,7 +300,13 @@ public class DeferredFileOutputStreamTest {
         // Ensure that the test starts from a clean base.
         testFile.delete();
 
-        final DeferredFileOutputStream dfos = new DeferredFileOutputStream(testBytes.length / 2, initialBufferSize, testFile);
+        // @formatter:off
+        final DeferredFileOutputStream dfos = DeferredFileOutputStream.builder()
+                .setThreshold(testBytes.length /2)
+                .setBufferSize(initialBufferSize)
+                .setOutputFile(testFile)
+                .get();
+        // @formatter:on
         final int chunkSize = testBytes.length / 3;
         dfos.write(testBytes, 0, chunkSize);
         dfos.write(testBytes, chunkSize, chunkSize);
@@ -285,6 +332,33 @@ public class DeferredFileOutputStreamTest {
         // Ensure that the test starts from a clean base.
         testFile.delete();
 
+        final DeferredFileOutputStream dfos = DeferredFileOutputStream.builder().setThreshold(testBytes.length / 2).setOutputFile(testFile).get();
+        dfos.write(testBytes);
+
+        assertTrue(testFile.exists());
+        assertFalse(dfos.isInMemory());
+
+        assertThrows(IOException.class, () -> dfos.writeTo(baos));
+
+        dfos.close();
+        dfos.writeTo(baos);
+        final byte[] copiedBytes = baos.toByteArray();
+        assertArrayEquals(testBytes, copiedBytes);
+        verifyResultFile(testFile);
+        testFile.delete();
+    }
+
+    /**
+     * Test whether writeTo() properly writes large content.
+     */
+    @ParameterizedTest(name = "initialBufferSize = {0}")
+    @MethodSource("data")
+    public void testWriteToLargeCtor(final int initialBufferSize) throws IOException {
+        final File testFile = new File("testWriteToFile.dat");
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream(initialBufferSize);
+        // Ensure that the test starts from a clean base.
+        testFile.delete();
+
         final DeferredFileOutputStream dfos = new DeferredFileOutputStream(testBytes.length / 2, testFile);
         dfos.write(testBytes);
 
diff --git a/src/test/java/org/apache/commons/io/output/FileWriterWithEncodingTest.java b/src/test/java/org/apache/commons/io/output/FileWriterWithEncodingTest.java
index 1ed8e5f4..f06b0a95 100644
--- a/src/test/java/org/apache/commons/io/output/FileWriterWithEncodingTest.java
+++ b/src/test/java/org/apache/commons/io/output/FileWriterWithEncodingTest.java
@@ -59,6 +59,12 @@ public class FileWriterWithEncodingTest {
             }
         });
         assertFalse(file1.exists());
+        assertThrows(IOException.class, () -> {
+            try (Writer writer = FileWriterWithEncoding.builder().setFile(temporaryFolder).setCharset(defaultEncoding).get()) {
+                // empty
+            }
+        });
+        assertFalse(file1.exists());
     }
 
     @Test
@@ -84,6 +90,12 @@ public class FileWriterWithEncodingTest {
         }
 
         assertEquals(4, file1.length());
+
+        try (FileWriterWithEncoding fw1 = FileWriterWithEncoding.builder().setFile(file1).setCharset(defaultEncoding).get()) {
+            fw1.write("ABcd");
+        }
+
+        assertEquals(4, file1.length());
     }
 
     @Test
@@ -118,6 +130,18 @@ public class FileWriterWithEncodingTest {
         }
 
         assertEquals(7, file1.length());
+
+        // @formatter:off
+        try (FileWriterWithEncoding fw1 = FileWriterWithEncoding.builder()
+                .setFile(file1)
+                .setCharset(defaultEncoding)
+                .setAppend(true)
+                .get()) {
+            fw1.write("XyZ");
+        }
+        // @formatter:on
+
+        assertEquals(10, file1.length());
     }
 
     @Test
@@ -125,6 +149,14 @@ public class FileWriterWithEncodingTest {
         try (FileWriterWithEncoding writer = new FileWriterWithEncoding(file2, Charset.defaultCharset())) {
             successfulRun(writer);
         }
+        // @formatter:off
+        try (FileWriterWithEncoding writer = FileWriterWithEncoding.builder()
+                .setFile(file2)
+                .setCharset(Charset.defaultCharset())
+                .get()) {
+            successfulRun(writer);
+        }
+        // @formatter:on
     }
 
     @Test
@@ -132,6 +164,14 @@ public class FileWriterWithEncodingTest {
         try (FileWriterWithEncoding writer = new FileWriterWithEncoding(file2, Charset.defaultCharset().newEncoder())) {
             successfulRun(writer);
         }
+        // @formatter:off
+        try (FileWriterWithEncoding writer = FileWriterWithEncoding.builder()
+                .setFile(file2)
+                .setCharsetEncoder(Charset.defaultCharset().newEncoder())
+                .get()) {
+            successfulRun(writer);
+        }
+        // @formatter:on
     }
 
     @Test
@@ -146,6 +186,12 @@ public class FileWriterWithEncodingTest {
         try (FileWriterWithEncoding writer = new FileWriterWithEncoding(file2.getPath(), (CharsetEncoder) null)) {
             successfulRun(writer);
         }
+        try (FileWriterWithEncoding writer = FileWriterWithEncoding.builder().setFile(file2.getPath()).get()) {
+            successfulRun(writer);
+        }
+        try (FileWriterWithEncoding writer = FileWriterWithEncoding.builder().setFile(file2.getPath()).setCharsetEncoder(null).get()) {
+            successfulRun(writer);
+        }
     }
 
     @Test
@@ -160,6 +206,14 @@ public class FileWriterWithEncodingTest {
         try (FileWriterWithEncoding writer = new FileWriterWithEncoding(file2.getPath(), Charset.defaultCharset())) {
             successfulRun(writer);
         }
+        // @formatter:off
+        try (FileWriterWithEncoding writer = FileWriterWithEncoding.builder()
+                .setFile(file2.getPath())
+                .setCharset(Charset.defaultCharset())
+                .get()) {
+            successfulRun(writer);
+        }
+        // @formatter:on
     }
 
     @Test
@@ -181,6 +235,14 @@ public class FileWriterWithEncodingTest {
         try (FileWriterWithEncoding writer = new FileWriterWithEncoding(file2.getPath(), defaultEncoding)) {
             successfulRun(writer);
         }
+        // @formatter:off
+        try (FileWriterWithEncoding writer = FileWriterWithEncoding.builder()
+                .setFile(file2.getPath())
+                .setCharset(defaultEncoding)
+                .get()) {
+            successfulRun(writer);
+        }
+        // @formatter:on
     }
 
     @BeforeEach
diff --git a/src/test/java/org/apache/commons/io/output/LockableFileWriterTest.java b/src/test/java/org/apache/commons/io/output/LockableFileWriterTest.java
index b17939db..5afef834 100644
--- a/src/test/java/org/apache/commons/io/output/LockableFileWriterTest.java
+++ b/src/test/java/org/apache/commons/io/output/LockableFileWriterTest.java
@@ -17,6 +17,7 @@
 package org.apache.commons.io.output;
 
 import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
@@ -32,7 +33,10 @@ import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.io.TempDir;
 
 /**
+ * Tests {@link LockableFileWriter}.
+ * <p>
  * Tests that files really lock, although no writing is done as the locking is tested only on construction.
+ * </p>
  */
 public class LockableFileWriterTest {
 
@@ -58,23 +62,42 @@ public class LockableFileWriterTest {
     public void testAlternateLockDir() throws IOException {
         // open a valid lockable writer
         try (LockableFileWriter lfw1 = new LockableFileWriter(file, StandardCharsets.UTF_8, true, altLockDir.getAbsolutePath())) {
-            assertTrue(file.exists());
-            assertTrue(altLockFile.exists());
-
-            // try to open a second writer
-            try (LockableFileWriter lfw2 = new LockableFileWriter(file, StandardCharsets.UTF_8, true, altLockDir.getAbsolutePath())) {
-                fail("Somehow able to open a locked file. ");
-            } catch (final IOException ioe) {
-                final String msg = ioe.getMessage();
-                assertTrue(msg.startsWith("Can't write file, lock "), "Exception message does not start correctly. ");
-                assertTrue(file.exists());
-                assertTrue(altLockFile.exists());
-            }
+            testAlternateLockDir(lfw1);
+        }
+        assertTrue(file.exists());
+        assertFalse(altLockFile.exists());
+        //
+        // open a valid lockable writer
+        // @formatter:off
+        try (LockableFileWriter lfw1 = LockableFileWriter.builder()
+                .setFile(file)
+                .setCharset(StandardCharsets.UTF_8)
+                .setAppend(true)
+                .setLockDirectory(altLockDir)
+                .get()) {
+            // @formatter:on
+            testAlternateLockDir(lfw1);
         }
         assertTrue(file.exists());
         assertFalse(altLockFile.exists());
     }
 
+    private void testAlternateLockDir(final LockableFileWriter lfw1) {
+        assertNotNull(lfw1);
+        assertTrue(file.exists());
+        assertTrue(altLockFile.exists());
+
+        // try to open a second writer
+        try (LockableFileWriter lfw2 = new LockableFileWriter(file, StandardCharsets.UTF_8, true, altLockDir.getAbsolutePath())) {
+            fail("Somehow able to open a locked file. ");
+        } catch (final IOException ioe) {
+            final String msg = ioe.getMessage();
+            assertTrue(msg.startsWith("Can't write file, lock "), "Exception message does not start correctly. ");
+            assertTrue(file.exists());
+            assertTrue(altLockFile.exists());
+        }
+    }
+
     @Test
     public void testConstructor_File_directory() {
         assertThrows(IOException.class, () -> new LockableFileWriter(temporaryFolder));
@@ -93,6 +116,13 @@ public class LockableFileWriterTest {
         // again
         assertFalse(file.exists());
         assertFalse(lockFile.exists());
+        //
+        assertThrows(UnsupportedCharsetException.class, () -> LockableFileWriter.builder().setFile(file).setCharset("BAD-ENCODE").get());
+        assertFalse(file.exists());
+        assertFalse(lockFile.exists());
+        // again
+        assertFalse(file.exists());
+        assertFalse(lockFile.exists());
     }
 
     @Test
@@ -103,6 +133,14 @@ public class LockableFileWriterTest {
         // again
         assertFalse(file.exists());
         assertFalse(lockFile.exists());
+        //
+        assertThrows(NullPointerException.class, () -> LockableFileWriter.builder().get());
+        assertFalse(file.exists());
+        assertFalse(lockFile.exists());
+        // again
+        assertFalse(file.exists());
+        assertFalse(lockFile.exists());
+
     }
 
     @Test
diff --git a/src/test/java/org/apache/commons/io/output/UncheckedFilterOutputStreamTest.java b/src/test/java/org/apache/commons/io/output/UncheckedFilterOutputStreamTest.java
index 8f86db77..4eff6581 100644
--- a/src/test/java/org/apache/commons/io/output/UncheckedFilterOutputStreamTest.java
+++ b/src/test/java/org/apache/commons/io/output/UncheckedFilterOutputStreamTest.java
@@ -40,10 +40,10 @@ public class UncheckedFilterOutputStreamTest {
 
     @SuppressWarnings("resource")
     @BeforeEach
-    public void setUp() {
+    public void setUp() throws IOException {
         exception = new IOException("test exception");
         brokenWriter = UncheckedFilterOutputStream.on(new BrokenOutputStream(exception));
-        stringWriter = UncheckedFilterOutputStream.on(new WriterOutputStream(new StringWriter(), Charset.defaultCharset()));
+        stringWriter = UncheckedFilterOutputStream.on(WriterOutputStream.builder().setWriter(new StringWriter()).setCharset(Charset.defaultCharset()).get());
     }
 
     @Test
diff --git a/src/test/java/org/apache/commons/io/output/WriterOutputStreamTest.java b/src/test/java/org/apache/commons/io/output/WriterOutputStreamTest.java
index 497334f4..551ce0e7 100644
--- a/src/test/java/org/apache/commons/io/output/WriterOutputStreamTest.java
+++ b/src/test/java/org/apache/commons/io/output/WriterOutputStreamTest.java
@@ -149,7 +149,7 @@ public class WriterOutputStreamTest {
     private void testWithBufferedWrite(final String testString, final String charsetName) throws IOException {
         final byte[] expected = testString.getBytes(charsetName);
         final StringWriter writer = new StringWriter();
-        try (WriterOutputStream out = new WriterOutputStream(writer, charsetName)) {
+        try (WriterOutputStream out = WriterOutputStream.builder().setWriter(writer).setCharset(charsetName).get()) {
             int offset = 0;
             while (offset < expected.length) {
                 final int length = Math.min(random.nextInt(128), expected.length - offset);
@@ -163,33 +163,45 @@ public class WriterOutputStreamTest {
 
     private void testWithSingleByteWrite(final String testString, final Charset charset) throws IOException {
         final byte[] bytes = testString.getBytes(Charsets.toCharset(charset));
-        final StringWriter writer = new StringWriter();
+        StringWriter writer = new StringWriter();
         try (WriterOutputStream out = new WriterOutputStream(writer, charset)) {
-            for (final byte b : bytes) {
-                out.write(b);
-            }
+            writeOneAtATime(bytes, out);
+        }
+        assertEquals(testString, writer.toString());
+        //
+        writer = new StringWriter();
+        try (WriterOutputStream out = WriterOutputStream.builder().setWriter(writer).setCharset(charset).get()) {
+            writeOneAtATime(bytes, out);
         }
         assertEquals(testString, writer.toString());
     }
 
     private void testWithSingleByteWrite(final String testString, final CharsetDecoder charsetDecoder) throws IOException {
         final byte[] bytes = testString.getBytes(CharsetDecoders.toCharsetDecoder(charsetDecoder).charset());
-        final StringWriter writer = new StringWriter();
+        StringWriter writer = new StringWriter();
         try (WriterOutputStream out = new WriterOutputStream(writer, charsetDecoder)) {
-            for (final byte b : bytes) {
-                out.write(b);
-            }
+            writeOneAtATime(bytes, out);
+        }
+        assertEquals(testString, writer.toString());
+        //
+        writer = new StringWriter();
+        try (WriterOutputStream out = WriterOutputStream.builder().setWriter(writer).setCharsetDecoder(charsetDecoder).get()) {
+            writeOneAtATime(bytes, out);
         }
         assertEquals(testString, writer.toString());
     }
 
     private void testWithSingleByteWrite(final String testString, final String charsetName) throws IOException {
         final byte[] bytes = testString.getBytes(Charsets.toCharset(charsetName));
-        final StringWriter writer = new StringWriter();
+        StringWriter writer = new StringWriter();
         try (WriterOutputStream out = new WriterOutputStream(writer, charsetName)) {
-            for (final byte b : bytes) {
-                out.write(b);
-            }
+            writeOneAtATime(bytes, out);
+        }
+        assertEquals(testString, writer.toString());
+        //
+        writer = new StringWriter();
+        try (WriterOutputStream out = WriterOutputStream.builder().setWriter(writer).setCharset(charsetName).get()) {
+            writeOneAtATime(bytes, out);
         }
         assertEquals(testString, writer.toString());
     }
@@ -201,5 +213,22 @@ public class WriterOutputStreamTest {
             out.write("abc".getBytes(StandardCharsets.US_ASCII));
             assertEquals("abc", writer.toString());
         }
+        // @formatter:off
+        try (WriterOutputStream out = WriterOutputStream.builder()
+                .setWriter(writer)
+                .setCharset("us-ascii")
+                .setBufferSize(1024)
+                .setWriteImmediately(true)
+                .get()) {
+            // @formatter:on
+            out.write("abc".getBytes(StandardCharsets.US_ASCII));
+            assertEquals("abcabc", writer.toString());
+        }
+    }
+
+    private void writeOneAtATime(final byte[] bytes, WriterOutputStream out) throws IOException {
+        for (final byte b : bytes) {
+            out.write(b);
+        }
     }
 }
diff --git a/src/test/java/org/apache/commons/io/output/XmlStreamWriterTest.java b/src/test/java/org/apache/commons/io/output/XmlStreamWriterTest.java
index 2a005bed..be24ebcb 100644
--- a/src/test/java/org/apache/commons/io/output/XmlStreamWriterTest.java
+++ b/src/test/java/org/apache/commons/io/output/XmlStreamWriterTest.java
@@ -29,6 +29,7 @@ import org.junit.jupiter.api.Test;
 import org.junitpioneer.jupiter.DefaultLocale;
 
 /**
+ * Tests {@link XmlStreamWriter}.
  */
 public class XmlStreamWriterTest {
 
@@ -48,15 +49,18 @@ public class XmlStreamWriterTest {
     private static final String TEXT_UNICODE = TEXT_LATIN1 + ", " + TEXT_LATIN7
             + ", " + TEXT_LATIN15 + ", " + TEXT_EUC_JP;
 
+    @SuppressWarnings("resource")
     private static void checkXmlContent(final String xml, final String encodingName, final String defaultEncodingName)
-        throws IOException {
+            throws IOException {
         final ByteArrayOutputStream out = new ByteArrayOutputStream();
-        final XmlStreamWriter writer = new XmlStreamWriter(out, defaultEncodingName);
-        writer.write(xml);
-        writer.close();
+        final XmlStreamWriter writerCheck;
+        try (final XmlStreamWriter writer = XmlStreamWriter.builder().setOutputStream(out).setCharset(defaultEncodingName).get()) {
+            writerCheck = writer;
+            writer.write(xml);
+        }
         final byte[] xmlContent = out.toByteArray();
         final Charset charset = Charset.forName(encodingName);
-        final Charset writerCharset = Charset.forName(writer.getEncoding());
+        final Charset writerCharset = Charset.forName(writerCheck.getEncoding());
         assertEquals(charset, writerCharset);
         assertTrue(writerCharset.contains(charset), writerCharset.name());
         assertArrayEquals(xml.getBytes(encodingName), xmlContent);
@@ -101,8 +105,16 @@ public class XmlStreamWriterTest {
 
     @Test
     public void testEmpty() throws IOException {
-        final ByteArrayOutputStream out = new ByteArrayOutputStream();
-        try (XmlStreamWriter writer = new XmlStreamWriter(out)) {
+        try (final ByteArrayOutputStream out = new ByteArrayOutputStream();
+                XmlStreamWriter writer = new XmlStreamWriter(out)) {
+            writer.flush();
+            writer.write("");
+            writer.flush();
+            writer.write(".");
+            writer.flush();
+        }
+        try (final ByteArrayOutputStream out = new ByteArrayOutputStream();
+                XmlStreamWriter writer = XmlStreamWriter.builder().setOutputStream(out).get()) {
             writer.flush();
             writer.write("");
             writer.flush();