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 BasithUpdated 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.
- Say in plain words what an exception is and what happens when nothing catches it.
- Catch a failing operation with
tryandcatch, one type or several, so the program recovers. - Use
finallyand 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 withthrows, and write your own exception class. - Read a stack trace several calls deep, including one with a
Caused bysection.
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.
public class Main { public static void main(String[] args) { int total = 100; int people = 0; System.out.println("About to divide..."); int share = total / people; System.out.println("Each person gets " + share); }}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 ideaAn 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.
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.
public class Main { public static void main(String[] args) { int total = 100; int people = 0; System.out.println("About to divide..."); try { int share = total / people; System.out.println("Each person gets " + share); } catch (ArithmeticException e) { System.out.println("Could not divide: " + e.getMessage()); } System.out.println("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.
public class Main { public static void main(String[] args) { int[] scores = {90, 75}; try { System.out.println("A"); System.out.println(scores[2]); System.out.println("B"); } catch (ArrayIndexOutOfBoundsException e) { System.out.println("C"); } System.out.println("D"); }}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:
public class Main { public static void main(String[] args) { String[] typed = {"12", "7", "4o", "20"}; int total = 0; for (int i = 0; i < typed.length; i++) { try { total = total + Integer.parseInt(typed[i]); } catch (NumberFormatException e) { System.out.println("Skipping " + typed[i] + ": " + e.getMessage()); } } System.out.println("Total: " + total); }}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().
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 (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.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:
public class Main { public static void main(String[] args) { String[] inputs = {"8", "zero", "0"}; for (int i = 0; i < inputs.length; i++) { try { int n = Integer.parseInt(inputs[i]); System.out.println("100 / " + n + " = " + (100 / n)); } catch (NumberFormatException e) { System.out.println(inputs[i] + " is not a number"); } catch (ArithmeticException e) { System.out.println("Cannot divide by " + inputs[i]); } } }}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:
public class Main { public static void main(String[] args) { String[] inputs = {"8", "zero", "0"}; for (int i = 0; i < inputs.length; i++) { try { int n = Integer.parseInt(inputs[i]); System.out.println("100 / " + n + " = " + (100 / n)); } catch (NumberFormatException | ArithmeticException e) { System.out.println("Skipping " + inputs[i] + ": " + e.getMessage()); } } }}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.
public class Main { public static void main(String[] args) { System.out.println("Opening the report file..."); try { System.out.println("Wrote one line."); } catch (RuntimeException e) { System.out.println("Something failed: " + e.getMessage()); } finally { System.out.println("Closing the file. (this always runs)"); } System.out.println("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.
public class Main { public static void main(String[] args) { System.out.println("Opening the report file..."); try { int bad = 10 / 0; System.out.println("This line never runs."); } catch (ArithmeticException e) { System.out.println("Something failed: " + e.getMessage()); } finally { System.out.println("Closing the file. (this always runs)"); } System.out.println("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
tryholds the risky work.catchis the recovery plan for when it fails.finallyis the cleanup that happens either way. A plaintryneeds at least onecatchor afinallyafter it.
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.
public class Main { public static void main(String[] args) { System.out.println(check(5)); System.out.println(check(0)); } static String check(int n) { try { System.out.println("checking " + n); int r = 10 / n; return "ok " + r; } catch (ArithmeticException e) { return "failed"; } finally { System.out.println("finally for " + n); } }}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:
import java.io.BufferedReader;import java.io.FileReader;import java.io.IOException; public class Main { public static void main(String[] args) { try (BufferedReader br = new BufferedReader(new FileReader("settings.txt"))) { System.out.println("First setting: " + br.readLine()); } catch (IOException e) { System.out.println("Could not read settings: " + e.getMessage()); } System.out.println("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.
Errorcovers serious trouble inside the JVM itself, such as theStackOverflowErroryou met in recursion, or running out of memory. Programs do not try to catch these.RuntimeExceptionand everything below it are unchecked exceptions. These are the runtime mistakes you have already met: dividing by zero, a bad index, anullreference. The compiler lets your code compile without a singletryaround 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.
| Exception | Its parent | Family |
|---|---|---|
ArithmeticException | RuntimeException | Unchecked |
NullPointerException | RuntimeException | Unchecked |
ArrayIndexOutOfBoundsException | IndexOutOfBoundsException, a RuntimeException | Unchecked |
NumberFormatException | IllegalArgumentException, a RuntimeException | Unchecked |
InputMismatchException | NoSuchElementException, a RuntimeException | Unchecked |
IOException | Exception | Checked |
FileNotFoundException | IOException | Checked |
Forget to handle a checked exception, and the program does not compile. This read-along block shows what javac says:
import java.io.FileReader; public class Main { public static void main(String[] args) { // FileReader can throw a checked exception. // With no try/catch and no throws, this does not compile: FileReader r = new FileReader("data.txt"); }}Main.java:7: error: unreported exception FileNotFoundException; must be caught or declared to be thrown
FileReader r = new FileReader("data.txt");
^
1 errorThat 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.
ArithmeticException, NullPointerException, NumberFormatExceptionIOException, FileNotFoundExceptionthrows 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.
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”.
public class Main { public static void main(String[] args) { int balance = 100; System.out.println("Withdrawing 30..."); balance = withdraw(balance, 30); System.out.println("Balance is now " + balance); System.out.println("Withdrawing -5..."); try { balance = withdraw(balance, -5); } catch (IllegalArgumentException e) { System.out.println("Caught: " + e.getMessage()); } } static int withdraw(int balance, int amount) { if (amount < 0) { throw new IllegalArgumentException("amount cannot be negative"); } return balance - amount; }}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.
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 (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.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.
import java.io.BufferedReader;import java.io.FileReader;import java.io.IOException; public class Main { public static void main(String[] args) { System.out.println("Trying to read config..."); try { String first = loadConfig(); System.out.println("Config says: " + first); } catch (IOException e) { System.out.println("Could not read config: " + e.getMessage()); System.out.println("Using default settings instead."); } } // This method does not handle the IOException itself. // It declares throws, passing the problem up to its caller. static String loadConfig() throws IOException { try (BufferedReader br = new BufferedReader(new FileReader("config.txt"))) { return br.readLine(); } }}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 ideaFor a checked exception you have two honest choices: handle it here with
tryandcatch, or declarethrowsand 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:
public class Main { public static void main(String[] args) { Account account = new Account(100); try { account.withdraw(30); System.out.println("Withdrew 30"); account.withdraw(500); System.out.println("Withdrew 500"); } catch (InsufficientFundsException e) { System.out.println("Refused: " + e.getMessage()); } System.out.println("Balance: " + account.getBalance()); }} class InsufficientFundsException extends Exception { InsufficientFundsException(String message) { super(message); }} class Account { private int balance; Account(int balance) { this.balance = balance; } void withdraw(int amount) throws InsufficientFundsException { if (amount > balance) { throw new InsufficientFundsException("balance is " + balance + ", cannot withdraw " + amount); } balance = balance - amount; } int getBalance() { return balance; }}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
Exceptionwhen a caller can reasonably recover and should be made to think about it, like a withdrawal that is too large. - Extend
RuntimeExceptionwhen the failure means the caller broke the rules, like passing a negative amount. - End the class name with
Exception, so anyone reading acatchknows 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.
public class Main { public static void main(String[] args) { start(); } static void start() { compute(); } static void compute() { int x = 5 / 0; }}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:
- 1The first line names the exception and its message:
ArithmeticException: / by zero. That is what went wrong. Always read this line first. - 2The topmost
atline is where it went wrong:Main.compute(Main.java:11). The failure happened insidecompute, at line 11 of the file. This is almost always the line you want to look at. - 3Each
atline below it is the caller of the one above.computewas called bystarton line 7, which was called bymainon 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.
The same report, from inside a catch#
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 !:
public class Main { public static void main(String[] args) { try { start(); } catch (ArithmeticException e) { e.printStackTrace(); } System.out.println("Still running."); } static void start() { int x = 5 / 0; }}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:
import java.io.BufferedReader;import java.io.FileReader;import java.io.IOException; public class Main { public static void main(String[] args) { String theme = loadTheme(); System.out.println("Theme: " + theme); } static String loadTheme() { try (BufferedReader br = new BufferedReader(new FileReader("theme.txt"))) { return br.readLine(); } catch (IOException e) { throw new IllegalStateException("Could not load the theme", e); } }}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:
$ javac Main.java$ java MainException 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 java.base/java.io.FileInputStream.open0(Native Method) at java.base/java.io.FileInputStream.open(FileInputStream.java:185) at java.base/java.io.FileInputStream.<init>(FileInputStream.java:139) at java.base/java.io.FileInputStream.<init>(FileInputStream.java:109) at java.base/java.io.FileReader.<init>(FileReader.java:60) at Main.loadTheme(Main.java:12) ... 1 moreWhen 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.
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.
import java.io.BufferedWriter;import java.io.FileWriter;import java.io.IOException; public class Main { public static void main(String[] args) { String[] names = {"report.txt", ""}; for (int i = 0; i < names.length; i++) { System.out.println("Saving to [" + names[i] + "]..."); try { save(names[i], "Sales: 12"); System.out.println("Saved."); } catch (IllegalArgumentException e) { System.out.println("Refusing to save: " + e.getMessage()); } catch (IOException e) { System.out.println("Disk problem: " + e.getMessage()); } finally { System.out.println("Save attempt finished."); } } } static void save(String fileName, String text) throws IOException { if (fileName.isEmpty()) { throw new IllegalArgumentException("file name must not be empty"); } try (BufferedWriter out = new BufferedWriter(new FileWriter(fileName))) { out.write(text); out.newLine(); } }}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.
CleanupFail.java, why did “Closing the file” print after “Something failed”, and why did “This line never runs” not print at all?NullPointerException, IOException, ArrayIndexOutOfBoundsException, FileNotFoundException, InputMismatchException.throw and throws? Use a sentence for each.java.lang.NullPointerException at Main.parse(Main.java:22) at Main.run(Main.java:11)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.
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.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.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.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.IOException as your example, and say what you would do instead if the language did not force you.Further reading#
- The exceptions tutorial on dev.java, the official Java learning site
- Throwable in the Java SE 25 API documentation, with getMessage, getCause and the stack trace format, Caused by included
- Exceptions in the Java Language Specification, chapter 11, the exact rules for checked and unchecked exceptions
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