Throwable Hierarchy
Explore the Java Throwable hierarchy to understand how Errors, checked Exceptions, and unchecked Exceptions differ. This lesson helps you recognize which problems are fatal, which require handling, and how to design robust error recovery in your applications.
Not all problems in Java are created equal. Some issues are catastrophic system failures that we can’t stop, while others are simple logic mistakes we should have avoided. Still, others are predictable external hiccups, like a missing file or a network timeout, that we must prepare for.
Java organizes all these scenarios into a strict family tree. Understanding this hierarchy allows us to write code that knows exactly which problems to anticipate, which to recover from, and which to fix.
The Throwable root
In Java, runtime failures that interrupt normal flow are represented as Throwable objects. These objects all share a single common ancestor: the java.lang.Throwable class.
Only objects that extend Throwable can be used with Java’s exception handling mechanisms (the throw keyword to trigger an error, or the catch keyword to handle it). While Throwable provides essential methods like getMessage() and printStackTrace(), we rarely use this class directly. Instead, we work with its two main subclasses: Error and Exception.
Visualizing the Throwable hierarchy
To understand how Java treats different problems, it helps to visualize the family tree. This structure determines everything from whether your code compiles to whether your application crashes.
Here is the hierarchy at a glance:
java.lang.Throwable├── java.lang.Error (System Failures - DO NOT CATCH)│ ├── OutOfMemoryError│ └── StackOverflowError│└── java.lang.Exception (Application Issues - HANDLE OR DECLARE)├── java.io.IOException (Checked Exception)├── java.sql.SQLException (Checked Exception)│└── java.lang.RuntimeException (Unchecked Exception - FIX THE BUG)├── NullPointerException└── IndexOutOfBoundsException
Throwable: The root. Everything starts here.Error: The “fatal” branch. These are system-level issues.Exception: The “recoverable” branch. This is where our application logic lives.Checked exceptions: These are subclasses of
Exception. The compiler forces us to handle these.Unchecked exceptions: These are subclasses of
RuntimeException. The compiler trusts us to prevent these.
The Error class
The Error class represents serious problems that a well-designed application should not try to catch. These are usually fatal conditions outside our control, such as running out of memory (OutOfMemoryError) or a stack overflow (StackOverflowError).
When an Error occurs, the Java Virtual Machine (JVM) is usually in a state that prevents recovery. We generally do not write code to handle these; we let the program terminate.
The Exception class
The Exception class represents conditions that an application might want to catch. This branch is where we spend most of our time as developers. It allows us to manage program failures gracefully.
The Exception branch splits further into two distinct categories based on how the compiler treats them: unchecked exceptions (RuntimeException) and checked exceptions.
Unchecked exceptions (RuntimeException)
Unchecked exceptions are exceptions that the compiler does not force us to handle. These are subclasses of java.lang.RuntimeException.
These exceptions typically represent programming bugs or logical errors that we should fix in our code rather than handle at runtime. Common examples include accessing an array index that doesn’t exist (ArrayIndexOutOfBoundsException) or trying to use a null reference (NullPointerException).
Because these are usually our fault as programmers, the compiler assumes we will fix the logic to prevent them, rather than forcing us to write code to catch them.
In this example, we trigger a common unchecked exception. The code compiles perfectly, but it crashes when run.
Line 3: We initialize a
Stringvariable tonull.Line 7: We attempt to call the
.length()method onnull. Sincenullreferences no object, the JVM throws aNullPointerException.Line 9: Because the exception was not caught, the current thread terminates (and if this is the main thread and no other non-daemon threads remain, the program exits), and this line never executes.
Checked exceptions
Checked exceptions are all subclasses of Exception that do not extend RuntimeException. They represent anticipated external failure problems that can happen even if our code is perfect.
For example, if we try to read a file, the file might not exist. If we try to connect to a website, the internet might be down. These are not logic bugs; they are environmental realities.
Java enforces a “handle or declare” rule for these exceptions. The compiler checks our code at compile-time. If we call a method that throws a checked exception, we must do one of two things:
Handle it: Write code to catch the exception and prevent the crash. (We will cover the syntax for this, called a
try-catchblock, in the next lesson).Declare it: Add
throwsto our method signature, passing the responsibility up the chain.
If we ignore a checked exception, the code simply will not compile.
Line 6: We create a
Fileobject. This is safe and does not throw an exception.Line 10: We attempt to create a
FileReader. TheFileReaderconstructor is defined to throwFileNotFoundException(a checked exception). Because we have neither handled it nor declared it, the Java compiler throws an error.
The handle or declare rule in action
The distinction between checked and unchecked exceptions dictates how we design our methods.
Feature | Checked exceptions | Unchecked exceptions |
Superclass |
|
|
Compiler behavior | Enforces handling or declaring. | Ignores them. |
Semantic meaning | This is an expected external failure. Recover from it. | This is a bug in the code. Fix the logic. |
Examples |
|
|
To fix the compilation error in the previous example without handling it immediately, we can use the declare approach by adding throws to the method signature. This tells the compiler: I know this might fail, and I am letting the caller deal with it.
Line 7: We add
throws FileNotFoundExceptionto themainmethod signature. This satisfies the “handle or declare” rule.Line 13: The
FileReadercreation now compiles successfully.
Since we are running this in main without error-handling logic, if the exception occurs, the JVM will simply print the error and stop.
Choosing the right type
When we write our own methods or custom exceptions, we must decide whether to make them checked or unchecked.
We generally follow this guideline:
Use checked exceptions if the caller can reasonably be expected to recover. For example, if a “User Not Found” error occurs, the caller might want to prompt the user to register.
Use unchecked exceptions if the condition represents a bug or a state from which recovery is impossible. For example, if we pass a negative number to a method that requires a positive length, the caller cannot recover; they simply wrote bad code.
Modern Java development has trended toward using unchecked exceptions more frequently to reduce boilerplate code (verbose error-handling blocks), but checked exceptions remain a core part of the language for ensuring robust API contracts.
We have explored the family tree of Java problems. We know that Error is for system crashes we ignore, RuntimeException is for bugs we fix, and checked Exception is for external failures we must handle. The compiler serves as our safety net, forcing us to acknowledge the checked exceptions we might otherwise overlook.