Skip to content
Java io 7 min read

PrintWriter

PrintWriter is the go-to class when you want convenient, formatted text output to a file, a network connection, or any Writer. It gives you the familiar println() and printf() methods you already know from System.out — but targeted at character streams instead of the console.

What Is PrintWriter?

PrintWriter lives in java.io and extends Writer. It wraps any existing Writer (or OutputStream) and adds a rich set of print methods that handle all Java primitive types, objects, and formatted strings. Unlike PrintStream, PrintWriter works at the character level and supports specifying a character encoding, making it portable and Unicode-friendly.

Key characteristics:

  • No checked IOException — methods like print() and println() never throw IOException. Instead, errors set an internal error flag you can check with checkError().
  • Optional auto-flush — you can configure it to flush automatically after every println(), printf(), or format() call.
  • Encoding support — you can specify a Charset so your output is always UTF-8 (or whatever you need), regardless of platform defaults.

Tip: If you just need console output, System.out (a PrintStream) is fine. Reach for PrintWriter when writing to files, network sockets, or any other Writer-based destination.

Constructors

PrintWriter has several constructors for different use cases:

ConstructorDescription
PrintWriter(Writer out)Wraps an existing Writer, no auto-flush
PrintWriter(Writer out, boolean autoFlush)Wraps a Writer; flushes after println/printf/format if true
PrintWriter(OutputStream out)Wraps an OutputStream; uses platform default encoding
PrintWriter(OutputStream out, boolean autoFlush)Same with auto-flush option
PrintWriter(String fileName)Creates a file by name; uses platform default encoding
PrintWriter(String fileName, Charset charset)Creates a file by name with explicit encoding (Java 10+)
PrintWriter(File file)Creates a named File; uses platform default encoding
PrintWriter(File file, Charset charset)Creates a File with explicit encoding (Java 10+)

Note: The constructors that accept a file name or File object open the file for writing (truncating any existing content). They are a shortcut — internally, Java wraps the file in a BufferedWriter for you.

Basic Usage

import java.io.PrintWriter;
import java.io.IOException;

public class BasicPrintWriter {
    public static void main(String[] args) throws IOException {
        try (PrintWriter pw = new PrintWriter("output.txt")) {
            pw.println("Hello, PrintWriter!");
            pw.println("Writing to a file is easy.");
            pw.printf("Pi is approximately %.5f%n", Math.PI);
        }
        System.out.println("File written successfully.");
    }
}

Output:

File written successfully.

The resulting output.txt contains:

Hello, PrintWriter!
Writing to a file is easy.
Pi is approximately 3.14159

The try-with-resources block calls close() automatically, which flushes the internal buffer and closes the underlying stream.

Specifying a Character Encoding

Always specify the charset explicitly to avoid platform-dependent encoding surprises:

import java.io.PrintWriter;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

public class EncodedPrintWriter {
    public static void main(String[] args) throws IOException {
        try (PrintWriter pw = new PrintWriter(
                new BufferedWriter(
                    new OutputStreamWriter(
                        new FileOutputStream("utf8_out.txt"),
                        StandardCharsets.UTF_8)))) {

            pw.println("Héllo Wörld — Unicode works great!");
            pw.printf("Amount: €%.2f%n", 1234.56);
        }
        System.out.println("UTF-8 file written.");
    }
}

Output:

UTF-8 file written.

Tip: In Java 10+, you can use the simpler new PrintWriter("file.txt", StandardCharsets.UTF_8) constructor instead of stacking streams manually.

println(), print(), and printf()

PrintWriter overloads its print methods for every type:

import java.io.PrintWriter;
import java.io.IOException;

public class PrintMethods {
    public static void main(String[] args) throws IOException {
        try (PrintWriter pw = new PrintWriter("types.txt")) {
            pw.println(42);               // int
            pw.println(3.14);             // double
            pw.println(true);             // boolean
            pw.println('A');              // char
            pw.println("Java");           // String
            pw.println(new int[]{1,2,3}); // Object (prints reference, not content)

            pw.print("no newline here");
            pw.println(" — newline now");

            pw.printf("Formatted: %s scored %d/%d%n", "Alice", 95, 100);
            pw.format("Same thing: %s scored %d/%d%n", "Bob",   88, 100);
        }
    }
}

Note: println(Object) calls String.valueOf(obj) internally, which calls toString(). For arrays, that prints something like [I@1a2b3c (the reference), not the contents. Use Arrays.toString() to get readable array output.

Auto-Flush

When auto-flush is enabled, PrintWriter flushes the underlying stream after every println(), printf(), or format() call. This is useful when writing to a socket or log stream where readers need data immediately:

import java.io.PrintWriter;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;

public class AutoFlushDemo {
    public static void main(String[] args) {
        // auto-flush = true: each println() flushes immediately
        PrintWriter pw = new PrintWriter(
            new OutputStreamWriter(System.out, StandardCharsets.UTF_8), true);

        pw.println("This line is flushed immediately.");
        pw.println("So is this one.");
        pw.close();
    }
}

Output:

This line is flushed immediately.
So is this one.

Warning: Auto-flush only triggers on println(), printf(), and format() — NOT on plain print() or write(). If you rely on auto-flush, make sure you use those specific methods.

Error Handling with checkError()

PrintWriter swallows IOExceptions silently and sets an internal error flag instead. Call checkError() to check whether any write has failed:

import java.io.PrintWriter;
import java.io.IOException;

public class ErrorCheck {
    public static void main(String[] args) throws IOException {
        try (PrintWriter pw = new PrintWriter("status.txt")) {
            pw.println("Writing line 1");
            pw.println("Writing line 2");

            if (pw.checkError()) {
                System.err.println("An I/O error occurred during writing!");
            } else {
                System.out.println("Write succeeded.");
            }
        }
    }
}

Output:

Write succeeded.

Warning: Once the error flag is set, it stays set even if subsequent writes succeed. Check checkError() after the critical writes, not just at the very end.

Writing to a Network Socket

PrintWriter is commonly used for socket-based communication because it layers cleanly over any OutputStream:

import java.io.PrintWriter;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.io.IOException;

public class SocketWrite {
    public static void send(String host, int port, String message) throws IOException {
        try (Socket socket = new Socket(host, port);
             PrintWriter pw = new PrintWriter(
                 new OutputStreamWriter(socket.getOutputStream(),
                                        StandardCharsets.UTF_8), true)) {
            pw.println(message); // auto-flush ensures server receives it immediately
        }
    }
}

The auto-flush here is important: the server needs to read the line before the client closes the connection. See Socket Programming for a full client/server example.

Stacking with BufferedWriter for Performance

When writing large amounts of text to a file, wrap a BufferedWriter between PrintWriter and the underlying stream to batch writes and reduce system calls:

import java.io.PrintWriter;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

public class BufferedPrintWriter {
    public static void main(String[] args) throws IOException {
        // Outer PrintWriter gives us println/printf convenience
        // Inner BufferedWriter reduces system calls with a 8 KB buffer
        try (PrintWriter pw = new PrintWriter(
                new BufferedWriter(new FileWriter("report.txt")))) {

            for (int i = 1; i <= 10_000; i++) {
                pw.printf("Row %d: value=%.4f%n", i, Math.random());
            }
        }
        System.out.println("Report written.");
    }
}

Output:

Report written.

Tip: The convenience constructors new PrintWriter("filename") add a BufferedWriter internally, so for simple file writes you don’t need to stack them yourself. Manual stacking is needed when you want a specific charset or a custom buffer size.

PrintWriter vs PrintStream

Both provide print(), println(), and printf(), but they differ in important ways:

FeaturePrintStreamPrintWriter
ExtendsFilterOutputStream (byte stream)Writer (character stream)
EncodingPlatform default (limited control)Full Charset support
Use caseSystem.out, System.errFiles, sockets, any Writer
Checked exceptionsNone thrownNone thrown
checkError()YesYes

For modern file and network output, prefer PrintWriter. PrintStream is mostly there for legacy compatibility and console output.

Under the Hood

Internally, PrintWriter delegates every print* and write* call to its wrapped Writer. The class keeps a boolean trouble field — the error flag set by checkError(). There is no internal buffer of its own; buffering is the responsibility of whatever Writer sits underneath (which is why stacking a BufferedWriter matters for performance).

The auto-flush mechanism is implemented in println(), printf(), and format() — after writing the content, they call out.flush() if the autoFlush flag is true. Plain write() and print() calls never flush regardless of the flag.

printf() and format() are identical — they both call new Formatter(this, locale).format(fmt, args) under the hood. The Locale-aware overloads (printf(Locale, String, Object...)) let you control decimal separators and number formatting for international output.

Thread safety: PrintWriter synchronizes on the wrapped Writer object (its lock). Method calls are individually thread-safe — one thread won’t corrupt another’s characters. However, interleaved println() calls from multiple threads can still produce mixed-up lines. For coordinated multi-threaded logging, use a dedicated logging framework.

Key Methods at a Glance

MethodDescription
print(T value)Print any type without a newline
println(T value)Print any type followed by a newline
printf(String fmt, Object... args)Formatted output (locale-sensitive overload available)
format(String fmt, Object... args)Identical to printf()
write(int c) / write(String s)Low-level character write (no auto-flush)
flush()Flush the underlying stream
close()Flush and close the stream
checkError()Returns true if any write failed
Last updated June 13, 2026
Was this helpful?