diff --git a/src/main/java/org/apache/commons/io/output/FlushShieldOutputStream.java b/src/main/java/org/apache/commons/io/output/FlushShieldOutputStream.java
new file mode 100644
index 00000000000..995cbde632d
--- /dev/null
+++ b/src/main/java/org/apache/commons/io/output/FlushShieldOutputStream.java
@@ -0,0 +1,119 @@
+/*
+ * 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
+ *
+ * https://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.output;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.apache.commons.io.build.AbstractStreamBuilder;
+
+/**
+ * Re-implements {@link FilterOutputStream#flush()} to do nothing.
+ *
+ * @since 2.22.0
+ */
+public final class FlushShieldOutputStream extends ProxyOutputStream {
+
+ // @formatter:off
+ /**
+ * Builds a new {@link FlushShieldOutputStream}.
+ *
+ *
+ * Using File IO:
+ *
+ * {@code
+ * FlushShieldOutputStream s = FlushShieldOutputStream.builder()
+ * .setPath("over/there.out")
+ * .setBufferSize(8192)
+ * .get();
+ * }
+ *
+ *
+ * Using NIO Path:
+ *
+ * {@code
+ * FlushShieldOutputStream s = FlushShieldOutputStream.builder()
+ * .setPath("over/there.out")
+ * .setBufferSize(8192)
+ * .get();
+ * }
+ *
+ *
+ * @see #get()
+ * @since 2.13.0
+ */
+ // @formatter:on
+ public static class Builder extends AbstractStreamBuilder {
+
+ /**
+ * Constructs a new builder of {@link FlushShieldOutputStream}.
+ */
+ public Builder() {
+ // empty
+ }
+
+ /**
+ * Builds a new {@link FlushShieldOutputStream}.
+ *
+ * @return a new instance.
+ * @throws IllegalStateException if the {@code origin} is {@code null}.
+ * @throws UnsupportedOperationException if the origin cannot be converted to an {@link OutputStream}.
+ * @throws IOException if an I/O error occurs converting to an {@link OutputStream} using {@link #getOutputStream()}.
+ * @see #getOutputStream()
+ * @see #getBufferSize()
+ * @see #getUnchecked()
+ */
+ @Override
+ public FlushShieldOutputStream get() throws IOException {
+ return new FlushShieldOutputStream(this);
+ }
+
+ }
+
+ /**
+ * Constructs a new builder of {@link FlushShieldOutputStream}.
+ *
+ * @return a new builder of {@link FlushShieldOutputStream}.
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ @SuppressWarnings("resource") // caller closes.
+ private FlushShieldOutputStream(final Builder builder) throws IOException {
+ super(builder.getOutputStream());
+ }
+
+ /**
+ * Constructs a {@code FlushShieldOutputStream} filter for the specified underlying output stream.
+ *
+ * @param out the underlying output stream to be assigned to the field {@code this.out} for later use, or {@code null} if this instance is to be created
+ * without an underlying stream.
+ */
+ public FlushShieldOutputStream(final OutputStream out) {
+ super(out);
+ }
+
+ @Override
+ public void flush() throws IOException {
+ // shield: do nothing.
+ }
+}
diff --git a/src/test/java/org/apache/commons/io/output/FlushShieldOutputStreamTest.java b/src/test/java/org/apache/commons/io/output/FlushShieldOutputStreamTest.java
new file mode 100644
index 00000000000..3f840d05d08
--- /dev/null
+++ b/src/test/java/org/apache/commons/io/output/FlushShieldOutputStreamTest.java
@@ -0,0 +1,135 @@
+/*
+ * 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
+ *
+ * https://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.output;
+
+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.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link FlushShieldOutputStream}.
+ */
+class FlushShieldOutputStreamTest {
+
+ /** Tracks whether the underlying stream's close() was called. */
+ private AtomicBoolean closed;
+
+ /** Tracks whether the underlying stream's flush() was called. */
+ private AtomicBoolean flushed;
+
+ /** The stream under test. */
+ private FlushShieldOutputStream shielded;
+
+ /** The underlying byte-array-backed stream used as the delegate. */
+ private ByteArrayOutputStream target;
+
+ @BeforeEach
+ void setUp() {
+ flushed = new AtomicBoolean();
+ closed = new AtomicBoolean();
+ target = new ByteArrayOutputStream() {
+
+ @Override
+ public void close() throws IOException {
+ closed.set(true);
+ super.close();
+ }
+
+ @Override
+ public void flush() throws IOException {
+ flushed.set(true);
+ super.flush();
+ }
+ };
+ shielded = new FlushShieldOutputStream(target);
+ }
+
+ @Test
+ void testBuilderGet() throws IOException {
+ assertNotNull(FlushShieldOutputStream.builder().setOutputStream(target).get());
+ }
+
+ @Test
+ void testBuilderWithOutputStream() throws IOException {
+ try (FlushShieldOutputStream built = FlushShieldOutputStream.builder().setOutputStream(target).get()) {
+ assertNotNull(built);
+ assertInstanceOf(FlushShieldOutputStream.class, built);
+ // flush must be shielded for builder-created instances too
+ built.flush();
+ assertFalse(flushed.get(), "flush() via builder instance must NOT reach the underlying stream.");
+ // writes must still work
+ built.write('B');
+ assertEquals(1, target.size());
+ assertEquals('B', target.toByteArray()[0]);
+ }
+ }
+
+ @Test
+ void testCloseReachesUnderlying() throws IOException {
+ assertFalse(closed.get(), "close should not have been called yet.");
+ shielded.close();
+ assertTrue(closed.get(), "close() must reach the underlying stream.");
+ }
+
+ @Test
+ void testFlushCanBeCalledMultipleTimes() throws IOException {
+ shielded.flush();
+ shielded.flush();
+ shielded.flush();
+ assertFalse(flushed.get(), "repeated flush() calls must NOT reach the underlying stream.");
+ }
+
+ @Test
+ void testFlushDoesNotDelegateToUnderlying() throws IOException {
+ assertFalse(flushed.get(), "flush should not have been called yet.");
+ shielded.flush();
+ assertFalse(flushed.get(), "flush() must NOT reach the underlying stream.");
+ }
+
+ @Test
+ void testWriteByteArray() throws IOException {
+ final byte[] data = { 'H', 'i' };
+ shielded.write(data);
+ assertEquals(2, target.size());
+ assertArrayEquals(data, target.toByteArray());
+ }
+
+ @Test
+ void testWriteByteArrayWithOffset() throws IOException {
+ final byte[] data = { 'X', 'Y', 'Z' };
+ shielded.write(data, 1, 2);
+ assertEquals(2, target.size());
+ assertArrayEquals(new byte[] { 'Y', 'Z' }, target.toByteArray());
+ }
+
+ @Test
+ void testWriteInt() throws IOException {
+ shielded.write('A');
+ assertEquals(1, target.size());
+ assertEquals('A', target.toByteArray()[0]);
+ }
+}