Java BufferedReader

In the last lesson you learned about Java writing files. Now let’s read text back, but faster than one character at a time. The tool for the job is BufferedReader. It lets you pull a whole line at once.

πŸ€” The problem BufferedReader solves

Say you have a text file with a thousand lines and want to print each one. A basic reader has two problems:

  • It gives you text one character at a time, so you glue letters into lines by hand. That is a lot of fussy code.
  • Each one-character request is a slow disk trip. Doing that for every letter in a big file is painfully slow.

BufferedReader fixes both:

  • It reads text in large chunks into a buffer, a small holding area in memory.
  • Disk trips happen rarely and in big gulps, so reading is fast.
  • Its readLine() method returns a whole line at once, so you never glue characters together.

🧩 How BufferedReader fits together

BufferedReader does not talk to the file directly. It wraps another reader and adds the buffer on top. For a file, that inner reader is usually FileReader.

Think of a tap and a bucket. FileReader is the tap connected to the file, giving water one drop at a time. BufferedReader is a bucket under the tap. It fills in one go, then you scoop as much as you like.

So the classic pattern is a FileReader wrapped in a BufferedReader:

BufferedReader reader = new BufferedReader(new FileReader("notes.txt"));
  • The inner new FileReader("notes.txt") is the real connection to the file.
  • The outer new BufferedReader(...) adds the speed buffer and readLine().
  • You almost always use them together like this.

πŸ“„ Reading a file line by line

Here is the pattern you will use most often in your whole Java life. Read each line with readLine() inside a while loop, and stop when it returns null. This program prints every line of a file with a line number in front.

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class Main {
public static void main(String[] args) {
try (BufferedReader reader = new BufferedReader(new FileReader("notes.txt"))) {
String line;
int number = 1;
while ((line = reader.readLine()) != null) { // βœ… stop when readLine returns null
System.out.println(number + ": " + line);
number++;
}
} catch (IOException e) {
System.out.println("Could not read the file: " + e.getMessage());
}
}
}

The important parts:

  • new FileReader("notes.txt") opens the file as the inner reader.
  • new BufferedReader(...) wraps it so we get the buffer and readLine().
  • reader.readLine() returns the next line as a String, without the line-break.
  • When there are no more lines, readLine() returns null, which ends the loop.
  • The try (...) is try-with-resources. It closes the reader when the block finishes.

The line while ((line = reader.readLine()) != null) reads a line into line and checks it for null in the same step. That is what makes this loop so short and so common. Get comfortable with this exact shape.

If notes.txt holds these three lines:

Buy milk
Call Alex
Finish report

then the program prints the lines with numbers in front.

Output

1: Buy milk
2: Call Alex
3: Finish report

⚑ Why buffering is faster

Picture the difference:

  • Without a buffer, every read is a separate request with a fixed cost. A thousand one-character reads pay that cost a thousand times.
  • With a buffer, BufferedReader grabs a big block at once and keeps it in memory.
  • Each readLine() hands you text from that block, with no disk trip.
  • Only when the block runs out does it fetch another. The slow trips become rare.

The first version reads one character at a time straight from a FileReader, which is slow on a big file.

import java.io.FileReader;
import java.io.IOException;
public class Main {
public static void main(String[] args) {
try (FileReader reader = new FileReader("big.txt")) {
int c;
while ((c = reader.read()) != -1) { // ❌ one slow character at a time
System.out.print((char) c);
}
} catch (IOException e) {
System.out.println("Read failed: " + e.getMessage());
}
}
}

The version below wraps the same FileReader and reads whole lines. Same result, much faster, far easier to read.

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class Main {
public static void main(String[] args) {
try (BufferedReader reader = new BufferedReader(new FileReader("big.txt"))) {
String line;
while ((line = reader.readLine()) != null) { // βœ… a whole line at a time, buffered
System.out.println(line);
}
} catch (IOException e) {
System.out.println("Read failed: " + e.getMessage());
}
}
}

The rule of thumb: Always wrap a FileReader in a BufferedReader. You get speed and you get readLine(). There is almost no reason to use a bare FileReader in a loop.

⌨️ Reading from the keyboard with System.in

BufferedReader is not only for files. It can also read what a person types in the console:

  • Keyboard input lives in System.in, a stream of raw bytes, not text.
  • Wrap it in an InputStreamReader to turn those bytes into characters.
  • Wrap that in a BufferedReader for readLine().

This program asks for a name and a city, then prints a greeting.

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
public class Main {
public static void main(String[] args) {
try (BufferedReader reader =
new BufferedReader(new InputStreamReader(System.in))) {
System.out.print("Your name: ");
String name = reader.readLine(); // βœ… reads one typed line
System.out.print("Your city: ");
String city = reader.readLine();
System.out.println("Hello " + name + " from " + city + "!");
} catch (IOException e) {
System.out.println("Could not read input: " + e.getMessage());
}
}
}

The wrapping runs from the inside out: System.in (raw bytes) β†’ InputStreamReader (characters) β†’ BufferedReader (buffer plus readLine()).

If the person types Sam and then Berlin, the run looks like this.

Output

Your name: Sam
Your city: Berlin
Hello Sam from Berlin!

One note on closing:

  • Closing the BufferedReader also closes System.in, which you usually cannot reopen.
  • In a short program that reads input once and ends, try-with-resources is fine.
  • If your program keeps reading input later, leave this reader open for the life of the program.

πŸ“š Reading all lines at once

Sometimes you do not want a loop. You just want every line in a list so you can sort it, count it, or pass it around.

The lines() method on a BufferedReader gives you a stream of the lines, a flow of values you process one after another. This example collects them into a list and prints how many there are.

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
try (BufferedReader reader = new BufferedReader(new FileReader("notes.txt"))) {
List<String> lines = reader.lines()
.collect(Collectors.toList()); // βœ… all lines in a list
System.out.println("Total lines: " + lines.size());
System.out.println("First line: " + lines.get(0));
} catch (IOException e) {
System.out.println("Could not read: " + e.getMessage());
}
}
}

Breaking that down:

  • reader.lines() produces a stream where each item is one line.
  • .collect(Collectors.toList()) gathers every line into a List of String.
  • Then you use the list like any other, here checking its size and first item.

For the three-line notes.txt from before, the output is short.

Output

Total lines: 3
First line: Buy milk

An even shorter helper is Files.readAllLines(path). It hands back a List of lines in one call and handles opening and closing. Use BufferedReader to read line by line in a loop, and Files.readAllLines when you just want the whole file as a list.

πŸ›Ÿ Handling IOException

Reading can fail, so the methods throw a checked exception called IOException:

  • The file might not exist, or the folder might be wrong.
  • You might not have permission to open it.
  • The compiler forces you to deal with it. You cannot pretend it never happens.
  • Handle it by catching with try/catch, or by declaring throws IOException.

The examples above caught it and printed a message, the usual choice for a small program. This snippet shows the throws style instead.

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class Main {
public static void main(String[] args) throws IOException { // βœ… passes the error on
try (BufferedReader reader = new BufferedReader(new FileReader("notes.txt"))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
}
}
}

Catching lets you show a clear message and keep control. Declaring throws is simpler but lets the program stop on error. For real apps, catching and reporting is kinder to the person using your program.

Pick a real message

When you catch IOException, print something a person can act on, like which file failed. A vague message such as β€œerror” makes it hard to know what went wrong.

⚠️ Common Mistakes

A few BufferedReader slip-ups to watch for.

  • Not closing the reader. An open reader holds onto the file and wastes resources. Use try-with-resources so it always closes.
BufferedReader r = new BufferedReader(new FileReader("notes.txt"));
String line = r.readLine(); // ❌ never closed, file stays open
try (BufferedReader r = new BufferedReader(new FileReader("notes.txt"))) {
String line = r.readLine(); // βœ… closed automatically
}
  • Forgetting the null check, so the loop never ends. If you do not stop on null, the loop runs forever or breaks badly.
String line;
while (true) { // ❌ no null check, never stops cleanly
line = reader.readLine();
System.out.println(line);
}
while ((line = reader.readLine()) != null) { // βœ… stops when the file ends
System.out.println(line);
}
  • Reading character by character without a buffer. A bare FileReader in a loop is slow and clumsy. Wrap it.
FileReader r = new FileReader("big.txt");
int c;
while ((c = r.read()) != -1) { } // ❌ slow, one character at a time
BufferedReader r2 = new BufferedReader(new FileReader("big.txt"));
String line;
while ((line = r2.readLine()) != null) { } // βœ… fast, a whole line at a time

βœ… Best Practices

Habits that make reading text safe and clean.

  • Always use try-with-resources. Open the reader inside try (...) so Java closes it for you, even when an error happens. This is the single most important habit.
  • Wrap FileReader in BufferedReader. You get speed from the buffer and the handy readLine() method. There is almost no reason to loop over a bare FileReader.
  • Use the readLine-until-null loop. The while ((line = reader.readLine()) != null) shape reads and checks in one step and is the standard way to walk a file.
  • Always handle IOException. Catch it and print a clear, specific message, or declare throws if a caller will handle it.
  • For keyboard input, chain System.in. Use new BufferedReader(new InputStreamReader(System.in)) to read typed lines with readLine().

🧩 What You’ve Learned

Nicely done. Let’s recap BufferedReader.

  • βœ… BufferedReader wraps another reader like FileReader and reads text efficiently through a buffer.
  • βœ… The classic pattern is new BufferedReader(new FileReader(path)) then readLine() in a while loop until null.
  • βœ… Buffering is faster because it reads big chunks instead of one slow character at a time.
  • βœ… For keyboard input, use new BufferedReader(new InputStreamReader(System.in)).
  • βœ… lines() gives a stream of lines, and Files.readAllLines gives a list in one call.
  • βœ… Always use try-with-resources to close the reader and always handle IOException.

Check Your Knowledge

Test what you learned. Pick an answer for each question, then click Check.

  1. 1

    What does BufferedReader.readLine() return when there are no more lines?

    Why: readLine() returns null at the end of the input, which is how the while loop knows to stop.

  2. 2

    What is the classic way to create a BufferedReader for a file?

    Why: You wrap a FileReader in a BufferedReader to add the buffer and the readLine() method.

  3. 3

    Why is BufferedReader faster than reading character by character?

    Why: Buffering grabs large blocks of text at once, so the slow disk requests happen rarely.

  4. 4

    How do you read typed keyboard input with BufferedReader?

    Why: Wrap System.in in an InputStreamReader to get characters, then in a BufferedReader for readLine().

πŸš€ What’s Next?

You can now read text quickly, line by line, from files and from the keyboard. The natural partner of an efficient reader is an efficient writer. Next you will meet the writing side of the same idea, which buffers your output the same way for speed.

Java BufferedWriter

Share & Connect