The Exception Hierarchy
The Exception Hierarchy
Every error signal in Java — whether a program bug, a missing file, or an out-of-memory crash — is represented as an object. All of those objects belong to a single inheritance tree rooted at the class Throwable. Understanding this tree tells you which problems you are expected to handle, which ones indicate bugs in your code, and which ones are so serious that recovery is nearly impossible.
The full picture
Here is the hierarchy you need to memorise:
Every class you see in a stack trace is somewhere in this tree. Let us go through each level.
Throwable — the root of everything
Throwable is the superclass of all throwable objects. Only instances of this class (or its subclasses) can be used with throw, catch, or throws. It defines the core methods you use every day:
getMessage()— returns the human-readable error message.getCause()— returns the exception that caused this one (used for chained exceptions).printStackTrace()— prints the full call stack to stderr.getStackTrace()— returns the call stack as an array you can inspect programmatically.
Exception (for recoverable situations) or RuntimeException (for programming mistakes). Extending Throwable itself bypasses the checked/unchecked distinction and surprises other developers.
Error — do not catch these
Error represents a serious problem that a well-written application should not try to catch. These are typically JVM-level failures that your code has no realistic way to recover from.
- OutOfMemoryError — the JVM ran out of heap space. There is nothing useful you can do once this fires.
- StackOverflowError — infinite or too-deep recursion exhausted the call stack.
- AssertionError — an
assertstatement failed during testing.
catch (Exception e) thinking it catches everything — it does not catch Error. Even catch (Throwable t) will catch Errors, but that is almost always wrong. Log the situation and let the JVM (or your container) shut down cleanly.
Exception — the recoverable branch
Exception is the class for conditions that a reasonable program might want to catch and handle. Most of the classes you write try/catch blocks for live here. Direct subclasses of Exception (other than RuntimeException) are called checked exceptions — the compiler forces you to either catch them or declare them with throws.
If you omit throws IOException and do not wrap the call in a try/catch, the code will not compile. That is the compiler protecting you: it knows this operation might fail and forces you to think about it.
RuntimeException — programming mistakes
RuntimeException is a special subclass of Exception. Its subclasses are called unchecked exceptions — the compiler does not require you to handle them. They typically indicate bugs in your code: you passed null where an object was expected, you accessed an array index that does not exist, or you tried to cast an object to the wrong type.
NullPointerException or ArrayIndexOutOfBoundsException you should fix the code that caused it — add a null check, validate the index — rather than swallowing it in a catch block and hiding the bug.
Checking the hierarchy at runtime with instanceof
Because the hierarchy uses ordinary inheritance, you can use instanceof to inspect where a thrown object sits in the tree:
This also explains why a single catch (Exception e) block catches both checked exceptions like IOException and unchecked ones like NullPointerException — they are both subclasses of Exception. But it will not catch an OutOfMemoryError, because Error is a sibling branch, not a subclass of Exception.
Why the hierarchy matters in practice
- Catching a superclass catches all subclasses.
catch (Exception e)will catchIOException,SQLException,NullPointerException, and thousands more. Be as specific as possible so different problems get different handling. - The compiler enforces checked exceptions. Any class that is a direct subclass of
Exception(but not ofRuntimeException) must be declared or caught. The hierarchy is what makes this rule possible. - Custom exceptions slot into the right place. When you create your own exception class (covered in a later lesson), you choose which class to extend — and that choice determines whether your exception is checked or unchecked.
Summary
Throwable is the root. Error signals JVM failures you should not catch. Exception signals recoverable problems — its direct subclasses are checked (compiler-enforced), while subclasses of RuntimeException are unchecked (indicating bugs). Every class in a stack trace lives in this tree, and that position determines how Java and the compiler treat it.