SEN1002 · Week 9 · Exception Handling

Exception handling: try, catch, finally, throw, throws

Where control goes when an error happens, when finally runs, the checked vs unchecked difference, and writing your own exception class. The favorite of output-tracing questions.

exceptiontrycatchfinallythrowthrowschecked exceptionunchecked exceptionRuntimeExceptionErrorstack unwindingmulti-catchcustom exceptiongetMessageexception chaining

It is easy to write what a program does when everything is fine. Exception handling organizes what happens when things go wrong: the user types a letter instead of a number, the file does not exist, an array bound is exceeded. In the exam this topic almost always comes as a “What is printed?” tracing question: which lines are skipped, which catch runs, where finally steps in. Alongside it come compile errors about checked exceptions and the “write your own exception class” question.

Golden rules:

  1. The moment an exception is thrown, the rest of the try block is skipped. Control goes to the matching catch.
  2. Catches are tried from top to bottom, and the first one that fits runs. What fits is the exception’s own type or its superclass.
  3. finally always runs: with or without an exception, even with a return.
  4. An uncaught exception ends the method immediately and passes to the caller (stack unwinding).
  5. A checked exception is either caught or declared with throws. There is no such requirement for unchecked ones (RuntimeException).

try, catch, finally

try {
    int[] arr = {1, 2, 3};
    System.out.println(arr[1]);
    System.out.println(arr[3]);        // the exception happens here
    System.out.println("not printed"); // skipped
} catch (ArrayIndexOutOfBoundsException e) {
    System.out.println("Caught: " + e.getMessage());
} finally {
    System.out.println("finally");
}
System.out.println("after");
2
Caught: Index 3 out of bounds for length 3
finally
after

Rules:

  • A try cannot stand alone: it needs at least one catch or one finally. Otherwise 'try' without 'catch', 'finally' or resource declarations.
  • A try can have more than one catch; only one of them runs.
  • finally is optional; if present, it runs once at the very end. It is used for closing resources (files, connections).

Multiple catches and their order

static void trace(int k) {
    try {
        System.out.print("A");
        if (k == 1) throw new IllegalStateException();
        if (k == 2) throw new IllegalArgumentException();
        System.out.print("B");
    } catch (IllegalStateException e) {
        System.out.print("C");
    } catch (RuntimeException e) {       // superclass: catches every RuntimeException
        System.out.print("D");
    } finally {
        System.out.print("E");
    }
    System.out.println("F");
}
trace(0);
trace(1);
trace(2);
ABEF
ACEF
ADEF

If you write a general catch (Exception, RuntimeException) before specific ones, the catch below it never gets its turn and the compiler counts this as an error: exception NumberFormatException has already been caught.

Multi-catch: to combine catches that do the same job, write catch (NumberFormatException | ArithmeticException e). The list cannot contain types that are subclasses of each other.

static void parse(String s) {
    try {
        int n = Integer.parseInt(s);
        System.out.println(100 / n);
    } catch (NumberFormatException | ArithmeticException e) {
        System.out.println("bad input: " + e.getClass().getSimpleName());
    }
}
parse("4"); parse("x"); parse("0");
25
bad input: NumberFormatException
bad input: ArithmeticException

finally and return

static int f() {
    try { return 1; }
    finally { System.out.println("finally in f"); }
}
static int h() {
    int x = 10;
    try { return x; }          // the return value was stored as 10
    finally { x = 20; }        // does not affect the stored value
}
System.out.println(f());
System.out.println(h());
finally in f
1
10

PITFALL · return inside finally. If you write return 2; in finally, even an exception thrown from the try is swallowed and the method returns 2 (in the test run, a try with throw new RuntimeException and a finally with return 2 silently returned 2). The compiler does not reject it (it only warns when -Xlint is on); never write it like this, but if you see it in the exam, the answer is the value in finally.

The exception hierarchy: checked and unchecked

Throwable
 ├─ Error                       (JVM problems: OutOfMemoryError, StackOverflowError; not caught)
 └─ Exception                   (CHECKED: the compiler checks)
     ├─ IOException
     │    └─ FileNotFoundException
     ├─ (your own extends Exception classes)
     └─ RuntimeException        (UNCHECKED: the compiler stays out)
          ├─ ArithmeticException
          ├─ NullPointerException
          ├─ ClassCastException
          ├─ IndexOutOfBoundsException
          │    ├─ ArrayIndexOutOfBoundsException
          │    └─ StringIndexOutOfBoundsException
          └─ IllegalArgumentException
               └─ NumberFormatException
CheckedUnchecked
Derives from?Exception (except RuntimeException)RuntimeException
What does the compiler want?Either catch or throwsNothing
When does it happen?Situations outside the program’s control: file missing, network downProgramming errors: null, division by zero, wrong index
ExampleIOException, FileNotFoundExceptionArithmeticException, NullPointerException

Common unchecked exceptions and the one-liners that produce them:

String s = null; s.length();          // NullPointerException
Object o = "hi"; Integer i = (Integer) o;   // ClassCastException
System.out.println(10 % 0);           // ArithmeticException: / by zero
System.out.println(10.0 / 0);         // NO exception: Infinity
NullPointerException
ClassCastException
ArithmeticException: / by zero
Infinity

(The output of the first three lines was obtained by running each in a try/catch and printing the name of the caught exception.)

throw and throws

  • throw throws an exception. An object follows it: throw new IllegalArgumentException("amount must be positive");
  • throws in a method’s signature declares which checked exceptions this method may let out: void withdraw(double a) throws InsufficientFundsException.

If you call a method that throws or may throw a checked exception, you have two options: catch it with try/catch, or add throws to your own signature and pass the responsibility up. If you do neither: unreported exception X; must be caught or declared to be thrown.

There is also a rule in the opposite direction: if nothing in the try block can throw a checked exception, you cannot catch it: exception IOException is never thrown in body of corresponding try statement.

Stack unwinding

If an exception happens in a method and is not caught there, the method ends at that moment. The exception behaves as if it were “thrown” to the place that called the method. The call stack is unwound this way until a catch that catches it is found. Every finally on the way runs.

static void level3() {
    System.out.println("level3 start");
    int[] a = new int[2];
    a[5] = 1;                                   // exception
    System.out.println("level3 end");
}
static void level2() {
    System.out.println("level2 start");
    try { level3(); }
    finally { System.out.println("level2 finally"); }
    System.out.println("level2 end");
}
static void level1() {
    System.out.println("level1 start");
    try { level2(); }
    catch (ArrayIndexOutOfBoundsException e) {
        System.out.println("level1 caught: " + e.getMessage());
    }
    System.out.println("level1 end");
}
level1();
level1 start
level2 start
level3 start
level2 finally
level1 caught: Index 5 out of bounds for length 2
level1 end
main → level1 → level2 → level3        (call stack)
level3: a[5] → exception, no catch → level3 ends (no "end")
level2: no catch, finally EXISTS → "level2 finally" → exception continues (no "end")
level1: the catch fits → "level1 caught..." → normal flow → "level1 end"

If the exception goes all the way up to main and is not caught there either, the program ends and the JVM prints the stack trace. When level3() is called directly from main:

level3 start
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 2
	at W09.level3(W09.java:31)
	at W09.main(W09.java:221)

A stack trace is read from the bottom up: main called level3; the exception happened at line 31 of level3.

Your own exception class

To report business-rule violations (insufficient balance, invalid grade) with a meaningful type, you write your own exception class. Derive from Exception if you want it to be checked, from RuntimeException if you want it unchecked. Pass the message with super(message).

class InsufficientFundsException extends Exception {
    private final double shortfall;
    public InsufficientFundsException(String message, double shortfall) {
        super(message);
        this.shortfall = shortfall;
    }
    public double getShortfall() { return shortfall; }
}

class Account {
    private double balance;
    Account(double b) { balance = b; }
    void withdraw(double amount) throws InsufficientFundsException {
        if (amount <= 0) throw new IllegalArgumentException("amount must be positive");
        if (amount > balance)
            throw new InsufficientFundsException("Not enough money", amount - balance);
        balance -= amount;
    }
    double getBalance() { return balance; }
}
Account acc = new Account(100);
try {
    acc.withdraw(30);
    acc.withdraw(100);
    System.out.println("not printed");
} catch (InsufficientFundsException e) {
    System.out.println(e.getMessage() + ", short by " + e.getShortfall());
}
System.out.println(acc.getBalance());
try {
    acc.withdraw(-5);
} catch (InsufficientFundsException e) {
    System.out.println("checked");
} catch (IllegalArgumentException e) {
    System.out.println("unchecked: " + e.getMessage());
}
Not enough money, short by 30.0
70.0
unchecked: amount must be positive

Methods of the exception object

MethodWhat does it give?Example
getMessage()Only the messagebad value
toString()Class name + messagejava.lang.IllegalArgumentException: bad value
printStackTrace()Prints the stack trace to stderrthe stack trace above
getCause()The exception that led to this onechaining

Catching an exception and wrapping it in a more meaningful one is called exception chaining: throw new IOException("load failed", e);. Then e.getCause().getMessage() gives the real reason (data.txt).

Common mistakes

Wrong ideaThe truth
After an exception, the rest of the try also runsIt is skipped, control goes to the catch
More than one catch can runOnly the first one that fits
catch (Exception e) at the topThe ones below become unreachable, compile error
If there is a return, finally does not runIt runs, before the return
Changing a variable in finally changes the returnThe return value was stored earlier
NumberFormatException is checkedIt is from the RuntimeException family, unchecked
throws throws the exceptionthrow throws, throws declares
A checked exception can be called without catchingcatch or throws is required
10.0 / 0 is an ArithmeticExceptionIn double it is Infinity; only int division throws
After a caught exception the program endsAfter the catch, normal flow continues

Test yourself

Exam-style questions. Solve them on paper first, then reveal the answer.

Question 1easy

What is printed?

try {
    int[] arr = {1, 2, 3};
    System.out.println(arr[1]);
    System.out.println(arr[3]);
    System.out.println("not printed");
} catch (ArrayIndexOutOfBoundsException e) {
    System.out.println("Caught: " + e.getMessage());
} finally {
    System.out.println("finally");
}
System.out.println("after");
Show answer
2 / Caught: Index 3 out of bounds for length 3 / finally / after

arr[1] = 2 is printed. arr[3] throws an exception: the REST of the try is SKIPPED and the matching catch runs. finally runs in every case. Because the exception was caught, the program continues normally and "after" is printed.

Question 2medium

What is printed by trace(0); trace(1); trace(2);?

static void trace(int k) {
    try {
        System.out.print("A");
        if (k == 1) throw new IllegalStateException();
        if (k == 2) throw new IllegalArgumentException();
        System.out.print("B");
    } catch (IllegalStateException e) {
        System.out.print("C");
    } catch (RuntimeException e) {
        System.out.print("D");
    } finally {
        System.out.print("E");
    }
    System.out.println("F");
}
Show answer
ABEF / ACEF / ADEF

k = 0: no exception, A B, the catches are skipped, finally E, then F. k = 1: A, IllegalStateException fits the first catch: C, E, F. k = 2: A, IllegalArgumentException does not fit the first catch, the second catch RuntimeException is its superclass: D, E, F. Catches are tried from top to bottom, and the first one that fits runs.

Question 3hard

What is printed by level1();?

static void level3() {
    System.out.println("level3 start");
    int[] a = new int[2];
    a[5] = 1;
    System.out.println("level3 end");
}
static void level2() {
    System.out.println("level2 start");
    try { level3(); }
    finally { System.out.println("level2 finally"); }
    System.out.println("level2 end");
}
static void level1() {
    System.out.println("level1 start");
    try { level2(); }
    catch (ArrayIndexOutOfBoundsException e) {
        System.out.println("level1 caught: " + e.getMessage());
    }
    System.out.println("level1 end");
}
Show answer
level1 start / level2 start / level3 start / level2 finally / level1 caught: Index 5 out of bounds for length 2 / level1 end

Stack unwinding: the exception happens in level3, there is no catch there, and the method ends immediately ("level3 end" is not printed). In level2 there is no catch but there is a finally: finally runs first, then the exception continues upward ("level2 end" is not printed). In level1 it is caught, the message is printed, and level1 continues normally.

Question 4hard

What do f() and h() return, and what is printed?

static int f() {
    try { return 1; }
    finally { System.out.println("finally in f"); }
}
static int h() {
    int x = 10;
    try { return x; }
    finally { x = 20; }
}
// in main:
System.out.println(f());
System.out.println(h());
Show answer
finally in f / 1 / 10

finally runs even before the return: first "finally in f", then the returned 1 is printed. In h, the return x expression computes and stores the value (10) at that moment; x = 20 in finally does not change the stored return value: 10. (If you wrote your own return inside finally, it would win, which is a bad habit.)

Question 5medium

Find the compile error.

static void open(String name) {
    throw new FileNotFoundException(name);
}
Show answer
The checked exception is neither caught nor declared with throws

FileNotFoundException is a subclass of IOException, so it is checked. javac: unreported exception FileNotFoundException; must be caught or declared to be thrown. Fix: add throws FileNotFoundException to the signature or wrap it in try/catch.

Question 6medium

Find the compile error.

try {
    int x = Integer.parseInt("12");
} catch (Exception e) {
    System.out.println("general");
} catch (NumberFormatException e) {
    System.out.println("number");
}
Show answer
A subclass catch after the superclass one: unreachable

Because Exception catches everything, the second catch never gets its turn. javac: exception NumberFormatException has already been caught. The rule: specific (subclass) catches first, general ones after.

Question 7hard

Find the compile error.

try {
    System.out.println("hi");
} catch (IOException e) {
    System.out.println("io");
}
Show answer
The try block cannot throw IOException, the catch is invalid

You cannot catch a checked exception if nothing in the try can throw it. javac: exception IOException is never thrown in body of corresponding try statement. This rule does not apply to unchecked exceptions and to Exception: catch (Exception e) would compile here.

Question 8easy

Which exceptions are checked? (1) ArithmeticException (2) IOException (3) NullPointerException (4) FileNotFoundException (5) NumberFormatException

Show answer
(2) and (4)

Those derived from RuntimeException are unchecked: ArithmeticException, NullPointerException, NumberFormatException (a subclass of IllegalArgumentException), ArrayIndexOutOfBoundsException, ClassCastException. IOException and its subclass FileNotFoundException come directly from Exception: checked, the compiler wants you to catch or declare them.

Question 9medium

What is printed?

Exception ex = new IllegalArgumentException("bad value");
System.out.println(ex.getMessage());
System.out.println(ex);
System.out.println(new RuntimeException().getMessage());
Show answer
bad value / java.lang.IllegalArgumentException: bad value / null

getMessage gives only the message. toString (which println calls) gives the full class name + ": " + the message. For an exception created without a message, getMessage returns null.

Question 10medium

What is printed?

static void parse(String s) {
    try {
        int n = Integer.parseInt(s);
        System.out.println(100 / n);
    } catch (NumberFormatException | ArithmeticException e) {
        System.out.println("bad input: " + e.getClass().getSimpleName());
    }
}
// in main:
parse("4"); parse("x"); parse("0");
Show answer
25 / bad input: NumberFormatException / bad input: ArithmeticException

Multi-catch catches more than one type in a single block. "4": 100 / 4 = 25. "x": parseInt throws NumberFormatException. "0": parse succeeds but 100 / 0 throws ArithmeticException. The types in a multi-catch cannot be subclasses of each other; ArithmeticException | RuntimeException does not compile.

Question 11medium

What is printed?

class InsufficientFundsException extends Exception {
    private final double shortfall;
    InsufficientFundsException(String msg, double shortfall) {
        super(msg); this.shortfall = shortfall;
    }
    double getShortfall() { return shortfall; }
}
// Account.withdraw throws it when amount > balance, otherwise subtracts
Account acc = new Account(100);
try {
    acc.withdraw(30);
    acc.withdraw(100);
    System.out.println("not printed");
} catch (InsufficientFundsException e) {
    System.out.println(e.getMessage() + ", short by " + e.getShortfall());
}
System.out.println(acc.getBalance());
Show answer
Not enough money, short by 30.0 / 70.0

The first withdrawal succeeds, the balance is 70. In the second withdrawal 100 > 70: the exception is thrown, the balance does not change, and "not printed" is skipped. The shortfall is 100 − 70 = 30.0. super(msg) passes the message to Exception, and getMessage returns it.

Question 12hard

Write a checked exception InvalidAgeException and a method int readAge(String text) that parses text (ignoring surrounding spaces) and throws InvalidAgeException with message not a number: <text> if it is not an integer, or out of range: <age> if it is outside 0 to 150.

Show answer
extends Exception + throws + catch the NumberFormatException and convert it to your own exception

For a checked exception, derive from Exception. Catch the unchecked exception of parseInt and convert it to a meaningful checked exception:

public class InvalidAgeException extends Exception {
    public InvalidAgeException(String msg) { super(msg); }
}

public static int readAge(String text) throws InvalidAgeException {
    int age;
    try {
        age = Integer.parseInt(text.trim());
    } catch (NumberFormatException e) {
        throw new InvalidAgeException("not a number: " + text);
    }
    if (age < 0 || age > 150)
        throw new InvalidAgeException("out of range: " + age);
    return age;
}
"21" → 21, " 40 " → 40, "abc" → not a number: abc, "200" → out of range: 200. The caller has to write try/catch; if it does not, unreported exception InvalidAgeException.

Verification: All code examples and answers were run with JDK 26 (java verify/SEN1002/W09.java; the uncaught-exception output is also from the end of the same file); the compile errors were tried with javac (verify/SEN1002/err/E09a-f.java).

Review card

try { risky } catch (Type e) { remedy } finally { ALWAYS } When an exception happens, the REST of the try is skipped. The first matching catch runs (top to bottom). Catch order: SUBCLASS first. If Exception is written first, "has already been caught". finally: runs whether or not there is an exception, even with a return (except System.exit). return x; then if x changes in finally, the returned value does NOT change. If not caught: the method ends immediately, finally runs, passes to the caller (stack unwinding). If not caught in main either: Exception in thread "main" ... + stack trace, the program ends. Throwable ← Error (do not catch) / Exception ← RuntimeException (unchecked) / IOException (checked). Unchecked: Arithmetic, NullPointer, ArrayIndexOutOfBounds, ClassCast, NumberFormat, IllegalArgument. Checked: IOException, FileNotFoundException, your own "extends Exception" class. Checked → either catch or throws in the signature. Otherwise "unreported exception ... must be caught". throw new X("msg"); (throws, singular) void m() throws X (declares, in the signature) Multi-catch: catch (A | B e) A and B cannot be related. getMessage() → message. toString() → ClassName: message. No message → null. Custom exception: class MyEx extends Exception { MyEx(String m) { super(m); } }