Learn Java

Lesson 46Phase 5: Errors, files and the standard library

Reading files

Opening a file, reading it line by line, and why every file operation can fail.

By Updated 55 min

  • FileReader
  • BufferedReader
  • reading lines
  • try-with-resources
  • IOException
  • EOF

In Scanner and console applicationsyour programs read what a person typed at the keyboard. Most of the data a program needs is not typed live, though. It is already on disk in a file: a list of names, yesterday's sales, a saved game. This lesson is about opening such a file and reading it, one line at a time.

By the end of this lesson
  • Explain why a program reads from files, not only from the keyboard.
  • Open a text file with a FileReader wrapped in a BufferedReader.
  • Read a file line by line with readLine() and stop at the end using its null signal.
  • Write the try-with-resources shape so the file always closes, even when something fails.
  • Explain why a file operation can fail, and what throws IOException on main does about it.
  • Turn each line into values with split and Integer.parseInt, or read a file with a Scanner.
From before
You can already read keyboard input with Scanner and the console, and you know loops, if-else, and null from object references. Later on this page, split from HashMap and HashSet and Integer.parseInt from wrapper classes come back too. Handling errors properly waits for a lesson of its own.
Files on this page
Every program on this page runs against a small disk that lives in your browser's memory. Under the code you can see the files a program starts with, labelled “Files before the program runs”, and as you step through, the memory view shows the files too. Nothing on your own computer is read or changed, and every run starts again from the same files.

Why read from a file at all?#

A Scanner on the keyboard is fine when a person is there to answer. Now think about a program that prints a report from ten thousand sales records. Nobody is going to type ten thousand numbers. Those records live in a file, written there earlier, perhaps by another program. Reading from a file lets your program work with data that already exists and that outlives any single run.

A plain text file is a sequence of characters, with line break marks splitting it into lines. When you open notes.txtin an editor and see three lines, the file on disk is one long ribbon of characters with invisible “new line” marks in it. Reading the file means walking along that ribbon from the start to the end.

Coming from Python?
Python's open("notes.txt") followed by for line in f: does the same job. In Java you build the reader yourself, ask for each line, and make sure the file gets closed. The try-with-resources shape later on this page takes care of the closing for you.

FileReader and BufferedReader#

To read a text file in Java you typically use two classes stacked together. A FileReader knows how to open one named file and pull characters out of it. A BufferedReader wraps around a FileReader and adds two things: it reads the file in large chunks, which is faster, and it has a method that hands back a whole line at a time. You wrap one inside the other:

FirstLine.java
1import java.io.BufferedReader;
2import java.io.FileReader;
3import java.io.IOException;
4
5public class Main {
6 public static void main(String[] args) throws IOException {
7 FileReader file = new FileReader("notes.txt");
8 BufferedReader br = new BufferedReader(file);
9
10 System.out.println("First line: " + br.readLine());
11
12 br.close();
13 }
14}
Files before the program runs
notes.txt
Buy milk
Walk the dog
Call Maya
Prints
›First line: Buy milk

Read the wrapping out loud: “a buffered reader, reading from a file reader, reading from notes.txt”. The FileReader is the part touching the disk. The BufferedReader is the friendly layer on top that you talk to, and its readLine() hands back the first line, Buy milk. Two pieces of this program are explained further down the page: throws IOException on the line that declares main, and the br.close() at the end. Until then, copy them as they are.

People usually fold the two lines that build the readers into one: BufferedReader br = new BufferedReader(new FileReader("notes.txt"));. The rest of this page writes it that way.

Reading line by line with readLine()#

The method you call on a BufferedReader is readLine(). Each call returns the next line of the file as a String, without the line break at its end. Call it again and you get the line after that. When there are no more lines, readLine() returns null. That nullis the file's way of saying “there is nothing left”. This point is the end of file, often shortened to EOF.

Key idea

readLine() gives you the next line as a String, or null when the file is used up. That null is your signal to stop reading.

Calling readLine() four times on a three-line file makes this concrete. The file notes.txt holds exactly the three lines shown under the code:

ReadThree.java
1import java.io.BufferedReader;
2import java.io.FileReader;
3import java.io.IOException;
4
5public class Main {
6 public static void main(String[] args) throws IOException {
7 BufferedReader br = new BufferedReader(new FileReader("notes.txt"));
8
9 System.out.println("First line: " + br.readLine());
10 System.out.println("Second line: " + br.readLine());
11 System.out.println("Third line: " + br.readLine());
12 System.out.println("Fourth call: " + br.readLine());
13
14 br.close();
15 }
16}
Files before the program runs
notes.txt
Buy milk
Walk the dog
Call Maya
Prints
›First line: Buy milk
›Second line: Walk the dog
›Third line: Call Maya
›Fourth call: null

The first three calls hand back the three lines. The fourth call has nothing left to give, so it returns null, and printing null shows the word null. Step through it and read the narration on the fourth call: it says the reader has reached the end of the file.

An empty line is not the end
Do not confuse an empty line with the end of the file. A blank line in the middle of a file makes readLine() return "", the empty string, which is a real value. Only the true end of the file gives you null. So you stop on null, never on "".
Quick check
A file holds three lines: Ada, an empty line, then Grace. What does the second call to readLine() return?

Why you must close a file#

When you open a file, the operating system hands your program a kind of ticket: a live connection to that file, called a file handle. The system has only a limited number of these tickets. If you open files and never give the tickets back, your program leaks them, and eventually the system refuses to open any more. Closing a reader returns the ticket. close()is how you say “I am done with this file”.

Closing matters for a second reason, and it bites harder when writing than reading: a buffer may still hold data that has not been pushed all the way to the file. Closing flushes it out, and writing files shows what happens without it. The habit to build is simple: if you open it, close it.

The danger of close() at the bottom
Putting br.close() as the last line looks tidy, but it has a hole. If anything goes wrong on a line above it, the program may jump away and never reach that close() at all. The file stays open. The next section closes the hole.

try-with-resources: closing that cannot be skipped#

Java has a dedicated shape for “use this thing, then close it no matter what”. It is called try-with-resources. You declare the reader inside parentheses right after the word try, and Java closes the reader for you when the block ends, whether the block finishes normally or something fails partway through.

ReadSafe.java
1import java.io.BufferedReader;
2import java.io.FileReader;
3import java.io.IOException;
4
5public class Main {
6 public static void main(String[] args) throws IOException {
7 try (BufferedReader br = new BufferedReader(new FileReader("notes.txt"))) {
8 System.out.println("First line: " + br.readLine());
9 }
10 // By this line, br has already been closed.
11 System.out.println("Done reading.");
12 }
13}
Files before the program runs
notes.txt
Buy milk
Walk the dog
Call Maya
Prints
›First line: Buy milk
›Done reading.

Look closely at the first line of the try. The reader is created inside the parentheses try ( ... ), not inside the body. That placement tells Java “this is a resource I am borrowing; close it the moment this block is done”. You write no close() yourself, so there is no way to forget it and no way for a failure to skip it. Step through the program: just before Done reading. prints, the narration says that br is closed automatically.

  1. 1Java runs the part in parentheses, opening the file and building br.
  2. 2Java runs the body of the try, where you read from br.
  3. 3When the body ends, for any reason, Java calls br.close() for you.
close() by hand
try-with-resources
You write br.close() yourself at the end.
Java closes the reader for you.
A failure above it can skip the close, and the file stays open.
The close runs even when something fails.
Easy to forget on a busy day.
Cannot be forgotten, because it is part of the shape.
Key idea

Declare a reader inside try ( ... ) and Java closes it for you when the block ends, however it ends. Use this shape for every file you open.

Quick check
A reader is declared in try ( ... ). When does Java close it?

Files can fail: IOException#

Reading the keyboard rarely surprises you. Reading a file is different, because the file lives outside your program and outside your control. The file might not exist. It might exist, but you might not have permission to read it. The disk might be unplugged in the middle of a read. Java does not let you pretend these cannot happen. Any operation that touches a file is allowed to fail with an IOException, where IO stands for input and output.

Here is what a missing file looks like. The disk holds notes.txt, but the program asks for notez.txt, a typo:

Missing.java
1import java.io.BufferedReader;
2import java.io.FileReader;
3import java.io.IOException;
4
5public class Main {
6 public static void main(String[] args) throws IOException {
7 System.out.println("Opening notez.txt...");
8 BufferedReader br = new BufferedReader(new FileReader("notez.txt"));
9 System.out.println("First line: " + br.readLine());
10 br.close();
11 }
12}
Files before the program runs
notes.txt
Buy milk
Walk the dog
Call Maya
Prints
›Opening notez.txt...
!Exception in thread "main" java.io.FileNotFoundException: notez.txt (No such file or directory)
! at Main.main(Main.java:8)
Stops on line 8

FileNotFoundException: notez.txt (No such file or directory)

The program prints its first line, then stops on line 8 with java.io.FileNotFoundException: notez.txt (No such file or directory). FileNotFoundException is one kind of IOException: the kind for a file that cannot be opened. The text in brackets comes from the operating system, so on Windows it reads (The system cannot find the file specified) instead.

Because files can fail, Java makes you acknowledge it. Every program on this page has the same small piece on the line that declares main: throws IOException. Those words are a label on the method. They say “the code inside might run into an input or output problem, and this method is not dealing with it here; it passes the problem on to whoever called it”. For main, that means the program stops with a report, as it just did. Writing throws IOException on main is enough to make a file-reading program compile and run. You also need the import import java.io.IOException; at the top, so Java knows what that name means.

Handling the failure instead
Passing the problem on with throwsis the quick option. The careful way is to catch the failure and respond to it, perhaps printing “sorry, that file is missing” instead of stopping. try, catch and the family of exceptions get a full lesson soon: exception handling. The try-with-resources shape from this page fits straight into it. Just before that, you use these same ideas in reverse to write files. Until then, throws IOException on main is all you need.

Reading the whole file: a loop that counts lines#

In practice you almost never want exactly three lines. You want every line, however many there are, and the file decides how many. That is a loop, and the stopping condition is the null from readLine(). The classic pattern reads a line, checks it against null, and enters the loop body only if there really was a line:

CountLines.java
1import java.io.BufferedReader;
2import java.io.FileReader;
3import java.io.IOException;
4
5public class Main {
6 public static void main(String[] args) throws IOException {
7 try (BufferedReader br = new BufferedReader(new FileReader("notes.txt"))) {
8 int count = 0;
9 String line = br.readLine();
10
11 while (line != null) {
12 count = count + 1;
13 if (line.length() > 10) {
14 System.out.println(line);
15 }
16 line = br.readLine();
17 }
18
19 System.out.println("The file notes.txt has " + count + " lines.");
20 }
21 }
22}
Files before the program runs
notes.txt
Buy milk
Walk the dog
Call Maya
Prints
›Walk the dog
›The file notes.txt has 3 lines.

Walk through it with the same three-line notes.txt. Before the loop, line is set to the first line, "Buy milk". The loop checks: not null, so enter. count becomes 1. "Buy milk" is 8 characters, not more than 10, so nothing prints. Then the last line of the body reads the next line, "Walk the dog". Loop again: not null, count becomes 2, and "Walk the dog" is 12 characters, more than 10, so it prints. Read again: "Call Maya". Loop: count becomes 3, 9 characters, no print. Read again: null. The loop condition is now false, so the program leaves the loop and prints the count, 3.

The two reads, and why there are two
readLine() appears twice: once just before the loop, and once at the bottom of the loop body. The first read primes the loop, so the very first check has something to look at. The read at the bottom moves on to the next line for the next check. Leave out the first and the loop has nothing to test; leave out the second and the loop never moves forward and runs forever.
Each readLine() uses up a line, so do not call it twice by accident
Every call to readLine() moves forward and hands back the next line. There is no peeking. A common bug is to test with br.readLine() != null and then call br.readLine() again to use the line. That reads two different lines each time round. Read each line exactly once, into a variable, and work with that variable.
Predict the outputTwiceBug.java
1import java.io.BufferedReader;
2import java.io.FileReader;
3import java.io.FileWriter;
4import java.io.IOException;
5
6public class Main {
7 public static void main(String[] args) throws IOException {
8 FileWriter setup = new FileWriter("notes.txt");
9 setup.write("Buy milk\nWalk the dog\nCall Maya\n");
10 setup.close();
11
12 try (BufferedReader br = new BufferedReader(new FileReader("notes.txt"))) {
13 while (br.readLine() != null) {
14 System.out.println(br.readLine());
15 }
16 }
17 }
18}

The first three lines of main make notes.txt with the same three lines as before (writing files is the next lesson's topic). What does the loop print?

It prints Walk the dog and then null. The test in the while reads Buy milk and throws it away, and the println reads the next line, Walk the dog. On the second pass the test uses up Call Maya, so the println finds the end of the file and prints null. Then the test gets null and the loop stops.

Put the lines in orderDashes.java
Put the lines in order so that the program first makes notes.txt (the three setup lines), then reads it back and prints each line with a dash in front: - Buy milk, - Walk the dog, - Call Maya.

Tap a line, then tap where it should go. Or use the arrows.

Turning lines into data#

A line from a file is always a String, even when it looks like a number. To do arithmetic with it, cut it into pieces and convert the pieces. Suppose scores.txt holds a name and a score on each line. split(" "), which you used to count words in HashMap and HashSet, cuts each line at the space into an array, and Integer.parseInt turns the score into an int:

Scores.java
1import java.io.BufferedReader;
2import java.io.FileReader;
3import java.io.IOException;
4
5public class Main {
6 public static void main(String[] args) throws IOException {
7 int total = 0;
8 int count = 0;
9
10 try (BufferedReader br = new BufferedReader(new FileReader("scores.txt"))) {
11 String line = br.readLine();
12 while (line != null) {
13 String[] parts = line.split(" ");
14 String name = parts[0];
15 int score = Integer.parseInt(parts[1]);
16 System.out.println(name + ": " + score);
17 total = total + score;
18 count++;
19 line = br.readLine();
20 }
21 }
22
23 System.out.println("Average: " + (double) total / count);
24 }
25}
Files before the program runs
scores.txt
Ada 91
Grace 88
Alan 79
Prints
›Ada: 91
›Grace: 88
›Alan: 79
›Average: 86.0

For the line Ada 91, split gives the array ["Ada", "91"]. parts[0] is the name and parts[1] is the text "91", which parseInt turns into the number 91. Without that step, "91" + 1 would join text and give "911". The three scores add up to 258, and the cast to double makes the division keep its fraction, so the program prints Average: 86.0.

ExerciseBasket.java
Each line of basket.txt holds an item and its price in cents, such as bread 250. The first lines of the program make the file. Finish the program so that it reads the file and prints the total price: Total: 465 cents.

Reading a file with a Scanner#

A Scanner can read a file as well as the keyboard. Give it a File instead of System.in, and every method from the Scanner lesson works on the file's text: next(), nextInt(), nextLine() and the hasNext family. The input runs out at the end of the file, so hasNext() makes a natural loop condition:

ScanScores.java
1import java.io.File;
2import java.io.IOException;
3import java.util.Scanner;
4
5public class Main {
6 public static void main(String[] args) throws IOException {
7 int total = 0;
8 int count = 0;
9
10 try (Scanner in = new Scanner(new File("scores.txt"))) {
11 while (in.hasNext()) {
12 String name = in.next();
13 int score = in.nextInt();
14 System.out.println(name + " scored " + score);
15 total = total + score;
16 count++;
17 }
18 }
19
20 System.out.println("Average: " + (double) total / count);
21 }
22}
Files before the program runs
scores.txt
Ada 91
Grace 88
Alan 79
Prints
›Ada scored 91
›Grace scored 88
›Alan scored 79
›Average: 86.0

next() reads the name and nextInt() reads the score, so there is no split and no parseInt. A Scanner on a file is closed with try-with-resources in the same way, and new Scanner(new File(...)) throws a FileNotFoundException when the file is missing, so main still says throws IOException. Use a BufferedReader when you want whole lines, and a Scanner when the file is a series of words and numbers you want one at a time.

One call instead of a loop
Java's newer file classes can read every line of a file into a list in a single call: Files.readAllLines(Path.of("notes.txt")). They arrive in Path and Files, after exception handling. The BufferedReader loop is still worth knowing: it reads a file a piece at a time instead of loading all of it at once, so it copes with files of any size, and you will meet it in plenty of existing code.

The shape to remember#

Strip away the example and a file-reading program makes the same four moves every time.

  • Open the file in a try ( ... ) so it closes itself: new BufferedReader(new FileReader(name)).
  • Loop, calling readLine(), doing something with each line.
  • Stop when readLine() returns null, the end of the file.
  • Let the method say throws IOException, because file work can fail.

That pattern reads a settings file, a list of names, a log or a saved score table: anything that is text, one record per line. From one program to the next, only the work inside the loop changes.

Try it

Answer each one in your head or on paper first, then open the answer.

1
What does readLine() return when it reaches the end of the file, and how is that different from reading a blank line in the middle of the file?
2
In the line new BufferedReader(new FileReader("notes.txt")), which of the two readers touches the file on disk, and what does the other one add?
3
Why is putting the reader inside try ( ... ) safer than calling br.close() as the last line of the method?
4
With the same three-line notes.txt, a program opens it in a try-with-resources block and calls br.readLine() exactly twice, printing each result. What does it print, and what happens to the file afterwards?
5
A line of scores.txt is Grace 88. After String[] parts = line.split(" ");, what is parts[1] + 1, and how do you get 89 instead?
6
Why does a method that reads a file usually carry throws IOException on its header, when a method that only reads the keyboard with Scanner often does not?

Practice

Work through these on your own. Each one builds on this page.

1
On paper, trace the line-counting program against a file with four lines, where the second line is completely blank. Write down the value of count after each pass of the loop, and explain why the blank line still counts.
2
Change the line-counting program so that, instead of counting all lines, it counts only the lines that contain the word milk. Use a String method you already know to test each line. Decide what your program should print if the file has no such line.
3
Write a program that reads a file numbers.txt where every line is a single whole number, and prints the largest number found. Assume the file has at least one line. In one sentence, say why you must convert each line from a String before comparing the numbers.
4
Print every line of a file with its line number in front, like 1: Buy milk, 2: Walk the dog. Then change it to skip blank lines without skipping their numbers.
5
In two or three sentences, explain to a friend why your file-reading method has throws IOException on it, and what exception handling will let you do instead.

Further reading#

Short guides on this topic

Quick answers to what people search for about it, with code to run.

Spotted a mistake, or a part that did not make sense? Report it by email