A hands-on guide to every major way of writing files in Java — and how to pick the right one
Sooner or later, almost every Java program needs to write something to disk — a log entry, an exported report, a config file, a saved user session. The good news is Java gives you plenty of ways to do it. The less good news is that "plenty of ways" can feel overwhelming if you're not sure which class actually fits your situation. This guide walks through the main options, with working examples for each, so you can pick the right tool instead of just the first one you remember.
The Building Blocks of Java File Writing
File writing in Java is fundamentally an I/O (input/output) operation, and most of the classes you'll use live in the java.io package, with a few modern additions in java.nio.file. The core players are:
FileWriter— writes character data directly to a file. Simple, but with no built-in buffering.BufferedWriter— wraps another writer and buffers the output, cutting down on the number of actual disk writes.PrintWriter— adds convenience methods likeprintln()andprintf()for writing formatted text.FileOutputStream— writes raw bytes, which makes it the go-to choice for binary data.Files(fromjava.nio.file) — a modern, streamlined API for reading and writing files in just a line or two.
Each one solves a slightly different problem. Let's go through them.
Quick Comparison: Which Writer Should You Use?
| Class | Best For | Buffered? | Notes |
|---|---|---|---|
FileWriter |
Quick, simple text writes | No | Fine for small, one-off writes |
BufferedWriter |
Frequent or large text writes | Yes | Reduces disk I/O overhead |
PrintWriter |
Formatted output (printf, println) |
Optional | Most convenient for readable output |
FileOutputStream |
Binary data | No | Pair with BufferedOutputStream for large files |
Files.write() |
Small files, one-shot writes | N/A | Shortest syntax, modern NIO API |
1. Writing to a File with FileWriter
FileWriter is the most basic way to put text into a file — good for simple, low-volume writes.
import java.io.FileWriter;
import java.io.IOException;
public class FileWriteExample {
public static void main(String[] args) {
try {
FileWriter writer = new FileWriter("output.txt");
writer.write("Hello, world!");
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
What's happening here:
new FileWriter("output.txt")opens the file for writing — and creates it automatically if it doesn't already exist.writer.write(...)sends the string to the file.writer.close()releases the underlying resource. Skipping this step is a common source of bugs, since unflushed data can be lost if the program exits before closing.
The catch with plain FileWriter is that every call to write() can trigger a separate disk operation, which gets slow fast if you're writing a lot of data. That's where BufferedWriter comes in.
2. Writing Efficiently with BufferedWriter
Wrapping a BufferedWriter around a FileWriter batches up writes in memory and flushes them to disk in larger chunks, which is noticeably faster for anything beyond a trivial amount of text.
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
public class BufferedWriterExample {
public static void main(String[] args) {
try {
BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"));
writer.write("Using BufferedWriter to write.");
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Here's a more realistic version that writes multiple lines, using newLine() for platform-correct line breaks:
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.List;
import java.util.Arrays;
public class MultiLineWriter {
public static void main(String[] args) {
List<String> lines = Arrays.asList("First line", "Second line", "Third line");
try (BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))) {
for (String line : lines) {
writer.write(line);
writer.newLine();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
If you're writing more than a handful of lines, BufferedWriter should generally be your default over a raw FileWriter.
3. Writing Formatted Text with PrintWriter
When you need output that looks like something a human would want to read — formatted numbers, multiple data types on one line — PrintWriter is the most convenient option, since it supports the same printf()-style formatting you'd use for console output.
import java.io.FileWriter;
import java.io.PrintWriter;
import java.io.IOException;
public class PrintWriterExample {
public static void main(String[] args) {
try {
PrintWriter writer = new PrintWriter(new FileWriter("output.txt"));
writer.println("This is a line.");
writer.printf("And this is a number: %d%n", 100);
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
You can also combine PrintWriter with BufferedWriter to get both formatting convenience and buffered performance:
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.io.IOException;
public class FormattedReportWriter {
public static void main(String[] args) {
try (PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter("report.txt")))) {
writer.println("Sales Report");
writer.println("------------");
writer.printf("Product: %-15s Revenue: $%,.2f%n", "Widget", 4523.75);
writer.printf("Product: %-15s Revenue: $%,.2f%n", "Gadget", 1899.10);
} catch (IOException e) {
e.printStackTrace();
}
}
}
This combination is common in real applications — reports, logs, and exports where readability matters.
4. Appending to a File Instead of Overwriting It
By default, most writers overwrite a file's existing content. If you want to add to a file instead — for logging, for example — pass true as a second argument to FileWriter's constructor:
import java.io.FileWriter;
import java.io.IOException;
public class AppendFileExample {
public static void main(String[] args) {
try {
FileWriter writer = new FileWriter("output.txt", true);
writer.write("Appending text.");
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
That true flag is easy to overlook but makes a big difference — without it, every run of your program would wipe out the previous content instead of adding to it. This pattern is especially useful for simple logging:
import java.io.FileWriter;
import java.io.PrintWriter;
import java.io.IOException;
import java.time.LocalDateTime;
public class SimpleLogger {
public static void log(String message) {
try (PrintWriter writer = new PrintWriter(new FileWriter("app.log", true))) {
writer.println(LocalDateTime.now() + " - " + message);
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
log("Application started");
log("User logged in");
}
}
5. Writing Binary Data with FileOutputStream
Text writers like FileWriter are built for characters, not raw bytes — so for binary data (images, serialized objects, custom formats), you'll want FileOutputStream instead.
import java.io.FileOutputStream;
import java.io.IOException;
public class FileOutputStreamExample {
public static void main(String[] args) {
byte[] data = "Binary-safe content".getBytes();
try (FileOutputStream fos = new FileOutputStream("output.bin")) {
fos.write(data);
} catch (IOException e) {
e.printStackTrace();
}
}
}
For larger binary writes, wrap it in a BufferedOutputStream the same way you'd buffer a text writer:
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class BufferedBinaryWriter {
public static void main(String[] args) {
byte[] chunk = new byte[1024];
try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("large.bin"))) {
for (int i = 0; i < 100; i++) {
bos.write(chunk);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
6. The Modern Shortcut: Files.write()
If you're on Java 7 or later, the java.nio.file.Files class offers a much shorter way to write small files in a single call:
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.io.IOException;
public class NioFileWriter {
public static void main(String[] args) {
String content = "Written using the NIO Files API.";
try {
Files.write(Paths.get("output.txt"), content.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
}
You can control append-versus-overwrite behavior explicitly with StandardOpenOption:
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.io.IOException;
public class NioAppendExample {
public static void main(String[] args) {
String content = "Appended with NIO.\n";
try {
Files.write(
Paths.get("output.txt"),
content.getBytes(),
StandardOpenOption.CREATE,
StandardOpenOption.APPEND
);
} catch (IOException e) {
e.printStackTrace();
}
}
}
For small, straightforward writes, this is often the cleanest option available — no explicit writer object to manage at all.
7. Handling Resources the Right Way: try-with-resources
Every writer and stream shown above implements AutoCloseable, which means try-with-resources should be your default pattern for writing files, since it guarantees the resource gets closed even if an exception occurs mid-write.
import java.io.FileWriter;
import java.io.IOException;
public class ExceptionHandlingExample {
public static void main(String[] args) {
try (FileWriter writer = new FileWriter("output.txt")) {
writer.write("Try with resources ensures closure.");
} catch (IOException e) {
e.printStackTrace();
}
}
}
Compare that to the manual cleanup it replaces:
// Old style — verbose, and easy to leak a resource if you forget the finally block
FileWriter writer = null;
try {
writer = new FileWriter("output.txt");
writer.write("Manual cleanup required.");
} catch (IOException e) {
e.printStackTrace();
} finally {
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
The try-with-resources version is shorter, harder to get wrong, and works with multiple resources at once — just separate them with semicolons inside the parentheses.
Choosing the Right Method: A Quick Decision Guide
- Writing a small amount of plain text? →
FileWriterorFiles.write() - Writing a lot of text, or writing frequently? →
BufferedWriter - Need formatted output (numbers, multiple lines,
printf-style)? →PrintWriter - Logging or adding to an existing file? → Any writer with the append flag (or
StandardOpenOption.APPEND) - Writing binary data? →
FileOutputStream, buffered for large files - Want the shortest possible code for a one-off write? →
Files.write()
Final Thoughts
Writing files in Java isn't complicated once you know which tool matches the job. For everyday text output, BufferedWriter and PrintWriter will cover the vast majority of cases. For binary data, FileOutputStream is the right call. And for quick, small writes, the modern Files.write() method is hard to beat for sheer simplicity.
Whichever class you reach for, make try-with-resources a habit — it's a small change that quietly prevents a whole category of resource-leak bugs. Once writing feels natural, pairing it with the file-reading techniques you already know rounds out a solid foundation for handling data in Java.