Memra

Checked vs unchecked & the exception hierarchy

Throwable, Error, Exception, RuntimeException — and what the compiler forces you to handle.

The Throwable family tree

Everything you can throw or catch is a subclass of Throwable. Two branches descend from it:

- Error — serious problems the JVM raises that an application normally should *not* catch (OutOfMemoryError, StackOverflowError). You don't handle these; you let the program die. - Exception — the conditions application code is expected to anticipate. Under it sits a special subclass, RuntimeException, which gets its own rule.

So the hierarchy is:

Throwable
├── Error                 (don't catch: JVM-level, unchecked)
└── Exception             (checked — must declare or handle)
    └── RuntimeException  (unchecked — programming bugs)

Checked vs unchecked — the one distinction that matters

The compiler divides exceptions into two groups:

- Checked exceptionsException and its subclasses *except* RuntimeException. The compiler forces you to either catch them or declare them with a throws clause. Examples: IOException, ClassNotFoundException, and the project's own ControllerException. The reasoning: these are *recoverable external conditions* (a file is missing, a window is stuck) that a caller could reasonably plan for, so the language makes the obligation visible. - Unchecked exceptionsRuntimeException and its subclasses (and Error). The compiler does not force handling. Examples: NullPointerException, ArrayIndexOutOfBoundsException, IllegalArgumentException, ArithmeticException. The reasoning: these almost always signal a *bug* you should fix, not a condition you should catch.

The quick test: is it a RuntimeException (or Error)? Then it's unchecked. Otherwise it's checked.

The throws clause

A method that can throw a *checked* exception and does not catch it must announce that in its signature with throws:

void loadEvents(String file) throws IOException {
    // ... code that may throw IOException, not caught here ...
}

Now loadEvents's callers face the same obligation: catch the IOException, or add throws IOException to *their* signature and pass the responsibility upward. A method may list several: throws IOException, ControllerException. You never need to declare unchecked exceptions — void f() can throw NullPointerException with no throws clause.

Worked example — a custom checked exception

The Greenhouse project defines its fault type as a checked exception by extending Exception directly:

public class ControllerException extends Exception {
    public ControllerException() { super(); }
    public ControllerException(String why) { super(why); }
}

Because it extends Exception (not RuntimeException), any method that throws it must declare it:

void action() throws ControllerException {
    if (powerOut) {
        throw new ControllerException("PowerOut");
    }
}

If you had instead written class ControllerException extends RuntimeException, the throws clause would become optional and the compiler would stop reminding callers to handle the fault — exactly the wrong choice for a recoverable hardware condition the controller is built to respond to. Choosing extends Exception is a deliberate design decision, and a classic exam question.

NORMAL ~/memra/learn/comp-308/checked-unchecked-hierarchy utf-8 LF