BufferedWriter
BufferedWriter wraps any Writer with an in-memory character buffer, reducing system calls by accumulating characters until the buffer fills, flush() is called, or close() is called. It adds two capabilities not present in Writer: newLine(), which writes the platform-specific line separator, and an optimized write(String, int, int) that avoids creating a char[] copy by writing directly from the String. BufferedWriter is the standard output partner to BufferedReader — together they provide efficient line-by-line text file processing. It is constructed either by wrapping a Writer (new BufferedWriter(new FileWriter(...))) or via Files.newBufferedWriter(path, charset, options), the modern idiomatic alternative. Like all buffered streams, correct usage requires try-with-resources to guarantee that buffered data is flushed and the file is closed even when exceptions occur. This entry covers construction and buffer sizing, all write methods and their interaction with the buffer, newLine() and its platform behavior, flush semantics including when explicit flush is necessary, the difference between close() and flush(), and performance patterns for high-throughput text writing.
Construction, Write Methods, and newLine()
// ── Construction patterns ─────────────────────────────────────────────
// Wrap FileWriter (Java 11+ charset constructor):
BufferedWriter bw1 = new BufferedWriter(
new FileWriter("output.txt", StandardCharsets.UTF_8));
// Custom 64KB buffer:
BufferedWriter bw2 = new BufferedWriter(
new FileWriter("large.txt", StandardCharsets.UTF_8), 65_536);
// Modern idiom: Files.newBufferedWriter
BufferedWriter bw3 = Files.newBufferedWriter(
Path.of("output.txt"), StandardCharsets.UTF_8); // CREATE + TRUNCATE_EXISTING default
// Append mode:
BufferedWriter bw4 = Files.newBufferedWriter(
Path.of("log.txt"), StandardCharsets.UTF_8,
StandardOpenOption.CREATE, StandardOpenOption.APPEND);
// ── Write methods ─────────────────────────────────────────────────────
try (BufferedWriter bw = Files.newBufferedWriter(
Path.of("out.txt"), StandardCharsets.UTF_8)) {
bw.write('H'); // single character (int → char)
bw.write(72); // same: 'H' (Unicode code point)
bw.write("Hello, World!"); // entire string → buffer
bw.write("Hello, World!", 7, 5); // "World" (offset=7, count=5)
char[] chars = {'J', 'a', 'v', 'a'};
bw.write(chars, 0, 4); // entire char array portion
bw.newLine(); // System.lineSeparator() → buffer
}
// ── newLine() vs explicit
──────────────────────────────────────────
try (BufferedWriter bw = Files.newBufferedWriter(
Path.of("platform.txt"), StandardCharsets.UTF_8)) {
bw.write("First line");
bw.newLine(); //
on Windows,
on Unix — correct for native tools
bw.write("Second line");
bw.write("
"); // always
— correct for JSON/CSV/HTTP/cross-platform text
}
// ── High-throughput line writing ──────────────────────────────────────
try (BufferedWriter bw = Files.newBufferedWriter(
Path.of("million_lines.txt"), StandardCharsets.UTF_8)) {
for (int i = 0; i < 1_000_000; i++) {
bw.write(Integer.toString(i));
bw.newLine(); // goes to buffer — system call only when buffer fills (~8KB)
}
} // close() flushes remaining buffer → all 1M lines written
// ── write(String) vs write(String, off, len) ─────────────────────────
String csv = "id,name,value,timestamp";
try (BufferedWriter bw = Files.newBufferedWriter(
Path.of("header.csv"), StandardCharsets.UTF_8)) {
bw.write(csv); // writes entire "id,name,value,timestamp"
bw.write(csv, 3, 4); // writes "name" (offset=3, count=4)
// write(String, off, len) avoids substring() allocation — writes directly from String
}Flush, Close, and try-with-resources Discipline
// ── try-with-resources: guaranteed flush and close ───────────────────
// CORRECT: close() is guaranteed even on exception
try (BufferedWriter bw = Files.newBufferedWriter(
Path.of("safe.txt"), StandardCharsets.UTF_8)) {
for (int i = 0; i < 100; i++) {
bw.write("Entry " + i);
bw.newLine();
if (i == 50) throw new RuntimeException("Simulated failure");
}
} // close() called → flush() called → file has entries 0-50 (up to the exception)
// WRONG: exception before close() loses buffered data
BufferedWriter bw = Files.newBufferedWriter(
Path.of("unsafe.txt"), StandardCharsets.UTF_8);
bw.write("Important data");
bw.newLine();
throw new RuntimeException("crash"); // buffer NOT flushed — "Important data" is LOST
bw.close(); // never reached
// ── Reverse close order with separate resource declarations ───────────
// This ensures flush propagates correctly:
try (FileOutputStream fos = new FileOutputStream("layered.txt");
OutputStreamWriter osw = new OutputStreamWriter(fos, StandardCharsets.UTF_8);
BufferedWriter bw = new BufferedWriter(osw)) {
bw.write("Layered stream test");
bw.newLine();
}
// Close order: bw.close() → flushes to osw → osw.close() → flushes to fos → fos.close()
// ── Explicit flush: when to call it ──────────────────────────────────
// Network protocol: flush after sending request
try (Socket socket = new Socket("api.example.com", 80);
BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.US_ASCII));
BufferedReader reader = new BufferedReader(
new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8))) {
writer.write("GET / HTTP/1.1
");
writer.write("Host: api.example.com
");
writer.write("
");
writer.flush(); // CRITICAL: without flush, server never receives the request
// Now read response:
String statusLine = reader.readLine();
System.out.println("Response: " + statusLine);
}
// Progress logging: flush after each entry
try (BufferedWriter log = Files.newBufferedWriter(
Path.of("progress.log"), StandardCharsets.UTF_8,
StandardOpenOption.CREATE, StandardOpenOption.APPEND)) {
for (int step = 0; step < 100; step++) {
processStep(step);
log.write(LocalDateTime.now() + " Step " + step + " complete");
log.newLine();
log.flush(); // visible immediately — survives crash after this line
}
}
// ── PrintWriter wrapping BufferedWriter: adds formatted output ────────
try (PrintWriter pw = new PrintWriter(Files.newBufferedWriter(
Path.of("report.txt"), StandardCharsets.UTF_8))) {
pw.printf("Report date: %s%n", LocalDate.now());
pw.printf("Total records: %,d%n", 1_234_567);
pw.printf("Average value: %.2f%n", 42.5);
pw.println("--- End of Report ---");
// PrintWriter.close() closes the wrapped BufferedWriter → flushes → closes
}BufferedWriter vs PrintWriter and Performance Patterns
// ── BufferedWriter vs PrintWriter: error handling ────────────────────
// PrintWriter: silent errors
try (PrintWriter pw = new PrintWriter(Files.newBufferedWriter(
Path.of("out.txt"), StandardCharsets.UTF_8))) {
pw.println("Line 1");
// If the disk is full, pw.println does NOT throw — error is silent
if (pw.checkError()) {
System.out.println("PrintWriter had an error — but data was already lost");
}
}
// BufferedWriter: explicit exceptions
try (BufferedWriter bw = Files.newBufferedWriter(
Path.of("out.txt"), StandardCharsets.UTF_8)) {
bw.write("Line 1");
bw.newLine();
// If the disk is full, bw.write() or bw.newLine() throws IOException
// Exception propagates immediately — caller handles it
} catch (IOException e) {
System.out.println("Write failed: " + e.getMessage()); // know exactly when failure occurred
}
// ── High-throughput: build full lines, write once per line ────────────
// SLOW: many small write() calls
try (BufferedWriter bw = Files.newBufferedWriter(
Path.of("slow.csv"), StandardCharsets.UTF_8)) {
bw.write("Name");
bw.write(",");
bw.write("Score");
bw.write(",");
bw.write("Date");
bw.newLine(); // 6 write calls per line
}
// FAST: build each line, one write() per line
try (BufferedWriter bw = Files.newBufferedWriter(
Path.of("fast.csv"), StandardCharsets.UTF_8)) {
bw.write("Name,Score,Date");
bw.newLine(); // 2 write calls per line (write + newLine)
// For data rows, build line in StringBuilder:
StringBuilder sb = new StringBuilder(128);
for (DataRecord record : records) {
sb.setLength(0); // reset without new allocation
sb.append(record.name()).append(',')
.append(record.score()).append(',')
.append(record.date());
bw.write(sb.toString());
bw.newLine();
}
}
// ── Writing CSV with proper escaping via Apache Commons CSV ──────────
try (BufferedWriter bw = Files.newBufferedWriter(
Path.of("data.csv"), StandardCharsets.UTF_8);
CSVPrinter csv = new CSVPrinter(bw, CSVFormat.DEFAULT.withHeader("Name","Age","City"))) {
csv.printRecord("Alice", 30, "New York");
csv.printRecord("Bob, Jr.", 25, "London"); // comma in name — library handles quoting
csv.printRecord("Carol", 35, "São Paulo"); // Unicode — UTF-8 handles it
} // bw.close() called → all records flushed
// ── Writing JSON lines (JSONL) format ────────────────────────────────
ObjectMapper mapper = new ObjectMapper();
try (BufferedWriter bw = Files.newBufferedWriter(
Path.of("data.jsonl"), StandardCharsets.UTF_8)) {
for (DataRecord record : records) {
bw.write(mapper.writeValueAsString(record)); // one JSON object per line
bw.write("
"); // JSONL uses
, not platform newline
}
}