Skip to content
Part IA Michaelmas Term

Java's Exception Hierarchy

Java's Throwable hierarchy

The Root: Throwable

java.lang.Throwable is the common superclass of everything that can be thrown with a throw statement. It provides:

  • getMessage() — the detail message string
  • getCause() — the “causing” throwable (for exception chaining)
  • getStackTrace() — an array of StackTraceElement representing the call stack at the point of throw
  • printStackTrace() — prints the stack trace to System.err
  • getSuppressed() — suppressed exceptions from try-with-resources

Branch 1: Error

Error and its subclasses represent serious, generally unrecoverable JVM-level problems:

Error SubclassMeaning
OutOfMemoryErrorThe JVM cannot allocate more heap memory
StackOverflowErrorA thread’s stack has overflowed (usually unbounded recursion)
InternalErrorAn unexpected internal error in the JVM itself
NoClassDefFoundErrorA class that was present at compile time is not available at runtime
AssertionErrorAn assert statement evaluated to false

Errors are unchecked — the compiler does not require you to declare or catch them. Application code should generally not catch Error — if the JVM is out of memory, there is little your code can do about it, and attempting complex recovery in that state may make things worse.

Branch 2: Exception

Exception is the class for conditions that application code might reasonably want to handle. It splits into two categories.

Checked Exceptions

Subclasses of Exception that are not subclasses of RuntimeException. The compiler forces every caller to either catch them or declare them in a throws clause.

Checked exceptions are used for recoverable failure conditions — things that can go wrong even in a correctly written program:

Checked ExceptionMeaning
IOExceptionGeneral I/O failure
FileNotFoundExceptionA file does not exist (subclass of IOException)
SQLExceptionDatabase error
ParseExceptionString parsing failure
ClassNotFoundExceptionA class name was not found by Class.forName()
InterruptedExceptionA thread’s sleep() or wait() was interrupted

The design philosophy: “this could go wrong, and you need a plan.” The compiler ensures you have one.

Unchecked Exceptions

RuntimeException and its subclasses. No compiler enforcement — the caller is not required to catch or declare them.

Unchecked exceptions are used for programming errors — bugs that should be fixed in the code, not handled at runtime:

Unchecked ExceptionMeaning
NullPointerExceptionDereferencing a null reference
IllegalArgumentExceptionA method received an illegal argument
IndexOutOfBoundsExceptionArray or list index is out of range
ConcurrentModificationExceptionCollection was structurally modified during iteration
ArithmeticExceptionInteger division by zero
ClassCastExceptionInvalid cast at runtime
NumberFormatExceptionInvalid string-to-number conversion

The design philosophy: “you wrote a bug.” The compiler assumes these should not happen in correctly written code, so it does not burden every caller with handling them. If they do happen, they propagate upward, usually terminating the affected thread and producing a stack trace that helps the developer find the bug.

The Philosophy: Checked versus Unchecked

CategoryWhen to UseCompiler Enforcement
Checked ExceptionRecoverable, expected failure (file not found, network timeout)Caller must catch or declare
Unchecked ExceptionProgramming error (null pointer, invalid argument)No enforcement
ErrorJVM failure (out of memory, stack overflow)No enforcement — generally should not be caught

The distinction is a matter of fault versus failure: a fault is a defect in the code (unchecked exception), while a failure is an external condition that correct code must cope with (checked exception).

Creating Custom Exceptions

public class InsufficientFundsException extends Exception {
    private final double balance;
    private final double requested;

    public InsufficientFundsException(double balance, double requested) {
        super("Insufficient funds: balance=" + balance + ", requested=" + requested);
        this.balance = balance;
        this.requested = requested;
    }

    public double getBalance() { return balance; }
    public double getRequested() { return requested; }
}

Extend Exception for a checked custom exception, or RuntimeException for an unchecked one. Always provide a meaningful message and, where useful, additional fields that capture the context of the error. Provide constructors that accept a message and optionally a cause (for exception chaining: super(message, cause)).

Exception Chaining

When wrapping a low-level exception in a domain-specific one, pass the original as the cause:

try {
    dbConnection.execute(sql);
} catch (SQLException e) {
    throw new DataAccessException("Failed to execute query", e);
}

The caller can inspect getCause() to see the original SQLException, maintaining the full diagnostic chain while presenting a clean API.