THINK FIRST·CODE LATER

← Java Programming
Chapter 16 · Week 13

Exception Handling

Before You Start: What You Must Be Able to Do

Before the questions, make sure you can: draw the part of the exception hierarchy that matters (Throwable, Error, Exception, RuntimeException); state the difference between a checked and an unchecked exception and what the compiler demands of each; predict the exact output of a try/catch/finally block, with and without an exception; order several catch clauses correctly and say why the reverse order does not compile; read nested try blocks; use throw and throws correctly; and write a custom exception class.

What an exception is

An exception is an object that represents an abnormal situation. When one is thrown, normal execution stops and the JVM looks for a matching catch in the current method, then in its caller, and so on up the call stack. If none is found, the program terminates and the stack trace is printed.

Throwable the root of everything that can be thrown
Error serious JVM problems (OutOfMemoryError); do not catch these
Exception application problems; checked, except the branch below
RuntimeException unchecked: NullPointerException, ArithmeticException, ArrayIndexOutOfBoundsException, StringIndexOutOfBoundsException, NumberFormatException, InputMismatchException, ClassCastException, IllegalArgumentException
  • Checked exceptions (IOException, FileNotFoundException, ParseException) must be handled or declared: either catch them, or add throws to the method heading. The compiler enforces this.
  • Unchecked exceptions (anything under RuntimeException) need no declaration. They usually indicate a programming bug, and the right response is normally to fix the code rather than to catch it.
  • Unchecked does not mean fatal, and checked does not mean likely.

try, catch, finally

try {
    int data = 100 / divisor;     // statements that might throw
    System.out.print("Try ");     // SKIPPED if the line above throws
} catch (ArithmeticException e) {
    System.out.print("Catch ");   // runs only for a matching exception
} finally {
    System.out.print("Finally "); // runs ALWAYS: exception or not, even after return
}
System.out.print("End");

Three behaviours to internalise:

  1. When an exception is thrown, the rest of the try block is abandoned; control never comes back to it.
  2. If a catch handles the exception, execution continues normally after the whole try statement.
  3. finally runs in every case — normal completion, handled exception, unhandled exception, even an early return — which makes it the place to release resources.

Several catch clauses

Clauses are tried in order and the first matching one wins, so they must be written from the most specific to the most general:

try { ... }
catch (ArrayIndexOutOfBoundsException e) { System.out.print("Specific"); }
catch (Exception e)                      { System.out.print("General");  }

Reversing them does not compile: the specific clause would be unreachable. Since Java 7, a multi-catch handles several unrelated types at once, and the parameter is implicitly final:

catch (NumberFormatException | ArithmeticException e) { ... }   // legal
catch (Exception | ArithmeticException e) { ... }               // ERROR: one is a subclass

Nested try blocks are resolved from the inside out: an inner catch that matches handles the exception, and the outer block continues after the inner statement as if nothing had happened.

throw and throws

public void setAge(int age) {
    if (age < 0) throw new IllegalArgumentException("age must be >= 0");  // throw an object
    this.age = age;
}
public void load(String file) throws IOException { ... }   // declare: callers must deal with it

throw is a statement that raises one exception object now; throws is a clause in a method heading that warns callers. A method may declare several, separated by commas.

Custom exceptions

public class TimeFormatException extends Exception {     // checked: extends Exception
    public TimeFormatException(String message) { super(message); }
}
public class MessageTooLongException extends RuntimeException {   // unchecked
    public MessageTooLongException(String message) { super(message); }
}

Extend Exception when the caller can reasonably be expected to recover and should be forced to think about it; extend RuntimeException for programming errors. Pass the message to super so that getMessage() works.

Good practice

Catch what you can actually handle; do not write an empty catch block; never catch Exception simply to silence the compiler; keep try blocks small; and close resources in finally or, better, with try-with-resources: try (Scanner in = new Scanner(new File("data.txt"))) { ... }.

Remember
  • Checked = handle or declare; unchecked = RuntimeException and its subclasses.
  • The rest of the try block is skipped once something is thrown.
  • finally always runs.
  • Specific catch clauses come before general ones.
  • throw raises; throws declares.
  • A custom checked exception extends Exception; an unchecked one extends RuntimeException.
Common Pitfalls
  • catch (Exception e) first, making later clauses unreachable.
  • Expecting statements after the failing line inside try to run.
  • Catching NullPointerException instead of testing for null.
  • Declaring throws on a method whose body cannot throw a checked exception (legal but misleading), or forgetting it when it can (a compile error).

Ready? Close the notes and practise.

38 questions. Predict the output before you check — that is the skill the exam measures.