Q25 of 40 · Core Java
Explain Java's exception hierarchy starting from Throwable.
Short answer
Short answer: Throwable is the root. Error (and subclasses like OutOfMemoryError) represents unrecoverable JVM failures — never catch. Exception is for recoverable conditions: RuntimeException (and subclasses) are unchecked, requiring no declaration; everything else under Exception is checked and the compiler forces callers to handle or propagate.
Detail
Throwable is the root of every exception and error in Java. Only Throwable instances can be thrown with throw or caught with catch. It carries a message, a cause (for exception chaining), and a stack trace.
Error — represents serious problems in the JVM or environment: OutOfMemoryError, StackOverflowError, ThreadDeath, AssertionError, LinkageError. These indicate conditions from which the application typically cannot recover. You almost never catch Error — the JVM or process monitor should handle it. The exception is AssertionError, which some testing frameworks catch to report failures.
Exception — splits into two branches:
RuntimeException(unchecked):NullPointerException,IllegalArgumentException,IllegalStateException,IndexOutOfBoundsException,ClassCastException,ArithmeticException,UnsupportedOperationException. These represent programming bugs. The compiler doesn't require you to declare or catch them.- Everything else under
Exception(checked):IOException,SQLException,InterruptedException,ClassNotFoundException,CloneNotSupportedException. These represent recoverable conditions the programmer should anticipate.
Exception chaining: when you catch an exception and throw a different one, preserve the original as the cause: throw new RuntimeException("failed to load config", e). This keeps the full chain visible in the stack trace and is critical for debugging in test infrastructure.
Multi-catch (Java 7+): catch (IOException | SQLException e) lets you handle multiple unrelated exception types in one block, reducing boilerplate when the handling is identical.
// EXAMPLE
ExceptionHierarchy.java
// Abbreviated hierarchy
// Throwable
// ├── Error (unchecked — don't catch)
// │ ├── OutOfMemoryError
// │ ├── StackOverflowError
// │ └── AssertionError
// └── Exception
// ├── IOException (checked)
// ├── SQLException (checked)
// ├── InterruptedException (checked)
// └── RuntimeException (unchecked)
// ├── NullPointerException
// ├── IllegalArgumentException
// ├── IllegalStateException
// └── IndexOutOfBoundsException
// Exception chaining — preserve the original cause
try {
String json = Files.readString(Path.of("config.json"));
return objectMapper.readValue(json, Config.class);
} catch (IOException e) {
// ❌ Don't swallow: throw new RuntimeException("failed"); loses context
// ✅ Chain the cause
throw new RuntimeException("Could not load test configuration", e);
}
// Multi-catch — same handling for different exception types
try {
dbSetup.run();
} catch (IOException | SQLException e) {
log.error("Test data setup failed: {}", e.getMessage(), e);
throw new RuntimeException("Setup failed", e);
}
// Catching Error — almost never appropriate
try {
loadHugeDataset();
} catch (OutOfMemoryError e) {
// Only valid in frameworks that need to report the failure cleanly
// In application code: don't do this
reportFatalError(e);
}