Java PrintWriter
Write formatted text to streams in Java with the PrintWriter class — print, println, printf, format.
Java PrintWriter
The character-streams chapter introduced Writer and its core method, write(String). That's enough for anything, but it isn't ergonomic — printing a number means w.write(Integer.toString(n)), printing a line means remembering to append the line terminator yourself, and formatted output requires String.format on every call. PrintWriter is the decorator that fixes the ergonomics: it adds print, println, printf, and format on top of any underlying Writer or OutputStream.
It's the file-side counterpart to the System.out you've been using since the first chapter — same API surface, written to a file instead of the console.
What PrintWriter adds
void print(boolean | char | int | long | float | double | String | Object);
void println(...); // same overloads, plus the line terminator
PrintWriter printf(String format, Object... args); // String.format under the hood
PrintWriter format(String format, Object... args); // alias for printf
PrintWriter append(CharSequence s); // returns this (for chaining)Plus the inherited Writer methods (write, flush, close). The point is the typed overloads: you can write any primitive or object directly and PrintWriter calls String.valueOf for you.
try (PrintWriter w = new PrintWriter(Files.newBufferedWriter(path))) {
w.println("header");
w.printf("count = %d%n", 42);
w.printf("rate = %.2f%%%n", 0.875 * 100);
w.println(); // blank line
}The try-with-resources close() flushes the buffer; without it the tail-buffer trap from the buffered-streams chapter applies just as much to PrintWriter.
Constructors
The useful ones, in order of preference:
PrintWriter(Path file, Charset charset); // Java 10+, opens the file with the charset
PrintWriter(Writer out); // wrap any Writer (typical: a BufferedWriter)
PrintWriter(Writer out, boolean autoFlush); // same, with autoFlush on println/printf
PrintWriter(OutputStream out, boolean autoFlush, Charset charset);The Path + Charset constructor is the simplest one for "open this file and write to it":
try (PrintWriter w = new PrintWriter(path.toFile(), StandardCharsets.UTF_8)) {
w.println("hello");
}It opens the file, wraps it in an OutputStreamWriter with the supplied charset, wraps that in a BufferedWriter, and gives you a PrintWriter. The four-layer stack you used to assemble by hand collapses to one line.
Always pass an explicit charset. The no-charset constructors — new PrintWriter("file.txt"), new PrintWriter(outputStream) — fall back to the JVM's default encoding, which is the same portability hazard described in the character-streams chapter. UTF-8 is the right default.
The swallowed IOException
PrintWriter differs from every other Writer in one important way: it does not throw IOException. None of print, println, printf, or write declare it. If an underlying I/O call fails, PrintWriter swallows the exception and sets an internal "error" flag.
This is convenient — you can write a long block of println calls without try/catch around each one — but it means a failed write is silent. You have to ask:
if (w.checkError()) {
// something went wrong; the underlying IOException was swallowed
throw new IOException("write to " + path + " failed");
}checkError() is the only way to find out. There is no way to retrieve the original IOException — by the time you check, it's gone. So:
- For console output (the
System.outuse case), the swallow is fine: nobody handles a failed write to a terminal. - For files where a partial write is a real problem (a logfile, a save file, a generated report), check
checkError()at the end of the block, or use aBufferedWriterand callwriteyourself so the exception propagates.
Autoflush
The autoFlush constructor argument controls whether each println, printf, or format call calls flush() afterward. Default is off:
new PrintWriter(out, false) // explicit close/flush only
new PrintWriter(out, true) // flush after every println/printf/formatprint and write never autoflush, even with the flag on — only the line-and-format methods do. That's why System.out.print("waiting...") may sit invisibly while the next computation runs, and System.out.println("waiting...") shows up immediately.
For files, leave autoflush off and let close() (via try-with-resources) handle it. For an interactive log you're tailing, turn it on or call flush() after each batch.
println and the platform line separator
println() writes System.lineSeparator() — \n on Unix and macOS, \r\n on Windows. Same for the %n specifier in printf. That's a feature for terminal output and a bug for data files; the discussion in the buffered-streams chapter (under BufferedWriter.newLine) applies here verbatim:
w.printf("row,%d%n", 1); // platform-dependent terminator
w.printf("row,%d\n", 1); // portable \nWhen the output is going to be read by anything other than the local machine, write \n explicitly.
Comparison with PrintStream
PrintStream is the byte-oriented sibling of PrintWriter — same API, different ancestor. System.out and System.err are PrintStream instances. For file output, prefer PrintWriter: it forces you to think about character encoding (because the constructor takes a Charset), where PrintStream will silently use the default encoding for any non-ASCII character you print.
The next chapter, Java PrintStream, goes into the differences in detail.
A worked example: writing a tiny CSV file
The program below opens a temp file with a PrintWriter, writes a small CSV (header + rows), demonstrates printf formatting, calls checkError() to confirm the writes succeeded, and finally shows the swallow-the-exception behaviour by writing to a writer whose underlying stream has been closed.
What to take from the run:
- The CSV came out exactly as the
printfformat specified.%.2frounded the price to two decimals;%-10sleft-aligned within a 10-character column;\n(not%n) kept the line terminator portable. Format strings are the single biggest reason to reach forPrintWriterover plainWriter. - The first
try-with-resources took the four-layer stack —Path→FileOutputStream→OutputStreamWriter(UTF-8)→BufferedWriter→PrintWriter— and collapsed it into one constructor call. The(File, Charset)constructor is the one you want for "open this file, write text to it, close it cleanly." - The
checkError()check ran before the close. Onceclose()runs, the flag check is harder to act on — you've left thetryblock already. Inside the block is the place to check. - The autoflush-on
PrintWriterwrappingSystem.outprinted the report column-aligned because eachprintfended in%n, which triggered the flush. With autoflush off, the report would still be correct but might appear in chunks. - The fourth block constructed a writer whose
writealways throws and pointed aPrintWriterat it. Theprintlnreturned normally.checkError()was the only way to find out the write had failed — there was no exception to catch. That's the trade-off of the swallow-and-flag design: convenient for casual code, dangerous if you don't check.
What's next
PrintWriter is the character-oriented file writer. Its sibling, Java PrintStream, is the byte-oriented one that powers System.out and System.err — same API, different ancestor, and the reason terminal output works for any character that fits in the platform's default charset.
Practice
You write a 10,000-line file with `PrintWriter` and never call `checkError()`. Three lines in the middle failed to write because of a disk-full condition. What does the resulting file look like, and what does the program report?