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 BasithUpdated 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.
- Explain why a program reads from files, not only from the keyboard.
- Open a text file with a
FileReaderwrapped in aBufferedReader. - Read a file line by line with
readLine()and stop at the end using itsnullsignal. - 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 IOExceptiononmaindoes about it. - Turn each line into values with
splitandInteger.parseInt, or read a file with aScanner.
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.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.
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:
import java.io.BufferedReader;import java.io.FileReader;import java.io.IOException; public class Main { public static void main(String[] args) throws IOException { FileReader file = new FileReader("notes.txt"); BufferedReader br = new BufferedReader(file); System.out.println("First line: " + br.readLine()); br.close(); }}Buy milk Walk the dog Call Maya
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 aString, ornullwhen the file is used up. Thatnullis 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:
import java.io.BufferedReader;import java.io.FileReader;import java.io.IOException; public class Main { public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new FileReader("notes.txt")); System.out.println("First line: " + br.readLine()); System.out.println("Second line: " + br.readLine()); System.out.println("Third line: " + br.readLine()); System.out.println("Fourth call: " + br.readLine()); br.close(); }}Buy milk Walk the dog Call Maya
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.
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 "".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.
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.
import java.io.BufferedReader;import java.io.FileReader;import java.io.IOException; public class Main { public static void main(String[] args) throws IOException { try (BufferedReader br = new BufferedReader(new FileReader("notes.txt"))) { System.out.println("First line: " + br.readLine()); } // By this line, br has already been closed. System.out.println("Done reading."); }}Buy milk Walk the dog Call Maya
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.
- 1Java runs the part in parentheses, opening the file and building
br. - 2Java runs the body of the
try, where you read frombr. - 3When the body ends, for any reason, Java calls
br.close()for you.
br.close() yourself at the end.Key ideaDeclare 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.
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:
import java.io.BufferedReader;import java.io.FileReader;import java.io.IOException; public class Main { public static void main(String[] args) throws IOException { System.out.println("Opening notez.txt..."); BufferedReader br = new BufferedReader(new FileReader("notez.txt")); System.out.println("First line: " + br.readLine()); br.close(); }}Buy milk Walk the dog Call Maya
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.
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:
import java.io.BufferedReader;import java.io.FileReader;import java.io.IOException; public class Main { public static void main(String[] args) throws IOException { try (BufferedReader br = new BufferedReader(new FileReader("notes.txt"))) { int count = 0; String line = br.readLine(); while (line != null) { count = count + 1; if (line.length() > 10) { System.out.println(line); } line = br.readLine(); } System.out.println("The file notes.txt has " + count + " lines."); } }}Buy milk Walk the dog Call Maya
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.
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.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.import java.io.BufferedReader;import java.io.FileReader;import java.io.FileWriter;import java.io.IOException; public class Main { public static void main(String[] args) throws IOException { FileWriter setup = new FileWriter("notes.txt"); setup.write("Buy milk\nWalk the dog\nCall Maya\n"); setup.close(); try (BufferedReader br = new BufferedReader(new FileReader("notes.txt"))) { while (br.readLine() != null) { System.out.println(br.readLine()); } } }}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.
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:
import java.io.BufferedReader;import java.io.FileReader;import java.io.IOException; public class Main { public static void main(String[] args) throws IOException { int total = 0; int count = 0; try (BufferedReader br = new BufferedReader(new FileReader("scores.txt"))) { String line = br.readLine(); while (line != null) { String[] parts = line.split(" "); String name = parts[0]; int score = Integer.parseInt(parts[1]); System.out.println(name + ": " + score); total = total + score; count++; line = br.readLine(); } } System.out.println("Average: " + (double) total / count); }}Ada 91 Grace 88 Alan 79
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.
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:
import java.io.File;import java.io.IOException;import java.util.Scanner; public class Main { public static void main(String[] args) throws IOException { int total = 0; int count = 0; try (Scanner in = new Scanner(new File("scores.txt"))) { while (in.hasNext()) { String name = in.next(); int score = in.nextInt(); System.out.println(name + " scored " + score); total = total + score; count++; } } System.out.println("Average: " + (double) total / count); }}Ada 91 Grace 88 Alan 79
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.
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()returnsnull, 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.
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?new BufferedReader(new FileReader("notes.txt")), which of the two readers touches the file on disk, and what does the other one add?try ( ... ) safer than calling br.close() as the last line of the method?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?scores.txt is Grace 88. After String[] parts = line.split(" ");, what is parts[1] + 1, and how do you get 89 instead?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.
count after each pass of the loop, and explain why the blank line still counts.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.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.1: Buy milk, 2: Walk the dog. Then change it to skip blank lines without skipping their numbers.throws IOException on it, and what exception handling will let you do instead.Further reading#
- BufferedReader in the Java SE 25 API documentation, with readLine and why wrapping a FileReader helps
- FileReader in the Java SE 25 API documentation, including the exceptions its constructors throw
- The try-with-resources statement in the Java Language Specification, section 14.20.3
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