EMZETT.
Login

FileOutputStream

In short: A byte stream for writing raw binary data into a file — the counterpart to FileInputStream.

In more detail: As with FileInputStream: for pure text, a Writer (e.g. FileWriter/BufferedWriter) is better suited, since it handles character encoding. FileOutputStream with the append constructor flag (new FileOutputStream(file, true)) appends to an existing file instead of overwriting it.

In Depth

byte[] data = { 0x48, 0x65, 0x6C, 0x6C, 0x6F }; // bytes for "Hello"
 
try (FileOutputStream fos = new FileOutputStream("raw.bin")) {
    fos.write(data);
} catch (IOException e) {
    System.err.println("Write failed: " + e.getMessage());
}
 
// Append mode - appends instead of overwriting
try (FileOutputStream log = new FileOutputStream("app.log", true)) {
    log.write("New entry\n".getBytes());
}

As with FileInputStream: FileOutputStream operates at the raw byte level, with no knowledge of character encoding whatsoever. For text files, a Writer (FileWriter/BufferedWriter) is usually the right choice, because it automatically handles string↔byte conversion via a configurable character encoding (e.g. UTF-8) — if you write text directly via FileOutputStream with .getBytes() instead, you implicitly rely on the default platform encoding, which can cause problems if the file is later read on a system with a different default encoding. Explicitly using .getBytes(StandardCharsets.UTF_8) makes this behaviour independent of the platform.

Without buffering, every write() call is potentially expensive

FileOutputStream.write() writes UNBUFFERED by default — every single call can trigger an actual operating system system call. If writing happens byte by byte or in many small chunks within a loop, this quickly adds up to noticeable slowdown. A BufferedOutputStream wrapper collects data in memory and only actually writes it to disk in larger blocks:

try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("large.bin"))) {
    for (int i = 0; i < 100_000; i++) {
        bos.write(i); // lands in the buffer, not immediately on disk
    }
} // the remaining buffer is automatically flushed when closed

flush() and when it matters

flush() forces buffered data to actually be written immediately, instead of waiting in the buffer — relevant, for example, for a log stream that should have the most up-to-date entries on disk as possible even if the program crashes mid-write. try-with-resources automatically calls close() when closing, which itself internally performs one final flush() — an explicit flush() is therefore only needed when data has to be visible BEFORE the regular end of the try block.

File channels as an alternative for high throughput

For particularly performance-critical file access (e.g. large log files with a high write frequency), java.nio.channels.FileChannel offers more direct access to the operating system buffer, including the ability to write to a specific position deliberately, instead of only sequentially at the end:

try (FileChannel channel = FileChannel.open(Path.of("data.bin"), StandardOpenOption.WRITE)) {
    ByteBuffer buffer = ByteBuffer.wrap("Text".getBytes());
    channel.position(100); // jump to byte position 100
    channel.write(buffer);
}

For the vast majority of use cases, this is unnecessary effort — FileOutputStream/Files.write() are entirely sufficient, FileChannel is only worthwhile for very specific performance or positioning requirements.

See also: I/O Streams, FileInputStream, Write Files