Search⌘ K
AI Features

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
A text-based view of the Java exception hierarchy
  • 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.

Java 25
public class UncheckedException {
public static void main(String[] args) {
String text = null;
// This line causes a NullPointerException at runtime.
// The compiler does not warn us about this potential crash.
System.out.println(text.length());
System.out.println("This line will never print.");
}
}
  • Line 3: We initialize a String variable to null.

  • Line 7: We attempt to call the .length() method on null. Since null references no object, the JVM throws a NullPointerException.

  • 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:

  1. Handle it: Write code to catch the exception and prevent the crash. (We will cover the syntax for this, called a try-catch block, in the next lesson).

  2. Declare it: Add throws to our method signature, passing the responsibility up the chain.

If we ignore a checked exception, the code simply will not compile.

Java 25
import java.io.File;
import java.io.FileReader;
public class CheckedException {
public static void main(String[] args) {
File file = new File("nonexistent_file.txt");
// COMPILATION ERROR:
// Unhandled exception: java.io.FileNotFoundException
FileReader reader = new FileReader(file);
}
}
  • Line 6: We create a File object. This is safe and does not throw an exception.

  • Line 10: We attempt to create a FileReader. The FileReader constructor is defined to throw FileNotFoundException (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

Exception (but not RuntimeException)

RuntimeException

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

IOException, SQLException

NullPointerException, IndexOutOfBoundsException

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.

Java 25
import java.io.File;
import java.io.FileReader;
import java.io.FileNotFoundException;
public class DeclareExample {
// We declare that this method might throw a FileNotFoundException
public static void main(String[] args) throws FileNotFoundException {
File file = new File("nonexistent_file.txt");
// Now this compiles, because we declared the exception.
// If the file is missing, the program will still crash,
// but we satisfied the compiler's safety check.
FileReader reader = new FileReader(file);
System.out.println("File opened successfully.");
}
}
  • Line 7: We add throws FileNotFoundException to the main method signature. This satisfies the “handle or declare” rule.

  • Line 13: The FileReader creation 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.