Learn Java

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

Exception handling

What an exception is, try-catch-finally, checked versus unchecked, and throwing your own.

By Updated 60 min

  • exception
  • try
  • catch
  • finally
  • checked vs unchecked
  • throw

Most programs so far assumed the happy path. The array index was in range, the number you divided by was not zero, the file you opened was really there. Real programs cannot count on that. This lesson is about what Java does when something goes wrong while a program is running, and how you take that moment back into your own hands.

By the end of this lesson
  • Say in plain words what an exception is and what happens when nothing catches it.
  • Catch a failing operation with try and catch, one type or several, so the program recovers.
  • Use finally and try-with-resources for cleanup that must run no matter what.
  • Tell a checked exception apart from an unchecked one, and know which the compiler forces you to handle.
  • Raise an exception with throw, declare one with throws, and write your own exception class.
  • Read a stack trace several calls deep, including one with a Caused by section.
From before
You met runtime errors and stack traces in reading errors and debugging: an index past the end of an array, a null reference, text that parseInt cannot read. In reading files and writing files you passed every IOException on with throws IOException, and in Scanner and console applications a typo stopped the program with an InputMismatchException. This lesson gives all of those a proper name, and the tools to deal with them.

When the program hits a wall#

Start with the failure, because the failure is the reason this topic exists. Here is a tiny program that divides two numbers, and the second number happens to be zero. A whole number divided by zero has no answer, so Java refuses to continue.

Press Run. The first line prints, then the program stops on the division and reports what went wrong. That report is what you see when the program throws an exception and nothing is there to catch it.

Divide.java
1public class Main {
2 public static void main(String[] args) {
3 int total = 100;
4 int people = 0;
5
6 System.out.println("About to divide...");
7 int share = total / people;
8 System.out.println("Each person gets " + share);
9 }
10}
Prints
›About to divide...
!Exception in thread "main" java.lang.ArithmeticException: / by zero
! at Main.main(Main.java:7)
Stops on line 7

ArithmeticException: / by zero

Whole numbers cannot be divided by zero. Check the divisor before dividing.

Notice two things. The first println ran, so the program really was alive and moving. And the last println never ran: the moment the division failed, Java abandoned the rest of main. An exception is an object Java creates at the instant something goes wrong while the program runs. It interrupts the normal flow and travels back up through the methods that are running, looking for code willing to handle it. If it finds none, the program stops and prints the report you just saw.

Key idea

An exception is Java's way of signalling “I cannot do what you asked” in the middle of running. Left unhandled, it stops the program where it stands.

Not a compile error
A compile error is caught before the program ever runs: a missing semicolon, a misspelled type. An exception is different. The code is valid Java. It only fails once it is running and meets a value it cannot cope with, here a divisor of zero. Compile time versus run time is the line between the two.

Catching it: try and catch#

Crashing is rarely what you want. A banking app that quits because one customer typed a zero is a bad banking app. Java lets you wrap a risky piece of code, attempt it, and if it throws, jump to a recovery plan instead of stopping. That is try and catch. Here is the same division, now caught. Step through it and watch the jump from the division straight into the catch block.

CatchDivide.java
1public class Main {
2 public static void main(String[] args) {
3 int total = 100;
4 int people = 0;
5
6 System.out.println("About to divide...");
7
8 try {
9 int share = total / people;
10 System.out.println("Each person gets " + share);
11 } catch (ArithmeticException e) {
12 System.out.println("Could not divide: " + e.getMessage());
13 }
14
15 System.out.println("Program keeps going.");
16 }
17}
Prints
›About to divide...
›Could not divide: / by zero
›Program keeps going.

Read the shape of it. The try block holds the code that might fail. If everything inside it succeeds, the catch block is skipped entirely. If any line inside try throws, Java stops that block at once and looks at the catch. Here the division throws an ArithmeticException, and the catch handles exactly that type, so control jumps inside it. The line that would have printed the share never runs, just like before, but now the program does not crash. It prints Could not divide: / by zero and carries on to the last line.

What is the e?#

The (ArithmeticException e) after catch looks like a method parameter, and it works like one. When the exception is thrown, Java hands the exception object to your catch block under the name you chose, here e. That object carries useful information. The most common thing to ask it is e.getMessage(), which returns the text describing what went wrong, here / by zero. You will meet e.printStackTrace() later in this lesson.

Predict the outputSkipped.java
1public class Main {
2 public static void main(String[] args) {
3 int[] scores = {90, 75};
4 try {
5 System.out.println("A");
6 System.out.println(scores[2]);
7 System.out.println("B");
8 } catch (ArrayIndexOutOfBoundsException e) {
9 System.out.println("C");
10 }
11 System.out.println("D");
12 }
13}

The array holds two scores, so index 2 does not exist. What does the program print?

It prints A, C and D. A prints normally. Reading scores[2] throws, so the rest of the try block, including B, is skipped, and the catch prints C. After the catch the program carries on as usual and prints D.

Recovering from bad input#

Two earlier lessons promised this moment. In wrapper classes, Integer.parseInt threw a NumberFormatException on text like 4o. With a try inside the loop, one bad entry no longer stops the whole program:

SkipBad.java
1public class Main {
2 public static void main(String[] args) {
3 String[] typed = {"12", "7", "4o", "20"};
4 int total = 0;
5
6 for (int i = 0; i < typed.length; i++) {
7 try {
8 total = total + Integer.parseInt(typed[i]);
9 } catch (NumberFormatException e) {
10 System.out.println("Skipping " + typed[i] + ": " + e.getMessage());
11 }
12 }
13
14 System.out.println("Total: " + total);
15 }
16}
Prints
›Skipping 4o: For input string: "4o"
›Total: 39

The program prints Skipping 4o: For input string: "4o" and then Total: 39. Because the try sits inside the loop, a failure ends only the current pass. The loop moves on to 20 and adds it.

In Scanner and console applications, nextInt() threw an InputMismatchException when someone typed a word. You can catch that too, and ask again. One detail matters: when nextInt()fails, the bad token stays in the scanner's buffer, so the next nextInt() would fail on it again. The catch has to read it out of the way with next().

ExerciseAskAge.java
This program asks for an age, and a typo stops it with an InputMismatchException. Wrap the read in try and catch so that a wrong answer prints, for example, twenty is not a whole number. and the loop asks again. The tests type their answers for you.
Catch the narrowest type that fits
It is tempting to write catch (Exception e) to catch absolutely everything, and sometimes that is right. Naming the specific type you expect, such as NumberFormatException, documents what you think can go wrong, and it stops you from silently swallowing a different, surprising failure you never meant to hide. Be as specific as you honestly can.
The empty catch block
The most tempting beginner mistake is to write catch (Exception e) { } with nothing inside, just to make the compiler stop complaining. It compiles, and it feels like progress, but the program now fails silently. The exception is caught and thrown away with no message and no clue, and later, when something further on goes wrong, you will have no idea a failure ever happened. At the very least, print the exception. An empty catch hides exactly the information you will need most.

Several catch blocks, and multi-catch#

One try can be followed by several catch blocks, each naming a different type. Java checks them from top to bottom and runs the first one whose type matches the exception. That lets you respond differently to different failures:

Several.java
1public class Main {
2 public static void main(String[] args) {
3 String[] inputs = {"8", "zero", "0"};
4
5 for (int i = 0; i < inputs.length; i++) {
6 try {
7 int n = Integer.parseInt(inputs[i]);
8 System.out.println("100 / " + n + " = " + (100 / n));
9 } catch (NumberFormatException e) {
10 System.out.println(inputs[i] + " is not a number");
11 } catch (ArithmeticException e) {
12 System.out.println("Cannot divide by " + inputs[i]);
13 }
14 }
15 }
16}
Prints
›100 / 8 = 12
›zero is not a number
›Cannot divide by 0

8 works and prints 100 / 8 = 12. zero fails inside parseInt, so the first catch runs. 0 parses fine but fails at the division, so the second one runs. At most one catch runs for each exception.

When two failures deserve the same response, one catch can name both types, separated by |. This is called a multi-catch:

MultiCatch.java
1public class Main {
2 public static void main(String[] args) {
3 String[] inputs = {"8", "zero", "0"};
4
5 for (int i = 0; i < inputs.length; i++) {
6 try {
7 int n = Integer.parseInt(inputs[i]);
8 System.out.println("100 / " + n + " = " + (100 / n));
9 } catch (NumberFormatException | ArithmeticException e) {
10 System.out.println("Skipping " + inputs[i] + ": " + e.getMessage());
11 }
12 }
13 }
14}
Prints
›100 / 8 = 12
›Skipping zero: For input string: "zero"
›Skipping 0: / by zero

The two failures now share one message, and e.getMessage() still tells them apart: For input string: "zero" for the first and / by zero for the second.

finally: the code that always runs#

Often there is something you must do whether the risky code succeeded or failed: close a file, release a connection, switch a sign back off. Putting it after the try is not safe, because if the catch itself returns or throws, that line is skipped. Java gives you a third block for exactly this. finally runs no matter how the try ended.

Cleanup.java
1public class Main {
2 public static void main(String[] args) {
3 System.out.println("Opening the report file...");
4
5 try {
6 System.out.println("Wrote one line.");
7 } catch (RuntimeException e) {
8 System.out.println("Something failed: " + e.getMessage());
9 } finally {
10 System.out.println("Closing the file. (this always runs)");
11 }
12
13 System.out.println("Done.");
14 }
15}
Prints
›Opening the report file...
›Wrote one line.
›Closing the file. (this always runs)
›Done.

Here nothing failed, so the catch was skipped, but the finally still ran. Now watch what happens when the try does throw. The catch runs, and then the finally runs after it, every single time.

CleanupFail.java
1public class Main {
2 public static void main(String[] args) {
3 System.out.println("Opening the report file...");
4
5 try {
6 int bad = 10 / 0;
7 System.out.println("This line never runs.");
8 } catch (ArithmeticException e) {
9 System.out.println("Something failed: " + e.getMessage());
10 } finally {
11 System.out.println("Closing the file. (this always runs)");
12 }
13
14 System.out.println("Done.");
15 }
16}
Prints
›Opening the report file...
›Something failed: / by zero
›Closing the file. (this always runs)
›Done.

Trace the order carefully. The division throws, and Java leaves the tryat once, so “This line never runs” is skipped. Java finds the matching catch and runs it, which prints the recovery message. Only then, on the way out of the whole structure, does the finallyrun and print “Closing the file”. finally always runs, and it runs last, after the try or the catch has done its part.

Key idea

try holds the risky work. catch is the recovery plan for when it fails. finally is the cleanup that happens either way. A plain try needs at least one catch or a finally after it.

Put the lines in orderCheckNumber.java
Put the lines in order so the program tries to read 42x as a number, reports that it is not one, and always prints the closing line. It should print Not a number: 42x and then Done checking 42x.

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

finally runs even when the try or the catch leaves the method with return. Java works out the value to return, runs the finally block, and only then hands the value back to the caller.

Predict the outputCheck.java
1public class Main {
2 public static void main(String[] args) {
3 System.out.println(check(5));
4 System.out.println(check(0));
5 }
6
7 static String check(int n) {
8 try {
9 System.out.println("checking " + n);
10 int r = 10 / n;
11 return "ok " + r;
12 } catch (ArithmeticException e) {
13 return "failed";
14 } finally {
15 System.out.println("finally for " + n);
16 }
17 }
18}

Each call to check returns a String, and main prints it. In what order do the lines appear?

For check(5), the try reaches return "ok 2", but finally prints its line before main receives the value and prints ok 2. For check(0), the division throws, the catch returns "failed", and again finally prints first. So finally for always comes before the returned value.

try-with-resources is a try too#

You have used try-with-resources since reading files. It is a try statement whose parentheses open a resource, such as a reader or a writer, and Java closes that resource when the block ends, as if you had written the close() in a finally yourself. That is why it can stand alone with no catch. It may also have catch blocks of its own, and that is the careful way to deal with a missing file, instead of throws IOException on main:

Settings.java
1import java.io.BufferedReader;
2import java.io.FileReader;
3import java.io.IOException;
4
5public class Main {
6 public static void main(String[] args) {
7 try (BufferedReader br = new BufferedReader(new FileReader("settings.txt"))) {
8 System.out.println("First setting: " + br.readLine());
9 } catch (IOException e) {
10 System.out.println("Could not read settings: " + e.getMessage());
11 }
12 System.out.println("Starting with default settings.");
13 }
14}
Prints
›Could not read settings: settings.txt (No such file or directory)
›Starting with default settings.

This page's disk has no settings.txt, so opening it throws a FileNotFoundException. That is a kind of IOException, so the catch handles it and prints Could not read settings: settings.txt (No such file or directory), and the program goes on with its defaults. main no longer says throws IOException, because nothing escapes it. When a reader did open, Java closes it before any catch or finally block of the same statement runs.

Two families: checked and unchecked#

Not all exceptions are treated the same by the compiler. Java sorts them into two families, and the family decides whether you must deal with an exception before your code will even compile.

Every exception class descends from one class, Throwable. Below it the family tree splits.

ThrowableErrorunchecked, not for catchingExceptioncheckedIOExceptioncheckedRuntimeExceptionunchecked
The top of the exception family tree. Each arrow points from a class to its parent.
  • Error covers serious trouble inside the JVM itself, such as the StackOverflowError you met in recursion, or running out of memory. Programs do not try to catch these.
  • RuntimeException and everything below it are unchecked exceptions. These are the runtime mistakes you have already met: dividing by zero, a bad index, a null reference. The compiler lets your code compile without a single try around them, on the theory that they signal bugs you should fix rather than failures you must routinely expect.
  • Every other exception is a checked exception, one the compiler insists you account for. The classic example is IOException, the failure that hangs over every file operation. A file can be missing or locked, or the disk can be full, and none of that is a bug in your code. Because such failure is expected, Java makes you say, in the code, that you have either handled it or passed it on.
ExceptionIts parentFamily
ArithmeticExceptionRuntimeExceptionUnchecked
NullPointerExceptionRuntimeExceptionUnchecked
ArrayIndexOutOfBoundsExceptionIndexOutOfBoundsException, a RuntimeExceptionUnchecked
NumberFormatExceptionIllegalArgumentException, a RuntimeExceptionUnchecked
InputMismatchExceptionNoSuchElementException, a RuntimeExceptionUnchecked
IOExceptionExceptionChecked
FileNotFoundExceptionIOExceptionChecked
The exceptions you have met so far, and where each one sits in the tree.

Forget to handle a checked exception, and the program does not compile. This read-along block shows what javac says:

MustHandle.java (does not compile)
read along
1import java.io.FileReader;
2
3public class Main {
4 public static void main(String[] args) {
5 // FileReader can throw a checked exception.
6 // With no try/catch and no throws, this does not compile:
7 FileReader r = new FileReader("data.txt");
8 }
9}
Output
Main.java:7: error: unreported exception FileNotFoundException; must be caught or declared to be thrown
        FileReader r = new FileReader("data.txt");
                       ^
1 error

That is not a crash at run time. The compiler refuses the program before it ever runs, points at the call, and names the checked exception that is going unhandled: FileNotFoundException, the exception that FileReader's constructor declares. The fix is either to wrap the call in a try and catch, or to declare the exception with throws, as the file lessons did and as the next sections explain.

Unchecked (RuntimeException and Error)
Checked (every other exception)
The compiler does not force you to handle it.
The compiler forces you to handle or declare it.
Usually signals a bug in your logic.
Usually signals an expected outside failure.
ArithmeticException, NullPointerException, NumberFormatException
IOException, FileNotFoundException
Code compiles with no try or throws.
Code does not compile until it is handled or declared.
Best fixed by correcting the code.
Best handled, because you cannot prevent it.
Quick check
Which of these must you catch or declare with throws before the program will compile?

The tree also explains which catch runs. A catch handles the type it names and every type below it in the tree, so catch (IOException e) also catches a FileNotFoundException. That is why the more specific catch must come first. If a catch for a parent type came first, the one for its child could never run, and javac refuses with a message such as error: exception NumberFormatException has already been caught.

Quick check
A try block throws a NumberFormatException. Its catch blocks are catch (ArithmeticException e) and then catch (IllegalArgumentException e). What happens?

Raising one yourself: throw#

So far exceptions have been thrown for you, by the division operator or parseInt. You can also throw one deliberately. This is how you guard your own methods against being asked to do something that makes no sense. If a withdrawal amount is negative, that is not a calculation to perform quietly. It is a caller's mistake, and saying so loudly is kinder than carrying on.

The keyword is throw, followed by a new exception object. Here a method refuses a negative amount by throwing an IllegalArgumentException, an unchecked exception that means “you passed me an argument I cannot accept”.

Withdraw.java
1public class Main {
2 public static void main(String[] args) {
3 int balance = 100;
4
5 System.out.println("Withdrawing 30...");
6 balance = withdraw(balance, 30);
7 System.out.println("Balance is now " + balance);
8
9 System.out.println("Withdrawing -5...");
10 try {
11 balance = withdraw(balance, -5);
12 } catch (IllegalArgumentException e) {
13 System.out.println("Caught: " + e.getMessage());
14 }
15 }
16
17 static int withdraw(int balance, int amount) {
18 if (amount < 0) {
19 throw new IllegalArgumentException("amount cannot be negative");
20 }
21 return balance - amount;
22 }
23}
Prints
›Withdrawing 30...
›Balance is now 70
›Withdrawing -5...
›Caught: amount cannot be negative

Follow the two calls. The first, withdraw(balance, 30), passes the check and returns 70, which prints. The second, withdraw(balance, -5), enters the if, runs throw, and the method ends right there without returning anything. The exception flies out of withdraw and back into main, where the surrounding try catches it and prints Caught: amount cannot be negative. The text you pass to the constructor is exactly what getMessage() later returns.

ExerciseThermostat.java
A thermostat accepts targets from 10 to 30 degrees. Finish setTarget so that it throws an IllegalArgumentException for a target below 10 or above 30, with a message such as 35 is outside 10 to 30 (using the real number). main already catches it.
throw versus throws
These two words look nearly identical and do different jobs. throw (no s) is an action: it raises an exception right now, in the body of a method. throws (with an s) is a label on a method's header: it warns callers that this method might let a certain exception escape. The next section is about throws.
Quick check
Which line raises an exception the moment it runs?

Passing it up: throws#

Catching is not the only response to a checked exception. Sometimes the method you are writing is not the right place to recover. A small method that reads a file has no idea what the program as a whole should do when the file is missing. Rather than guess, it can decline to handle the failure and let it travel up to a caller who does know. The way it says so is throws on its header.

ReadConfig.java
1import java.io.BufferedReader;
2import java.io.FileReader;
3import java.io.IOException;
4
5public class Main {
6 public static void main(String[] args) {
7 System.out.println("Trying to read config...");
8 try {
9 String first = loadConfig();
10 System.out.println("Config says: " + first);
11 } catch (IOException e) {
12 System.out.println("Could not read config: " + e.getMessage());
13 System.out.println("Using default settings instead.");
14 }
15 }
16
17 // This method does not handle the IOException itself.
18 // It declares throws, passing the problem up to its caller.
19 static String loadConfig() throws IOException {
20 try (BufferedReader br = new BufferedReader(new FileReader("config.txt"))) {
21 return br.readLine();
22 }
23 }
24}
Prints
›Trying to read config...
›Could not read config: config.txt (No such file or directory)
›Using default settings instead.

Read how the responsibility moves. loadConfig opens a file, which can throw a checked IOException. It chooses not to catch it. By writing throws IOExceptionafter its parameter list, it tells its callers: “I might let this exception out, so you must be ready for it.” That is what satisfies the compiler. Up in main, the call to loadConfig is wrapped in a try, so when the file is missing, as it is on this page's disk, the exception lands there and main carries on with default settings. Step through it and watch the loadConfig frame disappear from the call stack as the exception leaves it.

Key idea

For a checked exception you have two honest choices: handle it here with try and catch, or declare throws and pass it to whoever called you. The compiler makes you pick one.

Your own exception classes#

Java's exception classes cover common failures, but they cannot name the failures of your own program. A banking app wants to say InsufficientFundsException, which tells a reader far more than a general IllegalArgumentException. An exception class is an ordinary class that extends Exception, for a checked exception, or RuntimeException, for an unchecked one. Most need only a constructor that passes the message up to the parent with super, as you did in inheritance:

Funds.java
1public class Main {
2 public static void main(String[] args) {
3 Account account = new Account(100);
4 try {
5 account.withdraw(30);
6 System.out.println("Withdrew 30");
7 account.withdraw(500);
8 System.out.println("Withdrew 500");
9 } catch (InsufficientFundsException e) {
10 System.out.println("Refused: " + e.getMessage());
11 }
12 System.out.println("Balance: " + account.getBalance());
13 }
14}
15
16class InsufficientFundsException extends Exception {
17 InsufficientFundsException(String message) {
18 super(message);
19 }
20}
21
22class Account {
23 private int balance;
24
25 Account(int balance) {
26 this.balance = balance;
27 }
28
29 void withdraw(int amount) throws InsufficientFundsException {
30 if (amount > balance) {
31 throw new InsufficientFundsException("balance is " + balance + ", cannot withdraw " + amount);
32 }
33 balance = balance - amount;
34 }
35
36 int getBalance() {
37 return balance;
38 }
39}
Prints
›Withdrew 30
›Refused: balance is 70, cannot withdraw 500
›Balance: 70

The first withdrawal works. The second asks for more than the balance, so withdraw throws, the line Withdrew 500 is skipped, and main prints Refused: balance is 70, cannot withdraw 500, then Balance: 70. Because InsufficientFundsException extends Exception, it is checked: withdraw must declare it with throws, and every caller must catch it or declare it in turn.

  • Extend Exception when a caller can reasonably recover and should be made to think about it, like a withdrawal that is too large.
  • Extend RuntimeException when the failure means the caller broke the rules, like passing a negative amount.
  • End the class name with Exception, so anyone reading a catch knows what it is at once.

Reading a stack trace#

You learned to read a stack trace in reading errors and debugging: the first line says what went wrong, and the first at line that names your own file says where. Traces get longer when the failure happens several method calls deep. In this program, main calls start, which calls compute, and the deepest one divides by zero.

Trace.java
1public class Main {
2 public static void main(String[] args) {
3 start();
4 }
5
6 static void start() {
7 compute();
8 }
9
10 static void compute() {
11 int x = 5 / 0;
12 }
13}
Prints
!Exception in thread "main" java.lang.ArithmeticException: / by zero
! at Main.compute(Main.java:11)
! at Main.start(Main.java:7)
! at Main.main(Main.java:3)
Stops on line 11

ArithmeticException: / by zero

Whole numbers cannot be divided by zero. Check the divisor before dividing.

Step to the division and look at the call stack: three frames, compute on top of start on top of main. The stack trace the program prints is a written copy of that stack, read from the top down:

  1. 1The first line names the exception and its message: ArithmeticException: / by zero. That is what went wrong. Always read this line first.
  2. 2The topmost at line is where it went wrong: Main.compute(Main.java:11). The failure happened inside compute, at line 11 of the file. This is almost always the line you want to look at.
  3. 3Each at line below it is the caller of the one above. compute was called by start on line 7, which was called by main on line 3. Reading downwards walks you back out through the chain of calls, to where the program began.

So a stack trace is a story told backwards. The top is the scene of the failure, and each line beneath it is who sent you there.

You do not have to crash to see a trace. Inside a catch block, e.printStackTrace() prints the same report while the program keeps running. It writes to standard error, so in the console here its lines are marked with !:

PrintTrace.java
1public class Main {
2 public static void main(String[] args) {
3 try {
4 start();
5 } catch (ArithmeticException e) {
6 e.printStackTrace();
7 }
8 System.out.println("Still running.");
9 }
10
11 static void start() {
12 int x = 5 / 0;
13 }
14}
Prints
!java.lang.ArithmeticException: / by zero
! at Main.start(Main.java:12)
! at Main.main(Main.java:4)
›Still running.

The report starts with java.lang.ArithmeticException: / by zero, without the Exception in thread "main" part, because the exception did not end the program. Still running. follows it.

Caused by: one exception wrapped in another#

A method sometimes catches a low-level exception and throws a clearer one in its place, passing the original along as the cause. Most exception classes have a constructor that takes a message and a cause. Here loadTheme turns a missing file into an IllegalStateException, and nothing catches it:

Theme.java
1import java.io.BufferedReader;
2import java.io.FileReader;
3import java.io.IOException;
4
5public class Main {
6 public static void main(String[] args) {
7 String theme = loadTheme();
8 System.out.println("Theme: " + theme);
9 }
10
11 static String loadTheme() {
12 try (BufferedReader br = new BufferedReader(new FileReader("theme.txt"))) {
13 return br.readLine();
14 } catch (IOException e) {
15 throw new IllegalStateException("Could not load the theme", e);
16 }
17 }
18}
Prints
!Exception in thread "main" java.lang.IllegalStateException: Could not load the theme
! at Main.loadTheme(Main.java:15)
! at Main.main(Main.java:7)
!Caused by: java.io.FileNotFoundException: theme.txt (No such file or directory)
! at Main.loadTheme(Main.java:12)
! ... 1 more
Stops on line 15

IllegalStateException: Could not load the theme

A method was called at a moment when the object could not handle it.

The trace now has two parts. The first is the exception that ended the program, thrown on line 15. The second starts with Caused by: and is the original FileNotFoundException, thrown where the file was opened, on line 12. Its last line, ... 1 more, is shorthand: the remaining frame is the same as the last frame of the part above (main, line 7), so Java does not repeat it. Run in a terminal, real Java prints the same trace with the library's own lines added:

Terminal
read along
1$ javac Main.java
2$ java Main
3Exception in thread "main" java.lang.IllegalStateException: Could not load the theme
4 at Main.loadTheme(Main.java:15)
5 at Main.main(Main.java:7)
6Caused by: java.io.FileNotFoundException: theme.txt (No such file or directory)
7 at java.base/java.io.FileInputStream.open0(Native Method)
8 at java.base/java.io.FileInputStream.open(FileInputStream.java:185)
9 at java.base/java.io.FileInputStream.<init>(FileInputStream.java:139)
10 at java.base/java.io.FileInputStream.<init>(FileInputStream.java:109)
11 at java.base/java.io.FileReader.<init>(FileReader.java:60)
12 at Main.loadTheme(Main.java:12)
13 ... 1 more

When a trace has a Caused by section, the real problem is usually in the last one: here, a file that is not there. The outer exception tells you what the program was trying to do when it hit that problem.

More on traces and tools
The line numbers inside Java's own files change from one Java version to the next, so a terminal may show different ones. Annotations, docs, stack traces, and the IDE goes further, including the editor tools that jump straight to the line in a trace.

Putting it together#

Here is one slightly larger example that uses the pieces together: a method that writes a real file and passes its IOException up with throws, an IllegalArgumentException of its own for a bad file name, two catch blocks, and a finally. The loop tries a good name and an empty one.

SaveReport.java
1import java.io.BufferedWriter;
2import java.io.FileWriter;
3import java.io.IOException;
4
5public class Main {
6 public static void main(String[] args) {
7 String[] names = {"report.txt", ""};
8
9 for (int i = 0; i < names.length; i++) {
10 System.out.println("Saving to [" + names[i] + "]...");
11 try {
12 save(names[i], "Sales: 12");
13 System.out.println("Saved.");
14 } catch (IllegalArgumentException e) {
15 System.out.println("Refusing to save: " + e.getMessage());
16 } catch (IOException e) {
17 System.out.println("Disk problem: " + e.getMessage());
18 } finally {
19 System.out.println("Save attempt finished.");
20 }
21 }
22 }
23
24 static void save(String fileName, String text) throws IOException {
25 if (fileName.isEmpty()) {
26 throw new IllegalArgumentException("file name must not be empty");
27 }
28 try (BufferedWriter out = new BufferedWriter(new FileWriter(fileName))) {
29 out.write(text);
30 out.newLine();
31 }
32 }
33}
Prints
›Saving to [report.txt]...
›Saved.
›Save attempt finished.
›Saving to []...
›Refusing to save: file name must not be empty
›Save attempt finished.

Trace it through. The first pass saves Sales: 12 into report.txt (watch it appear in the Files section), prints Saved., and then the finally line. On the second pass, save("", ...) finds the empty name, so throw raises an IllegalArgumentException before any file is touched. The first catch handles it and prints the refusal, the finally runs again, and the loop ends. The IOException catch is there for a real disk failure, which did not happen in this run.

Try it

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

1
In your own words, what is the difference between a compile error and an exception?
2
In CleanupFail.java, why did “Closing the file” print after “Something failed”, and why did “This line never runs” not print at all?
3
Classify each as checked or unchecked: NullPointerException, IOException, ArrayIndexOutOfBoundsException, FileNotFoundException, InputMismatchException.
4
What is the difference between throw and throws? Use a sentence for each.
5
Given this stack trace, which method failed and on what line, and who called it?
java.lang.NullPointerException
at Main.parse(Main.java:22)
at Main.run(Main.java:11)
6
A trace starts with IllegalStateException: Could not load the theme and has a Caused by: java.io.FileNotFoundException: theme.txt section below it. What is the real problem, and where do you look first?

Practice

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

1
Write a method divide(int a, int b) that returns a / b, but throws an IllegalArgumentException with a clear message when b is zero, instead of letting the ArithmeticException happen. In main, call it once with a valid pair and once with a zero divisor, catching and printing the message for the second. Write down what you expect it to print before you run it.
2
Write a method readFirstLine(String name) that declares throws IOException and returns the first line of a file, using try-with-resources. In main, first write a small file with a BufferedWriter, as in writing files. Then call readFirstLine on that file and on a name that does not exist, catching the IOException and printing its message, with a finally that prints cleanup done. Explain in one sentence why main must catch it or declare it.
3
Write a short program, with no try anywhere, that crashes with an ArrayIndexOutOfBoundsException from three methods deep: main calls a, a calls b, and b indexes past the end of an array. On paper, sketch the stack trace you expect, the error line first and then the three at lines in order. Then run it and compare.
4
Write a checked exception class OutOfStockException and a Shop class whose buy(String item, int quantity) method throws it when there are not enough of the item left. In main, make one purchase that works and one that does not, and print the exception's message for the second.
5
In two or three sentences, argue for or against Java forcing you to handle checked exceptions. Use IOException as your example, and say what you would do instead if the language did not force you.

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