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,BufferedOutputStreammay 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
| Constructor | Buffer 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()(orclose()) 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
BufferedOutputStreamwith aPrintWriter, passautoFlush = falseand flush manually. Auto-flush on everyprintln()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 inbuf
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):
- 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. - 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
| Feature | FileOutputStream | BufferedOutputStream wrapping FileOutputStream |
|---|---|---|
Each write(int) call | OS system call | Written to memory buffer |
| Bulk write performance | Good with large arrays | Excellent; auto-batches small writes |
| Explicit flush needed | No (each write goes immediately) | Yes, before reading the file from another process |
| Overhead | Minimal 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:
- Calls
flush()to drain any remaining buffered bytes. - Calls
close()on the underlyingOutputStream.
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.
Related Topics
- BufferedInputStream — the read-side counterpart for buffered byte input
- FileOutputStream — the unbuffered byte stream that BufferedOutputStream typically wraps
- BufferedWriter — buffered character-stream writing with
newLine()support - Byte vs Character Streams — choosing the right stream family for your data
- Java I/O — overview of the entire java.io package
- Serialization — writing entire object graphs to an OutputStream