5.1 Exception Hierarchy: Checked vs. Unchecked Exceptions
Key Takeaways
- java.lang.Throwable serves as the common root of the Java exception hierarchy, branching into java.lang.Error for catastrophic JVM failures and java.lang.Exception for conditions applications can handle.
- Checked exceptions extend Exception (excluding RuntimeException) and are enforced by the compiler via the handle-or-declare rule, requiring enclosing try-catch blocks or method throws declarations.
- Unchecked exceptions comprise RuntimeException and its subclasses along with Error, representing programmatic logic flaws and fatal JVM states that do not require mandatory compile-time handling.
- Overriding methods in subclasses can declare the same, narrower (subclass), fewer, or no checked exceptions compared to the overridden method, and can declare any unchecked exceptions without restriction.
5.1 Exception Hierarchy: Checked vs. Unchecked Exceptions
In Java, robust error handling is built around a strongly typed object hierarchy rooted at java.lang.Throwable. The Java compiler and Java Virtual Machine (JVM) strictly enforce how exceptions are declared, thrown, and handled. Understanding the structural boundaries between checked exceptions, unchecked runtime exceptions, and fatal JVM errors is one of the most heavily tested domains on the Oracle Certified Professional: Java SE 21 Developer (1Z0-830) exam.
The Throwable Class Hierarchy
Every object that can be thrown via the throw statement or caught within a catch block must be an instance of java.lang.Throwable or one of its subclasses. Attempting to throw or catch an object that does not extend Throwable (such as throw new String("Error");) causes an immediate compilation failure.
java.lang.Object
│
java.lang.Throwable
│
┌────────────────┴────────────────┐
│ │
java.lang.Error java.lang.Exception
│ │
┌───────────┴───────────┐ ┌───────────┴───────────┐
│ OutOfMemoryError │ │ RuntimeException │ (Unchecked)
│ StackOverflowError │ │ ├─ NullPointerExc │
│ NoClassDefFoundError │ │ ├─ ClassCastExc │
│ ExceptionInInitError │ │ ├─ IllegalArgumentExc│
└───────────────────────┘ │ └─ IndexOutOfBounds │
(Unchecked) ├───────────────────────┤
│ Checked Exceptions │
│ ├─ IOException │
│ ├─ SQLException │
│ └─ ClassNotFoundExc │
└───────────────────────┘
The Throwable hierarchy branches immediately into two primary subtrees:
java.lang.Error(Unchecked): Indicates serious system-level problems and hardware or JVM resource exhaustion that a normal application should not attempt to catch or handle.java.lang.Exception: Indicates conditions that a reasonable application might anticipate and recover from. This subtree is divided into:- Unchecked Exceptions (
RuntimeExceptionand its subclasses): Conditions representing programming bugs, logic flaws, and improper API usage. - Checked Exceptions (all subclasses of
Exceptionthat do not extendRuntimeException): Conditions external to the application logic that a well-written program must anticipate (e.g., missing files, broken network sockets, database outages).
- Unchecked Exceptions (
Unrecoverable JVM Errors (java.lang.Error)
Errors extend java.lang.Error and represent abnormal conditions from which an application cannot realistically recover. Because they are unchecked, methods do not need to declare them in their throws clause, nor are developers expected to catch them.
| Error Type | Triggering Condition | Exam Significance |
|---|---|---|
java.lang.OutOfMemoryError | The JVM heap is exhausted and the Garbage Collector cannot reclaim enough memory to allocate a new object. | Often simulated on exams with unbounded array allocations (e.g., int[] arr = new int[Integer.MAX_VALUE];). |
java.lang.StackOverflowError | A thread's execution stack is exhausted, typically due to infinite or excessively deep recursive method invocations without a terminating base condition. | Synchronous failure occurring on method call boundaries. |
java.lang.NoClassDefFoundError | The Java compiler successfully compiled a class against a dependency, but the JVM ClassLoader cannot locate the .class file at runtime during dynamic resolution. | Contrasts with ClassNotFoundException (which is a checked exception thrown during explicit reflection like Class.forName()). |
java.lang.ExceptionInInitializerError | An unchecked runtime exception occurs while evaluating a static variable initializer or inside a static { ... } initialization block. | The JVM wraps the originating unchecked exception inside ExceptionInInitializerError. |
java.lang.InternalError / VirtualMachineError | Fatal internal JVM malfunction or resource limitation. | Superclass of OutOfMemoryError and StackOverflowError. |
The ExceptionInInitializerError Trap
A classic 1Z0-830 exam question presents a class where static field initialization or a static block throws an unchecked exception:
public class StaticInitFailure {
static int[] values = new int[0];
static int badAccess = values[5]; // Throws ArrayIndexOutOfBoundsException
public static void main(String[] args) {
System.out.println("Application started");
}
}
When the JVM attempts to load and initialize StaticInitFailure, the ArrayIndexOutOfBoundsException occurs during class initialization. The JVM intercepts it, wraps it inside java.lang.ExceptionInInitializerError, and aborts execution. Execution never enters main().
[!NOTE] A static initialization block cannot throw checked exceptions directly unless they are caught inside the block itself. Uncaught checked exceptions in static initializers cause a compilation error.
Checked vs. Unchecked Exceptions
The fundamental distinction between checked and unchecked exceptions lies in compiler enforcement.
+-----------------------+-----------------------------------------------------------+
| Exception Category | Compiler Enforcement & Handling Requirements |
+-----------------------+-----------------------------------------------------------+
| Checked Exceptions | Subject to the 'Handle-or-Declare' rule. The compiler |
| (extends Exception | verifies that every invocation capable of throwing a |
| excluding Runtime) | checked exception is enclosed in a try-catch or declared |
| | in the enclosing method's throws clause. |
+-----------------------+-----------------------------------------------------------+
| Unchecked Exceptions | NOT enforced by the compiler. Methods may throw, catch, |
| (extends | or ignore them without any throws declaration or try-catch|
| RuntimeException) | block. Represents programming bugs and logic defects. |
+-----------------------+-----------------------------------------------------------+
Common Checked Exceptions in java.base
java.io.IOException(and subclassesFileNotFoundException,EOFException)java.sql.SQLExceptionjava.lang.ClassNotFoundException(thrown byClass.forName(),ClassLoader.loadClass())java.lang.InterruptedException(thrown when a thread sleeping or waiting is interrupted)java.text.ParseException(thrown byDateFormat.parse())
Common Unchecked Exceptions (RuntimeException Subclasses)
java.lang.NullPointerException: Attempting to dereference anullobject pointer.java.lang.ArrayIndexOutOfBoundsException/IndexOutOfBoundsException: Accessing an index outside[0, size - 1].java.lang.IllegalArgumentException: Passing an illegal or inappropriate argument to a method.java.lang.IllegalStateException: Invoking an operation when the object is in an invalid state.java.lang.ClassCastException: Attempting to cast an object reference to an incompatible type.java.lang.ArithmeticException: Integer division or modulo by zero (10 / 0). (Note: Floating-point division by zero producesInfinityorNaN, not anArithmeticException!).java.lang.NumberFormatException: Parsing an invalid numeric string (e.g.,Integer.parseInt("abc")).
The Handle-or-Declare Rule
When a method executes an expression or invokes a method that can throw a checked exception, the enclosing code must satisfy at least one of two options:
- Handle: Enclose the throwing call inside a
try-catchblock that catches the checked exception (or a superclass likeExceptionorThrowable). - Declare: Append a
throwsclause to the enclosing method signature listing the checked exception (or a superclass).
import java.io.FileReader;
import java.io.IOException;
public class HandleOrDeclareDemo {
// Option 1: Declare via throws clause
public void readFileDeclared(String path) throws IOException {
FileReader reader = new FileReader(path); // Constructor throws FileNotFoundException
reader.read();
reader.close();
}
// Option 2: Handle via try-catch
public void readFileHandled(String path) {
try {
FileReader reader = new FileReader(path);
reader.read();
reader.close();
} catch (IOException e) {
System.err.println("I/O Error: " + e.getMessage());
}
}
}
[!NOTE] A method is permitted to declare checked exceptions in its
throwsclause even if its body never throws those exceptions. This is common when declaring abstract or interface methods intended to be overridden.
The Unreachable Catch Block Rule (Checked Exceptions)
Java enforces a strict reachability rule specifically for checked exceptions in catch blocks.
[!WARNING] If a
catchblock specifies a checked exception $E$, but the correspondingtryblock cannot possibly throw $E$ (nor any subclass of $E$), the compiler produces a compilation error:exception E is never thrown in body of corresponding try statement.
Contrast this with unchecked exceptions and general base types:
- Catching
RuntimeException,NullPointerException,IllegalArgumentException,Exception, orThrowableis always legal, even if thetryblock body is completely empty!
import java.io.IOException;
public class CatchReachabilityDemo {
public void test() {
// COMPILES: Exception and RuntimeException are always allowed
try {
int x = 10 / 2;
} catch (RuntimeException e) {
System.out.println("Caught runtime");
} catch (Exception e) {
System.out.println("Caught general exception");
}
// DOES NOT COMPILE:
try {
int y = 20;
} catch (IOException e) { // ERROR: IOException is never thrown in try statement
System.out.println("I/O failure");
}
}
}
Method Overriding and Checked Exceptions
When a subclass overrides a method declared in a superclass or interface, the Java compiler enforces strict covariance rules on the throws clause to preserve polymorphism:
- Same or Narrower Checked Exceptions: The overriding method may declare the exact same checked exceptions, narrower (subclass) checked exceptions, or a subset of them.
- No Broader or New Checked Exceptions: The overriding method cannot declare broader (superclass) checked exceptions or entirely new checked exceptions not declared by the superclass method.
- Fewer or No Checked Exceptions: The overriding method is free to declare fewer checked exceptions or omit the
throwsclause entirely. - Unchecked Exceptions Are Free: The overriding method can declare any unchecked exceptions (
RuntimeException,Error, or their subclasses), regardless of whether the superclass method declared them.
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.SQLException;
class SuperClass {
public void performAction() throws IOException {
System.out.println("Super performing");
}
}
class SubClassA extends SuperClass {
@Override
public void performAction() throws FileNotFoundException { // LEGAL: Narrower checked exception
System.out.println("Sub A");
}
}
class SubClassB extends SuperClass {
@Override
public void performAction() { // LEGAL: Declares no checked exceptions
System.out.println("Sub B");
}
}
class SubClassC extends SuperClass {
@Override
public void performAction() throws IOException, IllegalArgumentException { // LEGAL: Any unchecked exception
System.out.println("Sub C");
}
}
/*
class SubClassInvalid extends SuperClass {
@Override
public void performAction() throws Exception { // COMPILE ERROR: Broader checked exception!
}
}
class SubClassInvalid2 extends SuperClass {
@Override
public void performAction() throws IOException, SQLException { // COMPILE ERROR: New checked exception SQLException!
}
}
*/
Consider a superclass method declaration: public void process() throws java.io.IOException. Which of the following method signatures in a subclass represents a legal override?
Given the following code snippet, what is the result of attempting to compile and execute the program?
public class InitializerDemo {
static String[] data = new String[0];
static String first = data[1];
public static void main(String[] args) {
System.out.println("Value: " + first);
}
}
Examine the following four independent methods. Which method fails compilation due to Java's exception reachability rules?
import java.io.IOException;
class ReachabilityRules {
void methodA() { try { int x = 10 / 2; } catch (ArithmeticException e) {} }
void methodB() { try { int x = 10 / 2; } catch (Exception e) {} }
void methodC() { try { int x = 10 / 2; } catch (IOException e) {} }
void methodD() { try { int x = 10 / 2; } catch (Error e) {} }
}
Which of the following classes is classified as a CHECKED exception in the Java Standard Library?