Java does not treat every exception as an unrelated problem. Instead, exceptions are organized into an inheritance hierarchy. This structure allows Java to classify different kinds of failures and gives developers a consistent way to catch, propagate, and handle them.
Understanding the hierarchy is important because the type of exception you catch determines which problems your code can handle. It also explains why a parent exception type can sometimes catch several different child exception types.
The Root of Java's Exception System
At the top of Java's throwable hierarchy is the Throwable class. Anything that can be thrown by Java's exception mechanism must ultimately be a subclass of Throwable.
Object
|
Throwable
|
+-- Error
|
+-- Exception
|
+-- RuntimeException
The two most important branches directly below Throwable are Error and Exception. The Exception branch contains conditions that application code commonly needs to handle, while Error generally represents serious problems that applications are not expected to recover from.
Important: Throwable is the common superclass of both Exception and Error. This relationship is the foundation of Java's exception-handling model.
Throwable
The Throwable class represents objects that can be thrown and caught by Java's exception mechanism. It provides useful functionality such as an exception message, cause information, and stack-trace details.
Throwable problem = new Exception("Something went wrong");
System.out.println(problem.getMessage());
Although code can technically work with Throwable, catching it directly is usually too broad for ordinary application logic because it includes serious Error conditions as well.
The Error Branch
Error is a subclass of Throwable. Errors generally indicate serious problems associated with the JVM, runtime environment, or system resources.
| Error | Typical Meaning |
|---|---|
| OutOfMemoryError | The JVM cannot allocate the required memory. |
| StackOverflowError | The thread's stack has been exhausted, often because of excessive recursion. |
| NoClassDefFoundError | A required class definition cannot be found at runtime. |
These conditions are generally not situations where an application should simply catch the error and continue as though nothing happened.
Remember: Do not assume that everything under Throwable should be caught. Error usually represents a serious runtime problem rather than a normal application-level exception.
The Exception Branch
The Exception class represents conditions that application code may reasonably want to handle. Many common Java exceptions belong to this branch.
Exception | +-- IOException | +-- SQLException | +-- RuntimeException
Some subclasses of Exception are checked exceptions, while RuntimeException and its subclasses are unchecked exceptions.
RuntimeException
RuntimeException is a major subclass of Exception. Its descendants commonly represent programming mistakes, invalid state, or invalid operations encountered while the program is running.
| Runtime Exception | Common Situation |
|---|---|
| NullPointerException | Using a null reference as though it referred to an object. |
| ArithmeticException | Performing invalid integer arithmetic such as division by zero. |
| ArrayIndexOutOfBoundsException | Accessing an array with an invalid index. |
| NumberFormatException | Converting an unsuitable string into a number. |
| ClassCastException | Performing an incompatible type cast. |
Checked and Unchecked Branches
One of the most important ideas in the hierarchy is the distinction between checked and unchecked exceptions. Checked exceptions generally extend Exception without passing through RuntimeException. Unchecked exceptions include RuntimeException and its subclasses.
Throwable
|
+-- Error
|
+-- Exception
|
+-- IOException // Checked
|
+-- SQLException // Checked
|
+-- RuntimeException // Unchecked
|
+-- NullPointerException
+-- ArithmeticException
+-- IllegalArgumentException
This distinction becomes especially important when you learn the throws keyword and compiler rules for checked exceptions.
Why Inheritance Matters in Exception Handling
Because exception classes use inheritance, a parent type can represent a broader category of problems. For example, RuntimeException is a parent of NullPointerException. Therefore, code that catches RuntimeException can also catch a NullPointerException.
try {
String name = null;
System.out.println(name.length());
} catch (RuntimeException e) {
System.out.println("A runtime problem occurred.");
}
The catch block matches because NullPointerException is a subclass of RuntimeException.
Specific vs General Exception Types
Although a parent type can catch child exceptions, catching the most specific meaningful exception is usually better. Specific handling tells future developers exactly what the code expects and makes recovery logic easier to maintain.
try {
int value = Integer.parseInt("hello");
} catch (NumberFormatException e) {
System.out.println("The input is not a valid number.");
}
This is clearer than catching Exception because the code communicates the precise failure it knows how to handle.
Catch Parent After Child
The hierarchy also affects the order of multiple catch blocks. A more specific exception must be caught before its broader parent. Otherwise, the parent handler would already match the child exception, making the later catch block unreachable.
try {
String value = null;
System.out.println(value.length());
} catch (NullPointerException e) {
System.out.println("Reference was null.");
} catch (RuntimeException e) {
System.out.println("Some runtime exception occurred.");
}
The first catch handles the specific NullPointerException. The second provides a broader fallback for other runtime exceptions.
Common Mistake: Never place a parent exception before its child when using separate catch blocks. A broad catch such as Exception can make more specific catches below it unreachable.
A Practical Hierarchy Example
public class HierarchyDemo {
public static void main(String[] args) {
try {
int[] numbers = {10, 20, 30};
System.out.println(numbers[5]);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Invalid array index.");
} catch (RuntimeException e) {
System.out.println("Runtime exception.");
} catch (Exception e) {
System.out.println("General exception.");
}
}
}
The actual exception is ArrayIndexOutOfBoundsException. Java checks the catch blocks in order and selects the first compatible handler. Because the specific exception appears first, it handles the failure directly.
How Java Finds a Matching Catch Block
When an exception is thrown, Java examines the available catch blocks from top to bottom. A handler is considered compatible when its parameter type is the same as the exception type or is one of its parent types.
| Thrown Exception | Catch Type | Match? |
|---|---|---|
| NullPointerException | NullPointerException | Yes |
| NullPointerException | RuntimeException | Yes |
| NullPointerException | Exception | Yes |
| NullPointerException | IOException | No |
Why You Should Know the Hierarchy
Knowing exception names individually is useful, but understanding their relationships is much more powerful. Once you know that a specific exception inherits from a broader category, you can predict how catch blocks, method declarations, and exception propagation will behave.
- It helps you choose the correct catch type.
- It explains why parent exceptions can catch child exceptions.
- It prevents unreachable catch blocks.
- It makes checked and unchecked exceptions easier to understand.
- It helps you design custom exceptions with appropriate parent classes.
Best Practices
- Prefer the most specific exception type that accurately describes the failure you can handle.
- Use broader exception types only when a broader recovery strategy genuinely makes sense.
- Keep specific catch blocks before general catch blocks.
- Avoid catching Throwable in ordinary application code.
- Understand whether an exception belongs to the checked or unchecked branch before deciding how to handle it.
Interview Insight
A common interview question is: "What is the difference between Exception and Error?" A strong answer is that both extend Throwable, but Exception generally represents conditions application code may handle, while Error generally represents serious runtime or JVM-level problems.
Another favorite question is: "Why must the child exception come before the parent exception in catch blocks?" The answer is simple: the parent type can already match the child, so placing the parent first makes the child handler unreachable.
Quick Revision
| Type | Position | Key Idea |
|---|---|---|
| Throwable | Top-level root | Base type for objects that can be thrown. |
| Error | Throwable branch | Generally represents serious runtime or JVM problems. |
| Exception | Throwable branch | Represents conditions application code may handle. |
| RuntimeException | Exception branch | Base class for common unchecked exceptions. |
| Specific exceptions | Lower levels | Represent particular failure conditions such as null access or invalid indexes. |
The Java exception hierarchy is more than a collection of class names; it is the structure that makes exception handling predictable. Once you understand the relationship between Throwable, Error, Exception, and RuntimeException, choosing catch types and understanding exception propagation becomes far more intuitive. The next step is to see how Java uses the try and catch mechanism to actually respond to these exceptions.
