Kestrel Exams kestrel logo KESTREL EXAMSPRACTICE TO PASS
Java13 min readCode verified

Exceptions: try, catch, finally, checked vs unchecked

An exception is Java’s way of saying “something went wrong here” and handing the problem to whoever can deal with it. This guide covers why that beats older error handling, how to read the crash message, try, catch and finally with the counting rules exams ask about, the family tree of exceptions, and checked versus unchecked. Every listing was compiled and run on OpenJDK 21.

Practice this topic in the free app →All Java guides

A smoke alarm, not a cook watching every pan#

There are two ways to deal with kitchen fires. You can check every pan, every few seconds, all evening, and still miss one. Or you can install a smoke alarm: cook normally, and if something goes wrong, the alarm goes off and someone deals with it.

Older-style error handling is the first way. Every method returns a special value like -1 to mean “failed”, and every caller must remember to check it. Forget one check and the failure slips through silently.

An exception is the smoke alarm. When something goes wrong, Java creates an exception object and throws it. Normal work stops at that line, and Java looks for code that has said it can catch that kind of problem. The benefits over return-value error handling:

  • It cannot be silently ignored. An uncaught exception stops the program and says why.
  • Normal code and error code are kept apart. The main logic reads cleanly in a try block; the clean-up for each problem sits in its own catch.
  • It travels to whoever can handle it. A problem deep inside a method can be dealt with several calls up, without every method in between passing error codes back.
  • It carries information. The exception’s type and message say what went wrong, and the stack trace says where.

Reading the crash message#

When nothing catches an exception, Java prints it and stops. Learning to read this message is half the skill:

public class Trace {
    public static int parseAge(String text) {
        return Integer.parseInt(text);
    }

    public static void main(String[] args) {
        int age = parseAge("12a");
        System.out.println("Done");
    }
}
Exception in thread "main" java.lang.NumberFormatException: For input string: "12a"
	at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:67)
	at java.base/java.lang.Integer.parseInt(Integer.java:662)
	at java.base/java.lang.Integer.parseInt(Integer.java:778)
	at Trace.parseAge(Trace.java:3)
	at Trace.main(Trace.java:7)

Read it from the top:

  1. The first line names the problem: the exception’s type, NumberFormatException, and its message, For input string: "12a".
  2. The at lines are the stack trace: the chain of method calls that was running, most recent first. The first lines are inside Java’s own library, where parseInt noticed the problem.
  3. Look for the first line naming your file. Trace.java:3 in parseAge is where your code made the bad call, and the line below shows that main called parseAge from line 7.

Notice Done was never printed. The exception ended main on line 7.

try and catch#

Put the code that might fail inside try { }. Follow it with catch (TypeOfException e) { }, which runs only if that type of exception is thrown. The variable e is the exception object, and e.getMessage() gives its message.

public class TryCatch {
    public static void main(String[] args) {
        int[] divisors = {4, 0, 5};
        for (int d : divisors) {
            try {
                System.out.println("Dividing 20 by " + d);
                int answer = 20 / d;
                System.out.println("Answer: " + answer);
            } catch (ArithmeticException e) {
                System.out.println("Could not divide: " + e.getMessage());
            }
        }
        System.out.println("Finished");
    }
}
Dividing 20 by 4
Answer: 5
Dividing 20 by 0
Could not divide: / by zero
Dividing 20 by 5
Answer: 4
Finished

Follow the second pass of the loop, the one with a zero. The division threw an ArithmeticException, so the rest of the try block was skipped: the “Answer” line never printed for it. The matching catch ran, and then the program carried on with the next pass as if nothing had happened. That is the whole flow: try until something throws, jump to the matching catch, then continue after the whole try statement.

Several catch blocks, and their order#

One try can have several catch blocks, one per kind of problem. Java checks them top to bottom and runs only the first one that matches.

public class Several {
    public static void check(String text) {
        try {
            int n = Integer.parseInt(text);
            int[] slots = new int[3];
            slots[n] = 100 / n;
            System.out.println(text + ": stored");
        } catch (NumberFormatException e) {
            System.out.println(text + ": not a number");
        } catch (ArithmeticException e) {
            System.out.println(text + ": cannot divide by zero");
        } catch (Exception e) {
            System.out.println(text + ": something else, " + e.getClass().getSimpleName());
        }
    }

    public static void main(String[] args) {
        check("2");
        check("two");
        check("0");
        check("7");
    }
}
2: stored
two: not a number
0: cannot divide by zero
7: something else, ArrayIndexOutOfBoundsException

Because a catch for a parent type also catches all its children, order matters. A catch (Exception e) catches nearly everything, so it must come last. Put it first and every catch below it could never run, and the compiler treats that dead code as an error:

public class BadOrder {
    public static void main(String[] args) {
        try {
            int x = 10 / 0;
        } catch (Exception e) {
            System.out.println("general");
        } catch (ArithmeticException e) {
            System.out.println("specific");
        }
    }
}
BadOrder.java:7: error: exception ArithmeticException has already been caught
        } catch (ArithmeticException e) {
          ^
1 error

Rule: most specific first, most general last. If two unrelated exceptions need the same handling, one catch can name both with a vertical bar: catch (ArithmeticException | NumberFormatException e).

finally: the block that always runs#

A finally block goes after the catches and runs no matter what: if the try finished normally, if an exception was caught, even if the try said return. It is for clean-up that must happen either way, such as closing a file.

public class Finally {
    public static String attempt(int d) {
        try {
            System.out.println("  try: 10 / " + d + " = " + (10 / d));
            return "returned normally";
        } catch (ArithmeticException e) {
            System.out.println("  catch: " + e.getMessage());
            return "returned from catch";
        } finally {
            System.out.println("  finally runs");
        }
    }

    public static void main(String[] args) {
        System.out.println(attempt(2));
        System.out.println(attempt(0));
    }
}
  try: 10 / 2 = 5
  finally runs
returned normally
  catch: / by zero
  finally runs
returned from catch

The first call returned from inside the try, and finally still ran before the method actually left. The second call threw, was caught, and finally ran after the catch. The file I/O guide shows finally’s modern replacement for closing files, try-with-resources.

The minimum and maximum of each block#

Exams like to ask how many of each block a try statement may have. The answer is a short table:

BlockMinimumMaximum
try11
catch0 (if there is a finally)no limit
finally0 (if there is at least one catch)1

So try plus catch, try plus finally, and try plus catches plus finally are all legal. A try on its own is not:

public class Lonely {
    public static void main(String[] args) {
        try {
            System.out.println("hello");
        }
        System.out.println("after");
    }
}
Lonely.java:3: error: 'try' without 'catch', 'finally' or resource declarations
        try {
        ^
1 error

The order is fixed too: try, then the catches, then finally. (The message mentions “resource declarations”: that is try-with-resources, the one form that may stand alone, covered in the file I/O guide.)

The exception family tree#

Exceptions are objects, and their classes form an inheritance tree, which is why a catch for a parent catches the children too:

Throwable
 |-- Error                        serious problems in the JVM: do not catch
 |    |-- OutOfMemoryError
 |    |-- StackOverflowError
 |-- Exception
      |-- IOException             CHECKED
      |    |-- FileNotFoundException
      |-- (other checked exceptions)
      |-- RuntimeException        UNCHECKED, and so are all its children
           |-- ArithmeticException
           |-- NullPointerException
           |-- ClassCastException
           |-- IllegalArgumentException
           |    |-- NumberFormatException
           |-- IndexOutOfBoundsException
                |-- ArrayIndexOutOfBoundsException
                |-- StringIndexOutOfBoundsException

At the top is Throwable: anything that can be thrown. Its two children split the world: Error means the Java machine itself is in trouble, such as running out of memory, and your program should not try to recover. Exception covers the problems a program can reasonably handle.

Checked versus unchecked#

This is the distinction exams test most, and it comes down to one question: does the compiler force you to deal with it?

  • Checked exceptions are every Exception that is not a RuntimeException, such as IOException and FileNotFoundException. They are problems outside your program’s control: a file that is missing, a network that is down. The compiler checks that you either catch them or declare them, and refuses to compile otherwise.
  • Unchecked exceptions are RuntimeException and all its children, plus Error. They usually mean a bug in the code, such as dividing by zero, a null reference or a bad index. The compiler does not force you to handle them; the fix is usually to correct the code.
import java.io.File;
import java.util.Scanner;

public class Checked {
    public static void main(String[] args) {
        Scanner in = new Scanner(new File("scores.txt"));
        System.out.println(in.nextLine());
    }
}
Checked.java:6: error: unreported exception FileNotFoundException; must be caught or declared to be thrown
        Scanner in = new Scanner(new File("scores.txt"));
                     ^
1 error

The cure is one of two things. Catch it with try/catch, or declare it by adding throws FileNotFoundException to the method heading, which passes the duty to whoever calls the method. The ArithmeticException earlier needed neither, because it is unchecked.

throw versus throws#

Two keywords, one letter apart:

  • throw (a statement inside a method) creates and throws an exception right now: throw new IllegalArgumentException("age cannot be negative");.
  • throws (in the method heading) warns callers that this method may throw an exception it does not catch itself. It is required for checked exceptions and optional for unchecked ones.
class Student {
    private int age;

    public void setAge(int age) {
        if (age < 0) {
            throw new IllegalArgumentException("age cannot be negative: " + age);
        }
        this.age = age;
    }

    public int getAge() {
        return age;
    }
}

public class Throw {
    public static void main(String[] args) {
        Student s = new Student();
        s.setAge(19);
        try {
            s.setAge(-4);
        } catch (IllegalArgumentException e) {
            System.out.println("Rejected: " + e.getMessage());
        }
        System.out.println("Age is still " + s.getAge());
    }
}
Rejected: age cannot be negative: -4
Age is still 19

The setter refuses a bad value by throwing, so no Student can ever hold a negative age. IllegalArgumentException is the standard choice for “you passed me something invalid”, and because it is unchecked, the setter needed no throws clause. You can also write your own exception class by extending Exception (checked) or RuntimeException (unchecked).

One sentence to carry into the exam

Code that might fail goes in try; the first matching catch handles it, most specific first; finally always runs. Checked exceptions (not RuntimeExceptions) must be caught or declared with throws; unchecked ones usually mean fix the bug.

Where these go wrong#

  • Catching Exception before a more specific type. The later catch can never run, and the compiler rejects it.
  • Expecting the rest of the try block to run after an exception. Everything after the throwing line is skipped.
  • A try with no catch and no finally. Not legal, except for try-with-resources.
  • Mixing up throw and throws. throw throws one now; throws in the heading warns callers.
  • Calling NumberFormatException or NullPointerException checked. Both are RuntimeExceptions, so unchecked.
  • Catching an exception and doing nothing. An empty catch { } hides the problem, which is exactly what exceptions exist to prevent. At least print the message.
  • Catching Errors. Out of memory or a stack overflow is not something a program can sensibly fix.

Practice#

Answer each one on paper before you look at the solutions. Every answer that involves running code was checked by compiling and running it.

  1. Give two benefits of exceptions over returning a special value such as -1 to signal an error.
  2. What does this print?
    String[] inputs = {"8", "x", "0"};
    for (String in : inputs) {
        try {
            int n = Integer.parseInt(in);
            System.out.println("A " + (16 / n));
        } catch (NumberFormatException e) {
            System.out.println("B");
        } catch (ArithmeticException e) {
            System.out.println("C");
        } finally {
            System.out.println("D");
        }
    }
    System.out.println("E");
  3. Classify each as checked or unchecked: IOException, ArithmeticException, FileNotFoundException, NullPointerException, NumberFormatException.
  4. State the minimum and maximum number of try, catch and finally blocks in one try statement.
  5. Write a method that keeps asking for a positive whole number until it gets one, using Integer.parseInt and a try/catch. Test it by giving the method a Scanner that reads from a String instead of the keyboard, such as new Scanner("ten\n-3\n7\n").
  6. A method calls new Scanner(new File("data.txt")) and does not catch anything. What must its heading include, and what does that do?

Solutions#

  1. Any two of: an exception cannot be silently ignored, because an uncaught one stops the program; error-handling code is kept separate from normal code; an exception travels up the call chain to whichever method can handle it; and it carries information, its type, message and stack trace.
  2. "8" divides cleanly, "x" fails to parse, "0" parses but divides by zero, and finally runs every time:
    public class QFlow {
        public static void main(String[] args) {
            String[] inputs = {"8", "x", "0"};
            for (String in : inputs) {
                try {
                    int n = Integer.parseInt(in);
                    System.out.println("A " + (16 / n));
                } catch (NumberFormatException e) {
                    System.out.println("B");
                } catch (ArithmeticException e) {
                    System.out.println("C");
                } finally {
                    System.out.println("D");
                }
            }
            System.out.println("E");
        }
    }
    A 2
    D
    B
    D
    C
    D
    E
  3. Checked: IOException and FileNotFoundException. Unchecked: ArithmeticException, NullPointerException and NumberFormatException, which all descend from RuntimeException.
  4. Exactly one try. Zero or more catch blocks, with no upper limit. Zero or one finally. There must be at least one catch or a finally (try-with-resources is the one form that may have neither).
  5. Loop until a valid value is returned; the catch handles text that is not a number, and an if handles numbers that are not positive. The method takes the Scanner as a parameter, so the same code works with new Scanner(System.in) for real typing:
    import java.util.Scanner;
    
    public class QRead {
        public static int readPositive(Scanner in) {
            while (true) {
                System.out.print("Enter a positive whole number: ");
                String line = in.nextLine();
                System.out.println(line);
                try {
                    int n = Integer.parseInt(line);
                    if (n > 0) {
                        return n;
                    }
                    System.out.println("That is not positive.");
                } catch (NumberFormatException e) {
                    System.out.println("That is not a whole number.");
                }
            }
        }
    
        public static void main(String[] args) {
            Scanner typed = new Scanner("ten\n-3\n7\n");
            int n = readPositive(typed);
            System.out.println("Got " + n);
        }
    }
    Enter a positive whole number: ten
    That is not a whole number.
    Enter a positive whole number: -3
    That is not positive.
    Enter a positive whole number: 7
    Got 7
  6. throws FileNotFoundException (or a parent such as throws IOException). It declares that the method may throw that checked exception, which satisfies the compiler and passes the duty to catch or declare it on to every caller.

Test-readiness checklist#

These are the items from the course’s Exam 2 topic list that this guide covers. Tick each one only when you can do it without looking.

  • ☐  I can explain the benefits of exception handling over error handling with return values.
  • ☐  I can draw the exception hierarchy from Throwable down, with Error, Exception and RuntimeException.
  • ☐  I can name the types of exceptions and sort examples into checked and unchecked.
  • ☐  I can write try-catch-finally statements, order several catches correctly, and trace the flow.
  • ☐  I can state the minimum and maximum number of each block.

Test yourself in the free Kestrel Exams app

40 practice questions on try, catch, finally and checked exceptions, with every code answer checked by compiling and running it. Free, offline, no account.

Practise exceptions →

Frequently asked questions#

What are the benefits of exception handling over traditional error handling?

Exceptions cannot be silently ignored, keep error-handling code separate from normal logic, travel up the call stack to the method able to handle them, and carry their type, message and stack trace. Error codes returned from methods must be checked by every caller and are easy to forget.

What is the difference between checked and unchecked exceptions?

Checked exceptions are subclasses of Exception that are not RuntimeExceptions, such as IOException. The compiler requires them to be caught or declared with throws. Unchecked exceptions are RuntimeException and its subclasses, such as ArithmeticException and NullPointerException, plus Error; the compiler does not require them to be handled.

Does finally always run?

Yes, in practice: the finally block runs whether the try block completes normally, throws an exception that is caught, or throws one that is not caught, and even when the try or catch block executes a return. Only exceptional events such as the program calling System.exit or the machine crashing stop it.

How many catch and finally blocks can a try have?

A try statement has exactly one try block, zero or more catch blocks with no fixed maximum, and zero or one finally block. It must have at least one catch or a finally, except for try-with-resources.

Why does the order of catch blocks matter?

Java uses the first catch block whose type matches the exception. A catch for a parent class also matches all its subclasses, so a general catch such as Exception placed before a specific one would make the specific one unreachable, which is a compile error. Order catches from most specific to most general.

What is the difference between throw and throws?

throw is a statement that throws an exception object immediately, as in throw new IllegalArgumentException("bad value");. throws appears in a method heading and declares exceptions the method may throw without catching them; it is required for checked exceptions and optional for unchecked ones.

Suggest a change

Something here not clear? A topic you wish we covered? Tell us. We read every message, and a request is the fastest way to get a guide written — several of these exist because somebody asked.