BufferedReader and BufferedWriter

Reading or writing a file one character at a time works, but it's painfully slow, because every call goes all the way to the operating system. Buffered streams collect data in memory and move it in big blocks. You'll measure the difference, learn the readLine() loop you'll use for the rest of your career, and learn why forgetting close() silently loses data.

Module 13 · File Handling and I/O⏱ 22 minIntermediate9 practice problems
1 The story

The export that took four minutes

The canteen now has a year of orders: about 2 lakh lines. Riya's export writes them straight to a FileOutputStream, a few bytes at a time. It takes minutes. Worse, one night the last few hundred orders are simply missing from the file.

one letter per trip app disk 2,00,000 trips collect, then one trip app buffer disk a few hundred trips myphatshala.in
Riya

Sir, why is writing a file so slow? My laptop has an SSD!

Arjun Sir

The disk isn't the slow part. Every little write is a request to the operating system, and you're making two lakh of them. Put a buffer in between: collect 8 KB in memory, then send it in one go.

Riya

And the missing orders at the end?

Arjun Sir

Those were still sitting in a buffer when your program stopped. A buffer must be flushed, and close() does that. Let's see both.

2 Definition

What is buffered I/O?

Formal definition (write this in exams)

Buffered I/O places an in-memory array (a buffer) between a program and an underlying stream, so that many small read or write operations are served from or collected in memory and transferred to the underlying stream in large blocks, reducing costly system calls. In Java, BufferedReader and BufferedWriter wrap any Reader or Writer (default buffer 8,192 characters), and BufferedInputStream and BufferedOutputStream wrap byte streams. BufferedReader.readLine() returns the next line without its terminator, or null at end of stream. Output buffers are written when full, on flush(), or on close().

In simple words

Don't run to the post office with every letter. Fill a bag, then go once. But remember to actually take the bag before you leave.

Words you'll meet in this lesson

BufferAn array in memory where data waits before moving on.
System callA request from your program to the OS (read, write). Relatively expensive.
flush()Send whatever is in the buffer now, without closing.
close()Flush, then release the file. Closing the outer stream closes the inner ones.
readLine()Next line of text without the line break; null at the end.
DecoratorAn object that wraps another of the same type to add a feature.

Plain vs buffered

Plain file streamsBuffered wrappers
Read a lineNo such methodreadLine()
Write a line breakwrite("\n")newLine() (platform separator)
OS calls for many small writesFileOutputStream: one per call. (FileWriter has a small byte buffer inside its encoder, but every call still goes through it.)One per full buffer
Data waits in memory?LittleYes, until flush/close
Create the modern way—Files.newBufferedReader/Writer(path)

Exam tip: “Buffered streams improve performance by reducing the number of I/O operations. readLine() returns null at end of file. Always close() (or flush()) a buffered writer, or buffered data may be lost.” Also name the pattern: wrapping streams is the decorator design pattern.

3 Types and ways to use it

The buffered family

// classic: wrap a file stream in a buffer
BufferedReader br = new BufferedReader(new FileReader("in.txt", StandardCharsets.UTF_8));
BufferedWriter bw = new BufferedWriter(new FileWriter("out.txt", StandardCharsets.UTF_8));
// for bytes
InputStream in = new BufferedInputStream(new FileInputStream("photo.jpg"));
// modern shortcut (UTF-8, already buffered)
BufferedReader r = Files.newBufferedReader(Path.of("in.txt"));
BufferedWriter w = Files.newBufferedWriter(Path.of("out.txt"));

BufferedReader

Reads text in blocks; readLine() and lines() for line-by-line processing.

text in

BufferedWriter

Collects text; newLine() writes the platform line separator.

text out

Buffered byte streams

BufferedInputStream, BufferedOutputStream for images, PDFs, binary data.

bytes

Files helpers

Files.newBufferedReader/Writer: buffered and UTF-8 in one call.

recommended

The data that wasn't there

import java.io.*;
import java.nio.file.*;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedWriter w = new BufferedWriter(new FileWriter("invoice.txt"));
        w.write("INV-501 Rs 2360");
        System.out.println("on disk now: " + Files.size(Path.of("invoice.txt")) + " bytes");
        w.close();
        System.out.println("after close: " + Files.size(Path.of("invoice.txt")) + " bytes");
    }
}
on disk now: 0 bytes after close: 15 bytes

Unclosed buffered writers lose data. Nothing is wrong with the code until the very end: the text sits in memory and the file stays empty. If the program crashes or exits first, it's gone. Use try-with-resources every time, and call flush() if another program must see the data before you close.

4 Step by step

Inside the readLine() loop

  1. Wrap the reader

    try (BufferedReader br = Files.newBufferedReader(Path.of("orders.txt"))) {
    A decoder reader on the file, wrapped by a BufferedReader with an empty char[8192].
  2. First readLine(): fill

        String line;
        while ((line = br.readLine()) != null) {
    The buffer is empty, so the reader asks the file for up to 8,192 characters in one call.
  3. Cut out one line

    It scans forward from pos to the next \n (or \r\n), creates a String from those characters, and moves pos past the line break.
  4. Next calls: from memory

    Following lines come straight from the array until it runs out; then one more big read refills it.
  5. End

    When the file has no more data, readLine() returns null, the loop ends, and try-with-resources closes the reader and the file.
5 See it happen

Buffers on the way out and on the way in

Left: writing with and without a buffer. Right: a BufferedReader on the heap, filling its array once and serving lines from it.

Why buffering is fast, both views 6 steps
What you see out.write(b); // FileOutputStream, x5 out.write(b); // FileOutputStream, x5 bw.write(line); // BufferedWriter bw.write(line); // more lines // buffer full, or flush()/close() bw.close(); // flushes, then closes program disk 5 trips to the disk buffer (8 KB) 1 trip carries everything close() = flush + release the file empty again No buffer: each write hits the disk. Many small trips: slow. With a buffer: writes go to memory. The buffer fills up. One big write to disk. close() flushes the rest. myphatshala.in
Behind the screen HEAP #1 BufferedReader in → #2 cb → char[8192] pos 0 0 12 24 36 — #2 FileReader (orders.txt) char[] holds lines 1–3 (whole small file) main stack line = "101,dosa,80" line = "102,idli,40" line = "103,vada,30" line = null (end) 1 read from the file, 3 lines served readLine() returns null at the end A reader wrapped in a buffered reader. First readLine: one big read. Line 1 from memory. Line 2 from memory. Line 3 from memory. No more lines: null. myphatshala.in
6 Real-world use

Buffered I/O in real systems

Practically every file and network stream in production Java code is buffered.

Watch it with an example: a bank's nightly statement file

A bank writes 2 lakh transaction lines every night. Without a buffer, each line is a separate trip to the disk; with one, lines are sent in big blocks.

2,00,000 lines, buffered vs unbuffered 5 steps
Animated example unbuffered BufferedWriter writes to OS: writes to OS: 50,000 1,20,000 2,00,000 2,00,000 ~150 ~350 ~600 ~600 one per line one per 8 KB block Same file, far fewer OS calls: the job finishes much sooner writing 2,00,000 lines of about 25 characters… The nightly job starts. A quarter of the way. Over half done. Finished. Why buffering wins. myphatshala.in

1. Log processing

Support teams scan gigabytes of logs for errors, reading line by line so memory stays tiny.

app.log (5 GB)→readLine() loop→errors.txt

2. Bulk exports

Statements, GST returns and data dumps with lakhs of lines are written through BufferedWriter.

2 lakh rows→BufferedWriter→statement.csv

3. Network sockets

Reading an HTTP response or a chat protocol line by line uses a BufferedReader on the socket's InputStream.

socket→BufferedReader(InputStreamReader)→lines

4. Competitive programming and input

Fast solutions read huge inputs with BufferedReader instead of Scanner, which does more work per token.

10 lakh numbers→BufferedReader(System.in)→accepted in time

Counts and sizes are illustrative; exact numbers depend on line lengths and buffer sizes.

7 Run the code

Write a menu, read it line by line

import java.io.*;
import java.nio.file.*;

public class Main {
    public static void main(String[] args) throws IOException {
        try (BufferedWriter w = Files.newBufferedWriter(Path.of("menu.txt"))) {
            w.write("Masala Dosa,80");
            w.newLine();
            w.write("Idli Sambar,50");
            w.newLine();
            w.write("Filter Coffee,25");
            w.newLine();
        }
        try (BufferedReader r = Files.newBufferedReader(Path.of("menu.txt"))) {
            String line;
            while ((line = r.readLine()) != null) {
                String[] f = line.split(",");
                System.out.printf("%-14s Rs %3s%n", f[0], f[1]);
            }
        }
    }
}
Masala Dosa Rs 80 Idli Sambar Rs 50 Filter Coffee Rs 25
  • Files.newBufferedWriter(Path.of("menu.txt"))Buffered, UTF-8, creates or replaces the file.
  • w.newLine()Writes the line separator for the current OS.
  • (line = r.readLine()) != nullRead and test in one step; stops at end of file.
  • printf("%-14s Rs %3s%n", …)Aligned columns: name left-aligned in 14 characters, price right-aligned in 3.
8 Line by line

Reading orders with readLine()

Recorded from the real JVM. Watch line on the stack take each value, and total grow; on the heap, the BufferedReader holds the FileReader it wraps.

Worked examples: real problems, solved and traced in memory

Three real uses: proving how many OS calls a buffer saves, writing a report with BufferedWriter, and filtering a large log line by line. Watch the stack: only one line is held at a time.

Example 1Performance · proof

Count the trips to the OS

Problem statement

Prove that buffering reduces system calls. Write 20 bytes one at a time, first straight to a FileOutputStream, then through a BufferedOutputStream with a tiny 8-byte buffer (so the trace stays short; the real default is 8,192). Count the calls that reach the file stream with a small counting wrapper.

Input
20 single-byte writes, buffer of 8 bytes
Expected output
unbuffered: 20 calls to the file
buffered:   3 calls to the file

Approach

  1. A Counting class extends FilterOutputStream and counts every write that reaches it.
  2. Put it directly under the loop, then under a BufferedOutputStream.
  3. Compare the counts. Each call into a FileOutputStream is one OS call.

Solution: run it live

What happens in memory, step by step

Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.

Example 2Payroll · reports

Write a salary-slip summary with BufferedWriter

Problem statement

Payroll must write one summary line per employee to payroll.txt, then read the file back with BufferedReader to confirm the line count and the total payout.

Input
Asha 52000, Ravi 47500, Meena 61000, Imran 39000
Expected output
4 slips, total payout Rs 199500

Approach

  1. Files.newBufferedWriter gives a BufferedWriter with UTF-8.
  2. Write each line with write + newLine().
  3. Read back with Files.newBufferedReader and readLine().

Solution: run it live

What happens in memory, step by step

Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.

Example 3Operations · logs

Pull the errors out of a big log

Problem statement

A server log has thousands of lines. Support needs only the ERROR lines, saved to errors.txt. Read line by line (never the whole file at once), write matching lines, and report counts.

Input
a generated log of 12 lines, every 4th an ERROR (real logs have millions)
Expected output
read 12 lines, saved 3 errors
first error: ERROR payment timeout #4

Approach

  1. Generate a test log with a BufferedWriter.
  2. Open a BufferedReader on the log and a BufferedWriter on errors.txt in one try.
  3. For each line containing "ERROR", write it out. Only one line is in memory at a time.

Solution: run it live

What happens in memory, step by step

Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.

9 Quiz and revision

Check yourself

Quick revision

Lesson 13.3 in one screen

  • A buffer collects data in memory so the OS is called once per block, not once per character.
  • Wrap: new BufferedReader(reader), new BufferedWriter(writer), or use Files.newBufferedReader/Writer.
  • Loop: while ((line = br.readLine()) != null); lines come without the line break.
  • newLine() writes the platform separator.
  • Buffered output is only on disk after flush() or close(); use try-with-resources.
  • Closing the outer wrapper closes the inner streams too.
  • Process big files line by line: memory stays constant.

Interview and viva questions

Why is BufferedReader faster than FileReader?

Every FileReader.read() call goes through the charset decoder and its locking for a single character. BufferedReader pulls 8,192 characters at once into its own array and serves later reads with plain array access. With raw byte streams such as FileInputStream, where each read() is a system call, the difference is even larger. BufferedReader also adds readLine().

What is the difference between flush() and close()?

flush() writes out buffered data but keeps the stream open for more writes. close() flushes and then releases the stream and its underlying resources; the stream can't be used afterwards.

BufferedReader vs Scanner?

BufferedReader reads lines of text quickly with a large buffer and does no parsing. Scanner parses tokens (ints, words) with regular expressions, which is convenient but slower, and has a smaller buffer. For large inputs, read lines with BufferedReader and parse them yourself.

What design pattern do Java I/O wrappers use?

The decorator pattern: a BufferedReader is a Reader and wraps a Reader, adding buffering without changing the interface. You can stack decorators, e.g. a BufferedReader over an InputStreamReader over a FileInputStream.

When must you call flush() explicitly?

When someone else must see the data before you close: e.g. sending a message over a socket and waiting for a reply, or writing a progress file that another program monitors. Otherwise, closing (via try-with-resources) is enough.

Practice problems

Read with BufferedReader.readLine(), write with BufferedWriter, and never forget to flush or close. Each program creates its own files. The last three are business tasks.

P1Easy

Read every line

Write three city names to cities.txt, then print each line with its number using BufferedReader.

Input
Pune, Nagpur, Nashik
Expected output
1: Pune
2: Nagpur
3: Nashik
Show solution and explanation
import java.io.*;
import java.nio.file.*;

public class Main {
    public static void main(String[] args) throws IOException {
        Files.writeString(Path.of("cities.txt"), "Pune\nNagpur\nNashik\n");
        try (BufferedReader br = new BufferedReader(new FileReader("cities.txt"))) {
            String line;
            int n = 1;
            while ((line = br.readLine()) != null) {
                System.out.println(n++ + ": " + line);
            }
        }
    }
}
1: Pune 2: Nagpur 3: Nashik

How it works: the assignment inside the condition reads a line and checks it for null in one go. This loop shape is worth memorising.

P2Easy

Write lines with newLine()

Write three lines with a BufferedWriter and newLine(), then print how many lines the file has.

Input
(none)
Expected output
3 tasks saved
Show solution and explanation
import java.io.*;
import java.nio.file.*;

public class Main {
    public static void main(String[] args) throws IOException {
        try (BufferedWriter bw = new BufferedWriter(new FileWriter("todo.txt"))) {
            bw.write("pay electricity bill");
            bw.newLine();
            bw.write("book train ticket");
            bw.newLine();
            bw.write("call bank");
            bw.newLine();
        }
        System.out.println(Files.readAllLines(Path.of("todo.txt")).size() + " tasks saved");
    }
}
3 tasks saved

How it works: newLine() writes the platform's line separator (\n on Linux and macOS, \r\n on Windows).

P3Easy

flush() before reading

Write hello with a BufferedWriter but don't close it. Print the file size, then call flush() and print it again.

Input
(none)
Expected output
before flush: 0 bytes
after flush:  5 bytes
Show solution and explanation
import java.io.*;
import java.nio.file.*;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedWriter bw = new BufferedWriter(new FileWriter("draft.txt"));
        bw.write("hello");
        System.out.println("before flush: " + Files.size(Path.of("draft.txt")) + " bytes");
        bw.flush();
        System.out.println("after flush:  " + Files.size(Path.of("draft.txt")) + " bytes");
        bw.close();
    }
}
before flush: 0 bytes after flush: 5 bytes

How it works: until the buffer is flushed, the data exists only in your program's memory. If the program crashed before flush() or close(), those 5 bytes would be lost.

P4Medium

Longest line

Find the longest line in a file of product names and print it with its length.

Input
4 product names
Expected output
Basmati Rice 5kg (16 chars)
Show solution and explanation
import java.io.*;
import java.nio.file.*;
import java.util.List;

public class Main {
    public static void main(String[] args) throws IOException {
        Files.write(Path.of("products.txt"), List.of("Tea", "Basmati Rice 5kg", "Sugar", "Sunflower Oil 1L"));
        String longest = "";
        try (BufferedReader br = Files.newBufferedReader(Path.of("products.txt"))) {
            String line;
            while ((line = br.readLine()) != null) {
                if (line.length() > longest.length()) longest = line;
            }
        }
        System.out.println(longest + " (" + longest.length() + " chars)");
    }
}
Basmati Rice 5kg (16 chars)

How it works: only the current line and the best so far are kept, so this works for files of any size.

P5Medium

Count lines with lines()

Use BufferedReader.lines() to count non-empty lines in a file that has some blank lines.

Input
5 lines, 2 blank
Expected output
non-empty lines: 3
Show solution and explanation
import java.io.*;
import java.nio.file.*;

public class Main {
    public static void main(String[] args) throws IOException {
        Files.writeString(Path.of("notes.txt"), "buy milk\n\ncall Ravi\n\nsubmit GST\n");
        try (BufferedReader br = Files.newBufferedReader(Path.of("notes.txt"))) {
            long filled = br.lines().filter(l -> !l.isBlank()).count();
            System.out.println("non-empty lines: " + filled);
        }
    }
}
non-empty lines: 3

How it works: lines() turns the reader into a lazy Stream of lines (Lesson 14.3), reading as it goes.

P6Medium

Copy a text file line by line

Copy menu.txt to menu-copy.txt using a BufferedReader and BufferedWriter, converting each line to upper case.

Input
3 dishes
Expected output
[MASALA DOSA, IDLI SAMBAR, FILTER COFFEE]
Show solution and explanation
import java.io.*;
import java.nio.file.*;
import java.util.List;

public class Main {
    public static void main(String[] args) throws IOException {
        Files.write(Path.of("menu.txt"), List.of("masala dosa", "idli sambar", "filter coffee"));
        try (BufferedReader in = Files.newBufferedReader(Path.of("menu.txt"));
             BufferedWriter out = Files.newBufferedWriter(Path.of("menu-copy.txt"))) {
            String line;
            while ((line = in.readLine()) != null) {
                out.write(line.toUpperCase());
                out.newLine();
            }
        }
        System.out.println(Files.readAllLines(Path.of("menu-copy.txt")));
    }
}
[MASALA DOSA, IDLI SAMBAR, FILTER COFFEE]

How it works: resources in one try are closed in reverse order: the writer first (flushing it), then the reader.

P7Business problem

Bank statement summary

statement.csv has date,type,amount lines with type CR or DR. Read it with BufferedReader and print total credits, total debits and the closing balance from an opening balance of 10,000.

Input
5 transactions
Expected output
credits 28000, debits 10150, closing 27850
Show solution and explanation
import java.io.*;
import java.nio.file.*;
import java.util.List;

public class Main {
    public static void main(String[] args) throws IOException {
        Files.write(Path.of("statement.csv"), List.of(
            "01-10,CR,25000", "03-10,DR,1200", "07-10,DR,8500", "15-10,CR,3000", "20-10,DR,450"));
        long cr = 0, dr = 0;
        try (BufferedReader br = Files.newBufferedReader(Path.of("statement.csv"))) {
            String line;
            while ((line = br.readLine()) != null) {
                String[] f = line.split(",");
                long amt = Long.parseLong(f[2]);
                if (f[1].equals("CR")) cr += amt; else dr += amt;
            }
        }
        System.out.println("credits " + cr + ", debits " + dr + ", closing " + (10000 + cr - dr));
    }
}
credits 28000, debits 10150, closing 27850

How it works: streaming through the statement keeps memory use constant whether it has 5 lines or 5 million.

P8Business problem

Split a big order file by city

orders.csv has id,city,amount. Write Pune orders to pune.csv and all others to others.csv, then print the line counts.

Input
6 orders
Expected output
pune.csv: 3, others.csv: 3
Show solution and explanation
import java.io.*;
import java.nio.file.*;
import java.util.List;

public class Main {
    public static void main(String[] args) throws IOException {
        Files.write(Path.of("orders.csv"), List.of("1,Pune,450", "2,Mumbai,900", "3,Pune,120", "4,Nagpur,300", "5,Pune,75", "6,Mumbai,640"));
        try (BufferedReader in = Files.newBufferedReader(Path.of("orders.csv"));
             BufferedWriter pune = Files.newBufferedWriter(Path.of("pune.csv"));
             BufferedWriter rest = Files.newBufferedWriter(Path.of("others.csv"))) {
            String line;
            while ((line = in.readLine()) != null) {
                BufferedWriter target = line.split(",")[1].equals("Pune") ? pune : rest;
                target.write(line);
                target.newLine();
            }
        }
        System.out.println("pune.csv: " + Files.readAllLines(Path.of("pune.csv")).size()
                + ", others.csv: " + Files.readAllLines(Path.of("others.csv")).size());
    }
}
pune.csv: 3, others.csv: 3

How it works: one pass over the input feeds two buffered outputs. Each writer is flushed and closed when the try ends.

P9Business problem

Resume-safe batch: flush every 100 lines

A job writes 250 receipt lines. To limit loss on a crash, it calls flush() every 100 lines. Count the flushes, including the final close.

Input
250 receipts
Expected output
250 receipts, 3 flushes
Show solution and explanation
import java.io.*;
import java.nio.file.*;

public class Main {
    public static void main(String[] args) throws IOException {
        int flushes = 0;
        try (BufferedWriter w = Files.newBufferedWriter(Path.of("receipts.txt"))) {
            for (int i = 1; i <= 250; i++) {
                w.write("RCPT-" + i);
                w.newLine();
                if (i % 100 == 0) {
                    w.flush();
                    flushes++;
                }
            }
        }
        flushes++;
        System.out.println(Files.readAllLines(Path.of("receipts.txt")).size() + " receipts, " + flushes + " flushes");
    }
}
250 receipts, 3 flushes

How it works: flushing too often removes the benefit of buffering; never flushing risks losing everything since the start. Periodic flushes (or checkpoints) are the usual compromise in batch jobs.

Real-world scenario questions (asked in company interviews)

These come up whenever a role involves batch jobs, reports or integrations.

Batch · data lossFresher

The last 300 rows are missing

An export writes 50,000 rows with a BufferedWriter. The file always ends a few hundred rows short, and the last row is cut in half. The code calls System.exit(0) right after the loop. Explain.

The interviewer is checking: buffers and flushing.

Show a strong answer
  • The writer is never closed, so the final partly-filled buffer (up to 8 KB) never reaches the file; System.exit doesn't flush it.
  • Wrap the writer in try-with-resources (or close it in finally) before exiting.

Avoid: adding flush() after every row; that removes the benefit of buffering.

Performance · I/OJunior

A 10-minute report that should take seconds

Profiling shows a report spends 95% of its time inside FileOutputStream.write, called once per character. How do you fix it with minimal code change?

The interviewer is checking: recognising unbuffered I/O.

Show a strong answer
  • Each call is a system call. Wrap the stream: new BufferedOutputStream(new FileOutputStream(f)), or use Files.newBufferedWriter for text.
  • Optionally use a larger buffer (64 KB) for very large outputs, and write whole strings rather than single characters.

Avoid: rewriting the report in multiple threads first; the bottleneck is the call pattern.

Integration · socketsJunior

The partner server never answers

A client sends a request line to a payment partner over a socket with a BufferedWriter, then waits for the reply with readLine(). It hangs forever, though the partner says it never received anything. Why?

The interviewer is checking: when explicit flush is needed.

Show a strong answer
  • The request is still in the client's buffer; it's never sent because the writer wasn't flushed. Both sides wait for each other.
  • Call writer.flush() after writing a complete request, before reading the reply.
  • Also set read timeouts so a missing reply can't hang the client forever.

Avoid: closing the writer to force sending; closing the socket stream closes the connection.

Data · line endingsMid

Files from Windows have an extra character

A Linux job reads a CSV uploaded from a Windows machine with Files.readString, splits the text on "\n", then parses the last column of each row. It fails with NumberFormatException: For input string: "450\r". Why, and how should it read the file?

The interviewer is checking: line terminators and defensive parsing.

Show a strong answer
  • Windows ends lines with \r\n. Splitting on "\n" leaves the \r at the end of every row.
  • readLine() (and lines()) treat \n, \r and \r\n all as line ends and strip them, so use those instead of splitting by hand.
  • Read with readLine (or lines()), trim() fields before parsing, and watch for a UTF-8 BOM at the start of files from Excel.

Avoid: assuming every file uses your OS's line endings.