💥 Exceptions & Errors · Intermediate

The Throwable hierarchy in Java

Throwable → Error / Exception → RuntimeException.

🧩 The mysteryYou wrap risky code in catch (Exception e) to catch EVERYTHING. Then the program crashes anyway. What slipped through the net?

A family tree of trouble

Everything you can throw extends **Throwable. It splits into two branches: Error and Exception. Under Exception sits RuntimeException**, the root of the unchecked exceptions.

Throwable
├── Error
│   └── StackOverflowError
└── Exception
    ├── IOException
    └── RuntimeException
        └── NullPointerException

Two very different branches

Error: serious trouble in the JVM or environment, like OutOfMemoryError — usually not something to handle. Exception: conditions a program may handle. Errors and RuntimeExceptions are unchecked; other Exceptions, like IOException, are checked. Families nest deep: NumberFormatException → IllegalArgumentException → RuntimeException.

🔮 Predict it

Where does it sit?

What does this print?

Throwable t = new ClassCastException();
System.out.println(t instanceof RuntimeException);
System.out.println(t instanceof Error);
  1. true false
  2. false false
  3. true true
Show the answer

ClassCastException extends RuntimeException, which extends Exception. It's on the Exception branch, so it is never an Error.

⚠️ The trap

catch (Exception e) is not a net for everything

Errors live on the other branch. catch (Exception e) does NOT catch a StackOverflowError — it escapes, and the line after the try never runs.

try {
    throw new StackOverflowError();
} catch (Exception e) {      // no match!
    System.out.println("caught");
}
🤔 Think first

Who gets checked?

Which of these does the compiler force you to handle: IllegalStateException, AssertionError, IOException, ArithmeticException?

Think about it, then reveal the answer

Only **IOException** — it's a checked Exception. Two of the others extend RuntimeException, and AssertionError is an Error: all unchecked.

💼 In the real world

Why the tree matters

In production, monitoring tools group crashes by exception type, and catch blocks decide what to handle by family. Knowing the tree tells you that a top-level catch (Exception e) in a web server will log bad requests — but won't catch an OutOfMemoryError killing the process.

Key takeaways

  1. Throwable → Error and Exception
  2. Exception → RuntimeException (unchecked) and others like IOException (checked)
  3. Errors and RuntimeExceptions are unchecked
  4. catch (Exception e) does NOT catch Errors
🤯 Did you know?

throw new Throwable() is legal — and Throwable itself counts as checked, so the method must declare throws Throwable. Only the Error and RuntimeException subtrees are unchecked.

Practice questions

What does this print?

Throwable t = new NumberFormatException();
System.out.println(t instanceof IllegalArgumentException);
System.out.println(t instanceof RuntimeException);
System.out.println(t instanceof Error);
  1. true true false
  2. false true false
  3. true false false
  4. false false false
Check your answer

true true false. NumberFormatException extends IllegalArgumentException, which extends RuntimeException. It's on the Exception branch, not Error.

What does this print?

try {
    throw new StackOverflowError();
} catch (Exception e) {
    System.out.println("caught");
}
System.out.println("after");
  1. caught after
  2. after
  3. Throws StackOverflowError
  4. caught
Check your answer

Throws StackOverflowError. StackOverflowError is an Error, not an Exception, so catch (Exception e) doesn't match and the error escapes main.

Next: why does the compiler nag you about IOException but stay silent about NullPointerException?