Skip to content
Java io 6 min read

BufferedOutputStream

Writing a file one byte at a time means one OS system call per byte — that’s thousands of expensive kernel transitions for even a small file. BufferedOutputStream fixes this by wrapping any OutputStream and accumulating your bytes in an internal memory buffer, flushing to the actual destination only when the buffer fills up (or when you explicitly flush).

What BufferedOutputStream Does

BufferedOutputStream is a decorator — it wraps another OutputStream and adds buffering on top. It lives in java.io and extends FilterOutputStream.

Every write() call places bytes into an internal byte[] buffer instead of writing directly to the underlying stream. When the buffer is full, or when you call flush() or close(), the entire buffer is sent to the OS in one shot. The result: far fewer system calls for the same amount of data.

Tip: Always wrap file-based or network streams with BufferedOutputStream. For sequential writes the speed improvement is typically 10–100× compared to unbuffered output.

Basic Usage

import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class BasicBufferedWrite {
    public static void main(String[] args) throws IOException {
        try (BufferedOutputStream bos = new BufferedOutputStream(
                new FileOutputStream("output.txt"))) {

            String message = "Hello, BufferedOutputStream!";
            bos.write(message.getBytes());
        } // flush + close happen automatically here
        System.out.println("Write complete.");
    }
}

Output:

Write complete.

The try-with-resources block calls close() automatically, which first flushes any remaining bytes in the buffer before closing the underlying stream.

Writing Byte Arrays

You can write a portion of a byte array using write(byte[], int offset, int length):

import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class ByteArrayWrite {
    public static void main(String[] args) throws IOException {
        byte[] data = {72, 101, 108, 108, 111}; // "Hello" in ASCII

        try (BufferedOutputStream bos = new BufferedOutputStream(
                new FileOutputStream("hello.bin"))) {

            bos.write(data, 0, data.length);
            System.out.println("Bytes written: " + data.length);
        }
    }
}

Output:

Bytes written: 5

Note: write(byte[], offset, length) copies bytes into the buffer. If the array is larger than the remaining buffer space, BufferedOutputStream may flush the current buffer and write the new data directly to avoid copying it twice.

Writing One Byte at a Time

You can still call write(int b) for single-byte writes. The buffer absorbs the cost:

import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class SingleByteWrite {
    public static void main(String[] args) throws IOException {
        try (BufferedOutputStream bos = new BufferedOutputStream(
                new FileOutputStream("numbers.bin"))) {

            for (int i = 0; i < 100; i++) {
                bos.write(i); // buffered — no system call each iteration
            }
        }
        System.out.println("100 bytes written.");
    }
}

Output:

100 bytes written.

Without buffering, the loop above would generate 100 system calls. With BufferedOutputStream (default 8 KB buffer), all 100 bytes fit in the buffer and only one write reaches the OS when close() is called.

Custom Buffer Size

The default buffer is 8192 bytes (8 KB). Pass a second argument to the constructor to override it:

// 64 KB buffer — useful for high-throughput file or network writes
BufferedOutputStream bos = new BufferedOutputStream(
        new FileOutputStream("large.bin"), 65536);

Choose a larger buffer when writing many small chunks to a large file or a slow network socket. For most everyday file writes, the default 8 KB is a sensible balance.

Constructors at a Glance

ConstructorBuffer Size
BufferedOutputStream(OutputStream out)8192 bytes (default)
BufferedOutputStream(OutputStream out, int size)Custom size bytes

Flushing the Buffer

flush() forces all bytes currently in the buffer out to the underlying stream without closing it. This is important whenever the recipient needs the data immediately:

import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class FlushDemo {
    public static void main(String[] args) throws IOException {
        try (BufferedOutputStream bos = new BufferedOutputStream(
                new FileOutputStream("log.txt"))) {

            bos.write("Starting process...\n".getBytes());
            bos.flush(); // ensure this line reaches the file right now

            // long-running work here
            bos.write("Process complete.\n".getBytes());
        }
    }
}

Warning: Forgetting to flush() (or close()) before the program exits can silently lose data. If you write to a socket or a file that another process reads, always flush at logical message boundaries.

Practical Example: Copying a File

Pairing BufferedOutputStream with BufferedInputStream gives you buffering on both ends of the pipe:

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class FileCopy {
    public static void main(String[] args) throws IOException {
        String src  = "source.bin";
        String dest = "destination.bin";

        try (BufferedInputStream  in  = new BufferedInputStream(new FileInputStream(src));
             BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(dest))) {

            byte[] buf = new byte[8192];
            int n;
            while ((n = in.read(buf)) != -1) {
                out.write(buf, 0, n);
            }
        }
        System.out.println("File copied successfully.");
    }
}

Output:

File copied successfully.

Both reads and writes hit memory most of the time. Only occasional buffer-fill (input) and buffer-flush (output) operations reach the OS.

Wrapping a Network Socket

BufferedOutputStream is equally useful for socket output, where individual byte writes would stall on the network stack:

import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.Socket;

public class SocketWrite {
    public static void send(String host, int port, String message) throws IOException {
        try (Socket socket = new Socket(host, port);
             BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
             PrintWriter writer = new PrintWriter(bos, false)) {

            writer.println(message);
            writer.flush(); // must flush — auto-flush is false
        }
    }
}

Tip: When wrapping BufferedOutputStream with a PrintWriter, pass autoFlush = false and flush manually. Auto-flush on every println() defeats the purpose of buffering.

Under the Hood

Internally, BufferedOutputStream holds two fields from its parent FilterOutputStream plus its own:

  • byte[] buf — the internal write buffer (default 8192 bytes)
  • int count — number of valid bytes currently sitting in buf

When you call write(int b), the byte is placed at buf[count++]. When count == buf.length, flushBuffer() is triggered automatically, which calls the underlying stream’s write(buf, 0, count) and resets count to zero.

When you call write(byte[], offset, length):

  1. If length >= buf.length, the current buffer is flushed first, then the new data is written directly to the underlying stream (bypassing the buffer entirely). This avoids an unnecessary copy.
  2. Otherwise, the bytes are appended into the buffer, flushing only if there is no room.

This optimization means large bulk writes are as fast as writing to the underlying stream directly, while small writes are batched cheaply.

Thread safety: BufferedOutputStream is not thread-safe. If multiple threads write concurrently, count and buf can become inconsistent, producing garbled output or ArrayIndexOutOfBoundsException. Protect shared instances with synchronized blocks, or give each thread its own stream.

BufferedOutputStream vs FileOutputStream

FeatureFileOutputStreamBufferedOutputStream wrapping FileOutputStream
Each write(int) callOS system callWritten to memory buffer
Bulk write performanceGood with large arraysExcellent; auto-batches small writes
Explicit flush neededNo (each write goes immediately)Yes, before reading the file from another process
OverheadMinimal per-object~8 KB heap for the buffer

For writing byte streams sequentially, the wrapped version wins almost every time.

Closing the Stream

Calling close() on BufferedOutputStream:

  1. Calls flush() to drain any remaining buffered bytes.
  2. Calls close() on the underlying OutputStream.

Always use try-with-resources (Java 7+) to guarantee this happens even when an exception is thrown:

try (BufferedOutputStream bos = new BufferedOutputStream(
        new FileOutputStream("report.bin"))) {
    // write data
} // flush + close happen here automatically

Warning: If you do not close or flush the stream and the JVM exits normally, the data in the buffer is lost. The JVM does not automatically flush application-level buffers on shutdown.

Last updated June 13, 2026
Was this helpful?