Memra

The Throwable hierarchy: checked vs unchecked

How Java classifies exceptions — and why the checked/unchecked distinction matters.

The exception hierarchy

Every problem Java can throw is a subclass of Throwable:

Throwable
├── Error            ← don't catch (JVM-level: OutOfMemoryError, StackOverflowError)
└── Exception
    ├── RuntimeException       ← unchecked
    │   ├── NullPointerException
    │   ├── IllegalArgumentException
    │   ├── ClassCastException
    │   └── ...
    └── IOException            ← checked
    └── SQLException           ← checked
    └── ... (checked)

Checked exceptions — everything that extends Exception but NOT RuntimeException. The compiler enforces the handle-or-declare rule: if a method can throw a checked exception, callers must either catch it or declare it with throws.

Unchecked exceptions — RuntimeException and its subclasses, plus Error. The compiler doesn't force you to handle them. They signal programming errors (NullPointerException, ArrayIndexOutOfBoundsException) or unrecoverable JVM failures (Error).

The basic try/catch/finally structure:

try {
    String text = Files.readString(Path.of("data.txt"));  // throws IOException
    System.out.println(text);
} catch (IOException e) {
    System.err.println("Read failed: " + e.getMessage());
} finally {
    System.out.println("this always runs");
}

finally runs regardless: whether the try completes normally, an exception is caught, or an exception is uncaught and propagates. The only exceptions are JVM abort (System.exit) and Error-level crashes.

ThrowableErroruncheckedExceptionRuntimeExceptionuncheckedIOExceptioncheckedSQLExceptionchecked
The compiler-enforced line is drawn at RuntimeException, not at Exception. Everything under Exception except the RuntimeException branch is checked, and handle-or-declare applies to exactly those.
~/memra/learn/java-from-zero/throwable-hierarchy