10.1 The Java Exception Hierarchy: Checked vs. Unchecked Exceptions and Errors

Key Takeaways

  • java.lang.Throwable is the root superclass of all errors and exceptions in the Java platform, bifurcating into java.lang.Error and java.lang.Exception.

  • Errors (such as OutOfMemoryError, StackOverflowError, and AssertionError) represent fatal system conditions or JVM failures that standard application code must never attempt to catch or recover from.

  • Checked exceptions are subclasses of Exception (excluding RuntimeException); the compiler enforces the 'catch-or-declare' rule, requiring them to be either caught in a try-catch block or declared in a method's throws clause.

  • Unchecked exceptions are subclasses of RuntimeException (or Error) that represent programming bugs or invalid states (such as NullPointerException and IllegalArgumentException); they are exempt from compiler catch-or-declare enforcement.

  • The compiler enforces strict reachability rules on checked exceptions: attempting to catch a checked exception that cannot be thrown within the enclosed try block results in a compile-time error, whereas catching unchecked exceptions, Exception, or Throwable is always permitted.

Last updated: October 2026

10.1 The Java Exception Hierarchy: Checked vs. Unchecked Exceptions and Errors

In Java, exceptional conditions that disrupt the normal flow of program execution are represented as first-class objects. Robust exception handling requires a deep understanding of the object model rooted at java.lang.Throwable, the fundamental architectural boundary separating recoverable conditions from catastrophic system failures, and the compiler's strict reachability rules.


The Throwable Class Hierarchy

Every exception and error in Java is an instance of java.lang.Throwable or one of its direct or indirect subclasses. The hierarchy splits into two primary branches:

                         java.lang.Throwable
                                  |
                +-----------------+-----------------+
                |                                   |
         java.lang.Error                   java.lang.Exception
                |                                   |
     +----------+----------+             +----------+----------+
     |                     |             |                     |
OutOfMemoryError   StackOverflowError    |              RuntimeException
                                         |                     |
                                  (Checked Exceptions)  +------+------+
                                  e.g. IOException      |             |
                                  e.g. SQLException  NullPointer-  Arithmetic-
                                                     Exception     Exception
  • java.lang.Throwable: The ultimate root of the exception hierarchy. Only instances of this class (or its subclasses) can be thrown by the Java Virtual Machine (JVM) or by the throw statement, and only these types can appear in a catch clause or throws declaration.
  • java.lang.Error: Represents serious problems that a reasonable application should not try to catch. Most such errors are abnormal conditions that occur within the virtual machine itself or the underlying hardware.
  • java.lang.Exception: Represents conditions that a reasonable application might want to catch and recover from.
  • java.lang.RuntimeException: A special branch of Exception. Subclasses of RuntimeException (along with Error) are collectively termed unchecked exceptions. All other subclasses of Exception are termed checked exceptions.

The Error Category: Unrecoverable JVM Failures

Instances of java.lang.Error designate fatal infrastructure issues or system resource exhaustion. When an Error occurs, the integrity of the JVM or the operating environment is typically compromised.

Key examples to recognize:

  • OutOfMemoryError: Thrown when the Java Virtual Machine cannot allocate an object because it is out of memory, and no more memory could be made available by the garbage collector.
  • StackOverflowError: Thrown when a thread stack overflows, most frequently due to infinite recursion or excessively deep call stacks.
  • NoClassDefFoundError: Thrown when the JVM or a ClassLoader instance tries to load in the definition of a class (as part of a normal method call or creation of a new instance) and no definition of the class could be found at runtime, even though it compiled cleanly.
  • ExceptionInInitializerError: Signals that an unexpected exception occurred in a static initializer block or during the evaluation of a static field initializer.
  • AssertionError: Thrown to indicate that an assert statement has failed.
  • VirtualMachineError: The abstract superclass of OutOfMemoryError and StackOverflowError.

Warning

Catching Error Is an Anti-Pattern While writing catch (Error e) or catch (Throwable t) is syntactically legal, application code should never attempt to handle or suppress Error subclasses (such as OutOfMemoryError or StackOverflowError). Attempting to continue execution after a JVM failure leaves threads in undefined, corrupted states. Let errors propagate to terminate the failing thread or process.


Checked vs. Unchecked Exceptions

The distinction between checked and unchecked exceptions is enforced entirely by the Java compiler.

The Catch-or-Declare Rule

Java enforces the catch-or-declare contract for all checked exceptions:

  1. Catch: Enclose the code that may throw the exception in a try block with an associated catch block that handles the exception type (or a supertype).
  2. Declare: Append a throws clause to the enclosing method or constructor signature declaring that the method may propagate the exception to its caller.

If a method contains an invocation of code that throws a checked exception without either catching it or declaring it in its throws clause, the Java compiler refuses to compile the code.

import java.io.*;

public class CatchOrDeclareDemo {
    // Approach 1: Declare via throws clause
    public void readFile(String path) throws IOException {
        FileReader reader = new FileReader(path); // FileReader throws FileNotFoundException (checked)
        reader.read();                            // read() throws IOException (checked)
        reader.close();
    }

    // Approach 2: Catch within try-catch block
    public void safeReadFile(String path) {
        try {
            FileReader reader = new FileReader(path);
            reader.read();
            reader.close();
        } catch (IOException e) {
            System.err.println("Failed to read file: " + e.getMessage());
        }
    }
}

Unchecked Exceptions (RuntimeException)

Unchecked exceptions are subclasses of java.lang.RuntimeException. They represent flaws in program logic, illegal arguments, or violated object states that can theoretically occur anywhere in an application.

Because runtime errors can happen in virtually any statement (such as dereferencing a reference or dividing numbers), forcing developers to declare them everywhere would clutter code with boilerplate. Therefore, unchecked exceptions are exempt from the catch-or-declare rule.

DimensionChecked ExceptionsUnchecked Exceptions (RuntimeException)System Errors (Error)
SuperclassSubclasses of Exception outside the RuntimeException branchSubclasses of java.lang.RuntimeExceptionSubclasses of java.lang.Error
Catch-or-Declare RuleMandatory: Compiler forces try-catch or throwsOptional: Compiler does not require catch or declareOptional: Must not be caught by normal logic
Primary Root CauseExternal conditions (missing files, network dropped, SQL errors)Programming bugs, invalid input, state contract violationsJVM resource exhaustion, linkage/classloading failures
Recovery FeasibilityGenerally recoverable with alternative workflowsRecoverable through bug fixes and defensive preconditionsUnrecoverable; system/JVM state is damaged

Common Built-in Exceptions for 1Z0-829

Exam questions frequently require identifying whether a specific exception is checked or unchecked, and what runtime scenario causes it to be thrown.

Unchecked Runtime Exceptions (java.lang)

ExceptionCommon Trigger Scenario
NullPointerExceptionAttempting to invoke an instance method, access an instance field, or calculate the length of an array through a null reference, or unboxing a null wrapper object.
ArrayIndexOutOfBoundsExceptionAccessing an array with an illegal index that is either negative or greater than or equal to array.length.
StringIndexOutOfBoundsExceptionAccessing a character of a String with an index that is out of bounds (subclass of IndexOutOfBoundsException).
ClassCastExceptionAttempting to cast an object to a class of which it is not an instance (e.g., (String) (Object) Integer.valueOf(5)).
IllegalArgumentExceptionA method received an argument that is inappropriate, invalid, or violates method preconditions.
IllegalStateExceptionA method has been invoked at an illegal or inappropriate time (the object environment or lifecycle state is not suitable for the requested operation, e.g. calling iterator.remove() before next()).
NumberFormatExceptionAttempting to convert a string to a numeric type when the string does not have an appropriate format (subclass of IllegalArgumentException).
ArithmeticExceptionExceptional arithmetic condition, specifically integer division or modulo by zero (10 / 0).
UnsupportedOperationExceptionInvoking a mutating method on an unmodifiable collection (e.g., List.of().add("x")).

Warning

The Floating-Point Division by Zero Trap In Java, integer division by zero (10 / 0) throws java.lang.ArithmeticException. However, floating-point division by zero (10.0 / 0.0 or 10.0f / 0) does not throw an exception! In compliance with IEEE 754 floating-point standards, it evaluates to Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, or Double.NaN (for 0.0 / 0.0). Watch for floating-point literals in arithmetic questions!

Checked Exceptions

ExceptionPackageCommon Trigger Scenario
IOExceptionjava.ioFailed or interrupted I/O operations (broken network connection, inaccessible pipe).
FileNotFoundExceptionjava.ioAttempting to open a file denoted by a specified pathname when the file does not exist (subclass of IOException).
SQLExceptionjava.sqlDatabase access errors, invalid SQL syntax, or lost database connections.
ClassNotFoundExceptionjava.langAn application tries to load a class through its string name (e.g., Class.forName("com.Foo")) but no definition can be located.
InterruptedExceptionjava.langA thread is waiting, sleeping, or occupied, and another thread interrupts it using Thread.interrupt().
ParseExceptionjava.textAn unexpected error has been reached while parsing a date or number string.

Compiler Reachability and Catch Block Validity

One of the trickiest areas on the 1Z0-829 exam involves compiler reachability checks on catch blocks. The Java compiler verifies whether a try block could ever possibly throw the checked exception listed in a catch clause:

  1. Unreachable Checked Exception Catch Error: If a catch block catches a checked exception EE, and the statements within the corresponding try block cannot possibly throw EE (or any subclass of EE), the compiler rejects the code with an error: exception E is never thrown in body of corresponding try statement.
  2. The Unchecked and Broad Catch Exemption: This reachability check does not apply to unchecked exceptions (RuntimeException or any of its subclasses) or to broad catch types (Exception and Throwable). You can catch NullPointerException, RuntimeException, Exception, or Throwable around any arbitrary code, even an empty try block!
import java.io.IOException;

public class ReachabilityRules {
    public void testReachability() {
        try {
            System.out.println("Processing...");
        } catch (RuntimeException e) {
            // COMPILES: Unchecked exceptions are always permitted in catch blocks
        } catch (Exception e) {
            // COMPILES: Exception is broad and could represent runtime exceptions
        }

        try {
            System.out.println("Testing checked...");
            // catch (IOException e) {} // COMPILE ERROR: IOException is never thrown in try block!
        } catch (Exception e) {
            // Valid
        }
    }
}

Java Assertions: Invariant Verification (Background)

Note

Assertions are not named in the current 1Z0-829 objectives. They appear briefly here because AssertionError belongs to the Error hierarchy and older study material still uses them; spend most of your time on the exception rules above.

Assertions, introduced via the assert keyword, enable developers to test internal assumptions and invariants during development and testing without incurring performance overhead in production.

Assertion Syntax

Java provides two forms of the assert statement:

  1. Simple assertion:
    assert booleanExpression;
    
  2. Assertion with detail expression:
    assert booleanExpression : detailExpression;
    

If assertions are enabled and booleanExpression evaluates to false, the JVM throws an AssertionError. In the two-argument form, detailExpression is evaluated and converted to a String to serve as the detail message of the AssertionError.

Controlling Assertions via the Command Line

By default, assertions are disabled at runtime. They must be explicitly activated using CLI flags:

  • -ea or -enableassertions: Enables assertions for all system-loaded application classes.
  • -da or -disableassertions: Disables assertions.
  • Granular control:
    • -ea:com.order...: Enables assertions for package com.order and all its subpackages (the trailing ... denotes subpackages).
    • -da:com.order.Pricing: Disables assertions for the specific class Pricing within com.order.
    • More specific rules take precedence over broader rules.
# Enable in all packages except com.example.cache
java -ea -da:com.example.cache... com.example.MainApp

Warning

Assertion Rules and Anti-Patterns

  1. Do not use assertions to validate arguments in public methods: Public methods must throw standard exceptions like IllegalArgumentException or NullPointerException because assertions can be disabled in production, leaving public API contracts unprotected. Use assertions for private method preconditions, postconditions, and internal invariant validation.
  2. Never write assertions with side effects: Expressions such as assert list.remove(item); or assert count++ > 0; alter program state when -ea is active, but fail to execute when assertions are disabled, causing divergent behavior between test and production environments!
Test Your Knowledge

Consider the following code snippet:

public class ExceptionReachability {
    public static void main(String[] args) {
        try {
            System.out.println("Parsing number...");
            int val = Integer.parseInt("123");
        } catch (java.io.IOException e) {
            System.out.println("IO Error");
        } catch (NumberFormatException e) {
            System.out.println("Format Error");
        }
    }
}

What is the result of attempting to compile and run this code?

A

Compilation fails because NumberFormatException is an unchecked exception and cannot be caught.

B

Compiles successfully and prints: Parsing number...

C

Compilation fails because java.io.IOException is never thrown inside the try block.

D

Compiles successfully, but throws an UncheckedIOException at runtime.

Test Your Knowledge

Which of the following classes is a checked exception that requires either handling via a try-catch block or declaration in a method's throws clause?

A

java.lang.ClassNotFoundException

B

java.lang.AssertionError

C

java.lang.IllegalStateException

D

java.lang.IllegalArgumentException

Test Your Knowledge

What does the following code print?

try {
    int n = Integer.parseInt("12a");
    System.out.print("A ");
} catch (IllegalArgumentException e) {
    System.out.print("B ");
} catch (RuntimeException e) {
    System.out.print("C ");
} finally {
    System.out.print("D");
}
A

C D

B

A D

C

B D

D

Compilation fails because NumberFormatException is not caught by name

Test Your Knowledge

What is the result of executing the following two statements in Java?

double d = 10.0 / 0.0;
int i = 10 / 0;
A

Both statements cause a compile-time error because division by zero is statically forbidden.

B

Both statements throw an ArithmeticException at runtime.

C

The first statement throws an ArithmeticException; the second assigns 0 to i.

D

The first statement assigns Infinity to d without throwing an exception; the second statement throws an ArithmeticException at runtime.

Sections you finish are checked off in the contents.