Reading a File with Scanner
The File class is a path, Scanner is the reader, throws is the escape hatch
In a nutshell
Three things you need to read a file in Java:
- A
Fileobject — a path on disk. Constructing one does not open the file or read anything. It’s just an address. - A
Scannerwrapped around theFile— the reader. The same Scanner methods you’ve been using onSystem.in(nextInt,nextLine,hasNextInt,hasNextLine) work identically on a file Scanner. The only thing that changed is the source. - The
throws FileNotFoundExceptiondeclaration onmain— the keyword the compiler insists on, because constructing a Scanner from a File can fail. This is what we use this quarter;try/catchis a CSCD 211 topic.
Once you have the Scanner, the EOF loop is the bread-and-butter pattern: keep reading until hasNextLine() (or hasNext(), or hasNextInt()) returns false.
Today in three sentences. A
Fileis a path; aScanneris what reads from it.throws FileNotFoundExceptiononmainis how we satisfy the checked-exception rule for now. The EOF loop iswhile (sc.hasNextLine()) { ... sc.nextLine() ... }.
After this lesson, you will be able to:
- Construct a
Scannerthat reads from a file and explain the role of theFileobject. - Add
throws FileNotFoundExceptiontomainand explain why the compiler insists on it. - Write the canonical EOF loop using
hasNextLine/nextLine(and the relatedhasNext/nextform). - Recognize the
try/catchandtry-with-resourcesalternatives without writing them yet.
From CSCD 110. Python hides this with
with open("data.txt") as f:— one line, no exception declaration, file auto-closes. Java makes the steps visible: build the path, build the reader, declare what can go wrong, close it explicitly. More verbose, more explicit, fewer surprises later.
The File class is a path, not the contents
This is the first place students get confused. Read it carefully.
File f = new File("scores.txt");
That line does not open scores.txt. It does not read anything. It does not even check whether the file exists. All it does is build a small Java object whose job is to hold the path string "scores.txt".
Mental model: the File object is like an address written on a piece of paper. Having someone’s address does not mean you have visited their house. It means you know where to go if you decide to go.
File f = new File("scores.txt"); // address on paper
boolean exists = f.exists(); // ask: does the house at this address exist?
String fullPath = f.getAbsolutePath(); // ask: what's the full address?
Both f.exists() and f.getAbsolutePath() look up information about the path, but they still do not open the file or read its contents. To do that, you hand the File object to a Scanner, which is the actual reader.
The File constructor accepts either a relative path ("scores.txt") or an absolute one ("/Users/jessica/data/scores.txt"). A relative path is resolved against the program’s working directory — the folder the program was launched from. If you launch from one folder and the file is in another, the program will report it as missing. (This is the most common reason a file “isn’t there” when the file clearly is.)
Common pitfall: thinking
new File(...)opens the file. It doesn’t. It’s just an address. The actual reading happens when you give that address to aScanner. Several students every term try to callf.nextInt()on aFiledirectly. That doesn’t compile becauseFiledoesn’t have anextIntmethod.Fileis just the address;Scanneris the reader.
Check your understanding. What does this code do?
File f = new File("nonexistent.txt"); System.out.println(f.exists());Reveal answer
Prints
false. Constructing theFileobject never tries to open the file, so it succeeds even when no such file exists.exists()is the line that actually goes to the disk and asks. The program does not throw any exception here — it just printsfalse. (You’d get an exception only when you tried to read from a Scanner pointed at a missing file.)
Wrapping a Scanner around a File
The Scanner you’ve been using all quarter has more than one constructor. You’ve been using this one:
Scanner kb = new Scanner(System.in);
There’s also one that takes a File:
Scanner sc = new Scanner(new File("scores.txt"));
Same class. Same methods. Different source. Anything you’ve called on kb works on sc:
int n = sc.nextInt();
String line = sc.nextLine();
boolean more = sc.hasNextInt();
The numbers and tokens come from the file instead of the keyboard, but the API is identical. This is the entire reason file I/O isn’t a whole new topic — Scanner abstracted the source away. You learn one new constructor, and your Scanner skills transfer.
Closing the Scanner when you’re done:
sc.close();
Closing releases the file handle back to the operating system. For programs that read one file and exit, this matters less (the OS cleans up on process exit). For programs that read many files, or run for a long time, leaking handles is a real bug. Close where the work for that Scanner ends.
Common pitfall: the working-directory trap. A program that worked fine in IntelliJ (“scores.txt is right there!”) fails when launched from a different folder, because
new File("scores.txt")is interpreted relative to wherever the program was started from, not relative to the source code’s folder. Either use an absolute path during testing, or printnew File("scores.txt").getAbsolutePath()when debugging to see exactly which path the program is looking at.
throws FileNotFoundException: the route this course takes
The Scanner(File) constructor can fail. If the file doesn’t exist (or the program doesn’t have permission to read it), the constructor throws a FileNotFoundException. Java categorizes this as a checked exception, which means the compiler will not let you ignore it.
Concretely: if you write this and nothing else, the program does not compile.
public static void main(String[] args) {
Scanner sc = new Scanner(new File("scores.txt")); // compile error
// ...
}
The compiler error reads:
error: unreported exception java.io.FileNotFoundException;
must be caught or declared to be thrown
You have two ways to satisfy the compiler. For this course, we use the simpler one.
The course route: throws FileNotFoundException on main
Add the keyword to the method signature:
public static void main(String[] args) throws FileNotFoundException {
Scanner sc = new Scanner(new File("scores.txt"));
// ...
}
That’s it. The throws declaration tells the compiler: “if this exception happens, let it propagate up out of main. The program will print a stack trace and exit.” For our purposes, that’s exactly what we want — if the input file is missing, there is nothing useful the program can do anyway.
You’ll add import java.io.FileNotFoundException; at the top of the file, alongside import java.io.File; and import java.util.Scanner;. Three imports, one keyword. This pattern is the convention you’ll use on every file-reading lab and exam.
The other routes: try/catch and try-with-resources (CSCD 211 topics)
Two alternatives exist. You will see them mentioned and may even see them in code from other sources. You do not need to write them this quarter. Brief tour, for recognition only.
try/catch lets the calling method intercept the exception and print a friendlier message:
try {
Scanner sc = new Scanner(new File("scores.txt"));
// use sc
sc.close();
} catch (FileNotFoundException e) {
System.out.println("Could not open file: " + e.getMessage());
}
try-with-resources does the same plus auto-closes the Scanner at the end of the try block:
try (Scanner sc = new Scanner(new File("scores.txt"))) {
// use sc, no need to call sc.close()
} catch (FileNotFoundException e) {
System.out.println("Could not open file: " + e.getMessage());
}
These are real Java and they’re useful, but they introduce control-flow ideas (catch blocks, resource scoping) that belong in CSCD 211. For now, when you see them in someone else’s code, recognize the shape; when you write your own, use throws.
Common pitfall: forgetting
throws FileNotFoundExceptiononmain. The compiler error is loud (unreported exception ...; must be caught or declared to be thrown), so this is easy to fix once you’ve seen it. The fix is exactly six words: addthrows FileNotFoundExceptionafter the closing parenthesis of the parameter list.
Check your understanding. Which of these
mainsignatures will letnew Scanner(new File("data.txt"))compile inside the body?A.
public static void main(String[] args)B.public static void main(String[] args) throws FileNotFoundExceptionC.public static void main(String[] args) throws ExceptionD.public static void main(String[] args) throws IOExceptionReveal answer
B, C, and D all work. B is the most precise (declares exactly what can be thrown). C and D both work because
FileNotFoundExceptionis a subclass ofIOException, which is a subclass ofException— declaring a more general type covers all the more specific ones. A does not work; that’s the compile-error case. Use B for this course. Specific is better than general.
Reading until end of file
You usually don’t know in advance how many lines or tokens a file has. The canonical pattern is to keep asking the Scanner whether there is more input, and stop when there isn’t.
For line-by-line reading:
Scanner sc = new Scanner(new File("data.txt"));
while (sc.hasNextLine()) {
String line = sc.nextLine();
System.out.println(line);
}
sc.close();
For token-by-token reading (whitespace-separated):
Scanner sc = new Scanner(new File("numbers.txt"));
while (sc.hasNextInt()) {
int n = sc.nextInt();
System.out.println(n);
}
sc.close();
This is the same shape as a sentinel loop you wrote on keyboard input, with the end-of-file playing the role of the sentinel. When the Scanner runs out of input, hasNextLine() (or hasNextInt(), etc.) returns false, and the loop exits naturally.
There’s a related family of methods you should recognize:
hasNext()— is there another whitespace-delimited token?hasNextLine()— is there another line?hasNextInt()— is there another token and does it parse as anint?hasNextDouble()— same fordouble.
The has* methods are predicates: they return true or false and they do not consume input. Asking them is always safe. Calling the matching next* method is what actually reads.
Common pitfall: calling
nextLine()(ornextInt(), etc.) without checking first. If the file is empty, or you’ve already read past the end, the call throwsNoSuchElementException. Always pair eachnext*call with a guard (while (hasNextLine())orif (hasNextInt())) so you only read when there’s something to read.
Check your understanding. What does this loop print, given a file
nums.txtcontaining exactly:7 12 -3Scanner sc = new Scanner(new File("nums.txt")); int sum = 0; while (sc.hasNextInt()) { sum += sc.nextInt(); } sc.close(); System.out.println(sum);Reveal answer
Prints
16. The loop reads7,12,-3in order, accumulatingsum = 0 + 7 + 12 + (-3) = 16. After the third read,hasNextInt()returnsfalse(no more tokens), the loop exits, andsumis printed. This is the same accumulator pattern from Week 4, with the end-of-file playing the sentinel role.
Check your understanding. What’s the bug?
Scanner sc = new Scanner(new File("data.txt")); while (true) { String line = sc.nextLine(); System.out.println(line); }Reveal answer
The loop never checks
hasNextLine(). Once the file is exhausted, the next call tosc.nextLine()throwsNoSuchElementException. Fix: change the condition towhile (sc.hasNextLine()). Thewhile (true)form is fine when the loop exits another way (abreakon a sentinel), but for a “read to the end of the file” pattern,while (sc.hasNextLine())is the idiomatic shape.
Wrap up and what’s next
Recap.
- A
Fileobject is a path, not the contents. Constructing it never opens or reads. - Wrap a Scanner around a
Fileto read:new Scanner(new File(path)). Same methods you already know. - The
Scanner(File)constructor throwsFileNotFoundException. The course convention is to declarethrows FileNotFoundExceptiononmain.try/catchandtry-with-resourcesexist; you’ll write them in CSCD 211. - The EOF loop is the canonical pattern:
while (sc.hasNextLine()), orwhile (sc.hasNextInt()), etc. Always pairnext*reads withhasNext*guards. - Close the Scanner when you’re done.
What you can do now. Read a file line by line, or token by token, into local variables or an array. Add throws FileNotFoundException to main without thinking about it. Recognize try/catch in someone else’s code without writing your own.
Next up: The next* Cascade and the nextLine Trap. The four readers (next, nextLine, nextInt, nextDouble) and how to choose between them. The hasNextInt-before-hasNextDouble rule (and why the order matters). The famous leftover-newline trap that breaks programs which mix nextInt and nextLine.
Related resources
- Reges & Stepp, Building Java Programs, Chapter 6 sections 6.1 and 6.2 on Scanner, files, and the EOF loop.
- The
ScannerJavadoc lists every method. ThehasNext*andnext*families are the load-bearing ones for this course.