PrintWriter
PrintWriter is a character-based output class that wraps any Writer or OutputStream and adds convenience methods for printing formatted representations of all Java primitive types, strings, and objects. Its defining characteristic is that none of its print(), println(), and printf() methods throw checked IOException — errors are silently swallowed and can only be detected after the fact by calling checkError(). This makes PrintWriter easy to use interactively and in situations where I/O failure is genuinely unrecoverable (writing to System.out, generating diagnostic output), but makes it dangerous for critical data writing where exceptions must be caught and handled. PrintWriter can auto-flush on println(), printf(), and format() calls when constructed with autoFlush=true, which is useful for interactive console output and network protocol streams. Its printf() and format() methods delegate to java.util.Formatter, enabling C-style formatted output with full locale awareness. This entry covers all constructor variants and their autoFlush and buffering behavior, every print/println/printf method, the checkError() error detection model, the difference between PrintWriter and PrintStream, charset handling, and when PrintWriter is the right choice versus BufferedWriter.
Constructors — Auto-flush, Buffering, and Charset
// ── Writer-based constructors ─────────────────────────────────────────
// No buffering added — FileWriter is unbuffered, every write hits OS:
PrintWriter pw1 = new PrintWriter(new FileWriter("out.txt", StandardCharsets.UTF_8));
// Correct: wrap FileWriter in BufferedWriter first:
PrintWriter pw2 = new PrintWriter(
new BufferedWriter(new FileWriter("out.txt", StandardCharsets.UTF_8)));
// With autoFlush — println/printf/format trigger flush automatically:
PrintWriter pw3 = new PrintWriter(
new BufferedWriter(new FileWriter("out.txt", StandardCharsets.UTF_8)), true);
// ── OutputStream-based constructors ──────────────────────────────────
// Internally creates OutputStreamWriter(platform charset) + BufferedWriter:
PrintWriter pw4 = new PrintWriter(System.out); // stdout
PrintWriter pw5 = new PrintWriter(System.out, true); // autoFlush
PrintWriter pw6 = new PrintWriter(new FileOutputStream("out.txt")); // file
// Java 10+: explicit charset:
PrintWriter pw7 = new PrintWriter(
new FileOutputStream("out.txt"), true, StandardCharsets.UTF_8);
// ── File/String constructors ──────────────────────────────────────────
try {
// Opens file, creates if absent, truncates if exists:
PrintWriter pw8 = new PrintWriter("output.txt"); // default charset
PrintWriter pw9 = new PrintWriter(new File("output.txt")); // default charset
PrintWriter pw10 = new PrintWriter("output.txt", "UTF-8"); // explicit charset
PrintWriter pw11 = new PrintWriter(new File("output.txt"), "UTF-8"); // explicit charset
// Java 10+:
PrintWriter pw12 = new PrintWriter("output.txt", StandardCharsets.UTF_8); // Charset object
} catch (FileNotFoundException e) {
System.err.println("Cannot open file: " + e.getMessage());
}
// ── Preferred modern construction ─────────────────────────────────────
// Most explicit: BufferedWriter from Files, explicit charset, explicit options
try (PrintWriter pw = new PrintWriter(Files.newBufferedWriter(
Path.of("output.txt"),
StandardCharsets.UTF_8,
StandardOpenOption.CREATE,
StandardOpenOption.TRUNCATE_EXISTING))) {
pw.println("UTF-8 with explicit options");
}
// ── autoFlush: only println/printf/format trigger it, NOT print/write ─
PrintWriter autoFlushing = new PrintWriter(System.out, true);
autoFlushing.print("not flushed"); // NO auto-flush — stays in buffer
autoFlushing.println("flushed now"); // println → auto-flush triggered
autoFlushing.printf("also flushed%n"); // printf → auto-flush triggeredPrint Methods, printf/format, and Error Handling
// ── print() overloads for all types ──────────────────────────────────
try (PrintWriter pw = new PrintWriter(Files.newBufferedWriter(
Path.of("out.txt"), StandardCharsets.UTF_8))) {
pw.print(true); // "true"
pw.print('A'); // "A"
pw.print(42); // "42"
pw.print(3.14f); // "3.14"
pw.print(3.14159265); // "3.141592653589793"
pw.print(9_876_543_210L);// "9876543210"
pw.print(new char[]{'H','i'}); // "Hi"
pw.print((Object)null); // "null" — no NullPointerException
pw.print("Hello"); // "Hello"
}
// ── println() adds line separator ─────────────────────────────────────
try (PrintWriter pw = new PrintWriter(Files.newBufferedWriter(
Path.of("lines.txt"), StandardCharsets.UTF_8))) {
pw.println("Line 1"); // "Line 1" + System.lineSeparator()
pw.println(42); // "42" + System.lineSeparator()
pw.println(); // just System.lineSeparator() (blank line)
}
// ── printf/format: C-style formatted output ───────────────────────────
try (PrintWriter pw = new PrintWriter(Files.newBufferedWriter(
Path.of("report.txt"), StandardCharsets.UTF_8))) {
// Basic formatting:
pw.printf("Name: %-20s Age: %3d%n", "Alice", 30); // left-aligned name, right-aligned age
pw.printf("Pi: %.5f%n", Math.PI); // 5 decimal places: 3.14159
pw.printf("Hex: %08X%n", 255); // zero-padded hex: 000000FF
pw.printf("Sci: %e%n", 1_234_567.89); // 1.234568e+06
pw.printf("Bool: %b%n", null); // "false" (null → false for %b)
// %n vs
: use %n for portable platform newline in format strings:
pw.printf("Line 1%nLine 2%n"); //
on Windows,
on Unix
pw.printf("Line 1
Line 2
"); // always
— for cross-platform formats
// Locale-specific number formatting:
pw.printf(Locale.GERMANY, "Preis: %.2f EUR%n", 1234.56); // "1234,56" (comma decimal)
pw.printf(Locale.US, "Price: $%.2f%n", 1234.56); // "1234.56" (period decimal)
// Date/time:
pw.printf("Date: %tY-%tm-%td%n", LocalDate.now(), LocalDate.now(), LocalDate.now());
}
// ── Silent error model: checkError() ─────────────────────────────────
PrintWriter riskWriter = new PrintWriter(new FileOutputStream("risk.txt"));
riskWriter.println("Important data");
// Simulate: file system becomes unavailable after open (in practice this would happen
// on disk full, network share disconnect, etc.)
riskWriter.println("More important data");
riskWriter.println("Critical record");
// checkError() flushes, then reports accumulated errors:
if (riskWriter.checkError()) {
System.err.println("WARNING: Write errors occurred — data may be incomplete");
// But it's too late to know WHICH writes failed
}
// ── Compare: BufferedWriter throws immediately ────────────────────────
try (BufferedWriter bw = Files.newBufferedWriter(Path.of("safe.txt"))) {
bw.write("Important data");
bw.newLine();
// If disk is full: IOException thrown HERE — caller handles it immediately
} catch (IOException e) {
System.err.println("Write failed at known point: " + e.getMessage());
// Recovery action: retry, alert, abort cleanly
}PrintWriter vs PrintStream, System.out, and When to Use Each
// ── PrintWriter vs PrintStream ────────────────────────────────────────
// System.out is a PrintStream (byte-based, platform encoding):
System.out.println("Hello"); // encodes "Hello" to bytes using platform charset
System.out.printf("Pi=%.2f%n", Math.PI);
// PrintWriter wrapping System.out (character-based, explicit charset):
PrintWriter stdout = new PrintWriter(
new BufferedWriter(new OutputStreamWriter(System.out, StandardCharsets.UTF_8)),
true); // autoFlush: println triggers immediate flush
stdout.println("Hello from PrintWriter");
stdout.printf("Pi=%.2f%n", Math.PI);
stdout.flush(); // explicit flush for non-println writes
// ── Servlet response pattern (javax.servlet / jakarta.servlet) ────────
// HttpServletResponse.getWriter() returns a PrintWriter:
@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
resp.setContentType("text/html; charset=UTF-8");
resp.setCharacterEncoding("UTF-8");
try (PrintWriter out = resp.getWriter()) {
out.println("<!DOCTYPE html>");
out.println("<html><body>");
out.printf("<h1>Hello, %s!</h1>%n", req.getParameter("name"));
out.println("</body></html>");
}
// checkError() can detect write failures to the client (e.g., client disconnected):
// if (out.checkError()) log.warn("Client disconnected during response");
}
// ── CLI tool: PrintWriter for formatted console output ────────────────
public static void printTable(List<DataRow> rows) {
try (PrintWriter out = new PrintWriter(
new BufferedWriter(new OutputStreamWriter(System.out, StandardCharsets.UTF_8)),
false)) { // no autoFlush — print header and all rows, then flush once
out.printf("%-20s %10s %15s%n", "Name", "Count", "Percentage");
out.printf("%-20s %10s %15s%n", "-".repeat(20), "-".repeat(10), "-".repeat(15));
for (DataRow row : rows) {
out.printf("%-20s %,10d %14.2f%%%n",
row.name(), row.count(), row.percentage());
}
out.flush(); // one flush for all output
}
}
// ── String.format() vs printf() performance trade-off ─────────────────
// String.format() creates intermediate String — useful when string is reused:
String formatted = String.format("User: %-20s ID: %08d", user.name(), user.id());
logger.info(formatted); // reused for logging
pw.println(formatted); // and for output
// printf() writes directly — no intermediate String — better for write-once output:
pw.printf("User: %-20s ID: %08d%n", user.name(), user.id());
// ── Wrapping PrintWriter in try-with-resources ────────────────────────
// PrintWriter.close() closes the underlying Writer — always use try-with-resources:
try (PrintWriter pw = new PrintWriter(Files.newBufferedWriter(
Path.of("output.txt"), StandardCharsets.UTF_8))) {
pw.println("Data written safely");
pw.printf("Formatted: %d%n", 42);
} // close() flushes BufferedWriter, closes FileOutputStream
// Check for errors after close (unusual pattern — only for critical files):
try (PrintWriter pw = new PrintWriter(Files.newBufferedWriter(
Path.of("critical.txt"), StandardCharsets.UTF_8))) {
pw.println("Critical data");
pw.println("More critical data");
boolean hadError = pw.checkError(); // checkError before close to detect write failures
if (hadError) throw new IOException("PrintWriter reported write error");
}