Skip to content
Java io 7 min read

FileOutputStream

FileOutputStream is Java’s fundamental class for writing raw bytes to a file. Whether you’re saving binary data, generating a file from scratch, or copying content from one location to another, FileOutputStream gives you low-level, direct control over what lands on disk.

Fileoutputstream outputstream hierarchy

What Is FileOutputStream?

FileOutputStream extends OutputStream and lives in the java.io package. It writes data to a file one byte at a time or in bulk chunks. Because it works at the byte level, it’s perfect for binary files (images, audio, serialized objects), and also works fine for plain text when you control the encoding yourself.

OutputStream
  └── FileOutputStream

Note: FileOutputStream writes bytes, not characters. For writing human-readable text with proper encoding, consider wrapping it in an OutputStreamWriter or using FileWriter directly.

Creating a FileOutputStream

You can open a FileOutputStream from a path string or a File object. By default, opening the stream overwrites the existing file (or creates a new one).

import java.io.FileOutputStream;
import java.io.File;
import java.io.IOException;

public class CreateExample {
    public static void main(String[] args) throws IOException {
        // From a path string — creates or overwrites the file
        FileOutputStream fos1 = new FileOutputStream("output.txt");

        // From a File object
        File file = new File("output.txt");
        FileOutputStream fos2 = new FileOutputStream(file);

        fos1.close();
        fos2.close();
    }
}

If the parent directory does not exist, the constructor throws a FileNotFoundException.

Append Mode

Pass true as the second argument to append to an existing file instead of overwriting it.

// Opens the file in append mode — existing content is preserved
FileOutputStream fos = new FileOutputStream("log.txt", true);

Tip: Append mode is ideal for log files where you want to keep adding entries without erasing previous runs.

Writing a Single Byte

The write(int b) method writes a single byte. Only the lowest 8 bits of the int argument are written; the upper 24 bits are ignored.

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

public class WriteSingleByte {
    public static void main(String[] args) throws IOException {
        try (FileOutputStream fos = new FileOutputStream("output.txt")) {
            fos.write('H');
            fos.write('i');
            fos.write('!');
        }
        System.out.println("Bytes written.");
    }
}

Output: (in output.txt)

Hi!

Warning: Writing one byte at a time is extremely slow for large amounts of data because every write() call can translate to a separate system call. Always prefer bulk writes or wrap with BufferedOutputStream in real-world code.

Writing a Byte Array (Bulk Write)

The write(byte[] b) method writes the entire byte array at once — far more efficient than single-byte writes.

import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

public class WriteBulk {
    public static void main(String[] args) throws IOException {
        String content = "Hello, FileOutputStream!";
        byte[] bytes = content.getBytes(StandardCharsets.UTF_8);

        try (FileOutputStream fos = new FileOutputStream("output.txt")) {
            fos.write(bytes);
        }

        System.out.println("File written successfully.");
    }
}

Output: (in output.txt)

Hello, FileOutputStream!

Tip: Always specify the charset explicitly with getBytes(StandardCharsets.UTF_8) rather than relying on the platform’s default encoding. This keeps your output portable across different operating systems.

Writing a Portion of a Byte Array

write(byte[] b, int off, int len) writes exactly len bytes from array b, starting at index off. This is useful when your buffer is partially filled.

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

public class WriteRange {
    public static void main(String[] args) throws IOException {
        byte[] data = "Hello, World!".getBytes();

        try (FileOutputStream fos = new FileOutputStream("output.txt")) {
            // Write only "World" (offset 7, length 5)
            fos.write(data, 7, 5);
        }
    }
}

Output: (in output.txt)

World

Since Java 7, the preferred way to work with FileOutputStream is inside a try-with-resources block. It closes the stream automatically — even if an exception is thrown partway through writing.

import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

public class TryWithResources {
    public static void main(String[] args) {
        String[] lines = {"Line 1\n", "Line 2\n", "Line 3\n"};

        try (FileOutputStream fos = new FileOutputStream("output.txt")) {
            for (String line : lines) {
                fos.write(line.getBytes(StandardCharsets.UTF_8));
            }
        } catch (IOException e) {
            System.err.println("Write failed: " + e.getMessage());
        }

        System.out.println("Done.");
    }
}

Output: (in output.txt)

Line 1
Line 2
Line 3

Flushing the Stream

flush() forces any internally buffered bytes to be pushed to the underlying OS. For a raw FileOutputStream there is no internal Java-level buffer (unlike BufferedOutputStream), but calling flush() is still good practice — especially when the stream is wrapped inside another buffered stream.

fos.flush(); // ensures OS receives all pending data

Warning: Simply calling flush() does not guarantee data is written to physical disk. For that, you need getFD().sync() (see the Under the Hood section), which is rarely needed in everyday applications.

Useful Methods

MethodDescription
void write(int b)Writes a single byte (lowest 8 bits of b)
void write(byte[] b)Writes the entire byte array
void write(byte[] b, int off, int len)Writes len bytes from array starting at off
void flush()Flushes any buffered data to the OS
void close()Closes the stream and releases the file descriptor
FileDescriptor getFD()Returns the underlying file descriptor
FileChannel getChannel()Returns the underlying FileChannel for NIO operations

Copying a File with FileOutputStream

A classic, practical use case — copying a file by pairing FileInputStream with FileOutputStream:

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

public class FileCopy {
    public static void main(String[] args) {
        String source = "original.png";
        String destination = "copy.png";

        try (FileInputStream fis = new FileInputStream(source);
             FileOutputStream fos = new FileOutputStream(destination)) {

            byte[] buffer = new byte[4096];
            int bytesRead;

            while ((bytesRead = fis.read(buffer)) != -1) {
                fos.write(buffer, 0, bytesRead);
            }

            System.out.println("File copied successfully.");
        } catch (IOException e) {
            System.err.println("Copy failed: " + e.getMessage());
        }
    }
}

Output:

File copied successfully.

Appending Multiple Writes to a Log File

Here’s a realistic example of appending log entries over multiple runs:

import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;

public class SimpleLogger {
    public static void log(String message) throws IOException {
        String entry = "[" + LocalDateTime.now() + "] " + message + "\n";
        // append = true keeps existing log content
        try (FileOutputStream fos = new FileOutputStream("app.log", true)) {
            fos.write(entry.getBytes(StandardCharsets.UTF_8));
        }
    }

    public static void main(String[] args) throws IOException {
        log("Application started");
        log("Processing data...");
        log("Application stopped");
        System.out.println("Log entries written.");
    }
}

Output: (in app.log, new entries added on each run)

[2026-06-13T10:00:01] Application started
[2026-06-13T10:00:01] Processing data...
[2026-06-13T10:00:01] Application stopped

Under the Hood

When you construct a FileOutputStream, the JVM calls the OS open() system call, which returns a file descriptor — a small integer index into the process’s open-file table. The JVM stores this in a FileDescriptor object.

Each write(int b) call (single-byte form) typically translates to a native write system call on the OS, moving one byte from the JVM heap into kernel space and then to the file. System calls involve a context switch from user mode to kernel mode, which has measurable overhead. Writing one byte at a time means paying this overhead on every iteration — devastating for performance.

The bulk write(byte[] b, int off, int len) form makes one system call for len bytes, amortizing the context-switch cost across many bytes. Wrapping with BufferedOutputStream goes further: it accumulates writes in an 8 KB in-memory buffer and only calls the OS when the buffer is full or flush() is called, reducing system call frequency dramatically.

Durability vs. Performance

When write() returns, data is in the OS’s page cache (kernel memory), not necessarily on physical disk. The OS flushes its page cache to disk asynchronously for performance. If you need durability guarantees (e.g., for a database or financial log), call:

fos.flush();          // push Java buffers → OS
fos.getFD().sync();   // push OS page cache → physical disk (fsync)

getFD().sync() is expensive — it blocks until the drive confirms the write — so only use it when data integrity is critical.

The getChannel() Bridge to NIO

FileOutputStream.getChannel() returns the underlying FileChannel, letting you use the NIO API (direct buffers, memory-mapped files, scatter/gather writes) without opening a separate handle. This is useful for high-throughput scenarios. Learn more at NIO.2: Path & Files.

Common Pitfalls

  • Accidentally overwriting a file — the default constructor truncates existing content. If you want to append, remember to pass true as the second argument.
  • Not closing the stream — a leaked FileOutputStream holds an OS file descriptor open and, on Windows, may keep the file locked so other processes cannot access it. Always use try-with-resources.
  • Relying on implicit flushing — unlike PrintStream, FileOutputStream does not auto-flush. Wrap with BufferedOutputStream and call flush() before closing if you hand the stream to another component.
  • Encoding issues — when converting a String to bytes with getBytes(), always specify StandardCharsets.UTF_8 (or whatever encoding your readers expect) instead of the platform default.
  • Partial writeswrite(byte[]) is not guaranteed to write all bytes atomically on every platform when used with very large arrays. Check the return value or use write(b, off, len) inside a loop for robust code.
Last updated June 13, 2026
Was this helpful?