Exceptions

75. The Exception Hierarchy

Throwable is the root; Error represents unrecoverable JVM failures (don't catch); Exception is the recoverable root; RuntimeException is the unchecked subclass. Know where to catch and what to rethrow.

Java 21 (core language)

Explanation

`` Throwable Error <- JVM errors: OutOfMemoryError, StackOverflowError, AssertionError Exception <- root of checked exceptions RuntimeException <- unchecked: NullPointerException, IllegalArgumentException, etc. IOException <- checked: FileNotFoundException, SocketException, etc. SQLException <- checked: database errors ... ``

Never catch Error: OutOfMemoryError, StackOverflowError, InternalError signal the JVM is in an unrecoverable state. Catching them and continuing leads to undefined behaviour.

Catch as specific as possible: catch FileNotFoundException before IOException before Exception. A catch block catches the declared type and all subtypes. Ordering matters โ€” the first matching catch wins.

Multi-catch (catch (A | B e)): catch two unrelated exceptions in one block. The caught type is the common supertype; the variable is implicitly final.

finally always runs: even if return is called inside try or catch. try-with-resources replaced most explicit finally cleanup patterns.

Example

import java.io.*;
import java.sql.SQLException;

public class ExceptionHierarchy {
    public static void main(String[] args) {
        // Specific before general
        try {
            throw new FileNotFoundException("/missing.txt");
        } catch (FileNotFoundException e) {
            System.out.println("FileNotFound: " + e.getMessage());
        } catch (IOException e) {
            System.out.println("IO: " + e.getMessage()); // never reached here
        } catch (Exception e) {
            System.out.println("Exception: " + e.getMessage());
        }

        // Multi-catch: unrelated exception types in one block
        try {
            if (Math.random() < 0.5) throw new IOException("io");
            else throw new SQLException("sql");
        } catch (IOException | SQLException e) {
            // e is implicitly final; type is common supertype (Exception)
            System.out.println("IO or SQL: " + e.getMessage());
        }

        // finally always runs
        try {
            System.out.println("try block");
            if (true) return; // still runs finally!
        } finally {
            System.out.println("finally runs even with return");
        }
    }

    static void demonstrateErrors() {
        // NEVER catch Error in production code
        try {
            int[] huge = new int[Integer.MAX_VALUE]; // OutOfMemoryError
        } catch (OutOfMemoryError e) {
            // Bad practice: JVM may be in broken state
            System.out.println("OOM caught โ€” heap may be exhausted");
        }
    }
}

Sources