Throwable Hierarchy: Error vs Exception in Java
Understand Java's Throwable hierarchy, the distinction between Error and Exception, and when each category should be handled differently.
Understand Java's Throwable hierarchy, the distinction between Error and Exception, and when each category should be handled differently.
Throwable Hierarchy: Error vs Exception in Java
Java’s exception handling framework is built on a single root class: Throwable. Everything that can be thrown in Java inherits from this type, whether it is a recoverable exception or a fatal virtual machine error.
Introduction
The Throwable hierarchy is the foundation of everything Java does with exceptions — from the NullPointerException every developer encounters to the StackOverflowError that terminates a thread and the IOException that signals a file read failure. Understanding the hierarchy means understanding the distinction between what you should catch and handle versus what you should never catch and should let terminate the process.
The hierarchy splits at Throwable into Error and Exception. Error represents fatal JVM conditions — things so wrong that the program cannot safely continue. OutOfMemoryError, StackOverflowError, and InternalError indicate systemic failures in the runtime environment, not failures in your logic. Catching these is almost always wrong because the program is already in an invalid state. Exception represents recoverable failures — the file was not found, the network connection was refused, the user typed invalid input. These are the failures that callers can reasonably be expected to handle.
The Exception branch further splits into checked exceptions (subclasses of Exception that are not RuntimeException) and unchecked exceptions (RuntimeException and its subclasses). Checked exceptions are enforced by the compiler — the caller must acknowledge them via catch or throws declaration. Unchecked exceptions are not compiler-enforced. The rule of thumb: external failures that the caller can reasonably handle (I/O, network, invalid input) are checked; programming bugs that indicate invalid program state (null dereference, index out of bounds, illegal argument) are unchecked.
This guide covers the full hierarchy, the checked/unchecked distinction and when each applies, the common mistakes of catching Error or generic Throwable, and the implications for exception handling design in application code.
When to Use
You will encounter the Throwable hierarchy when:
- Catching built-in Java exceptions (
NullPointerException,IllegalArgumentException) - Distinguishing recoverable failures from unrecoverable errors
- Understanding framework error handling behavior
- Designing application-specific exception types
try {
Integer.parseInt("not-a-number");
} catch (NumberFormatException e) {
// Handle recoverable input validation error
System.err.println("Invalid number format: " + e.getMessage());
}
When NOT to Use
Avoid these common mistakes:
- Do not catch
Error— JVM errors likeStackOverflowErrorindicate fatal conditions that cannot be meaningfully handled - Do not throw
Errorsubclasses — UseRuntimeExceptionor custom exceptions for business logic failures - Do not catch generic
Throwablein application code — This masks all problems including AssertionErrors and JVM bugs - Do not confuse checked vs unchecked — Catching
Exception(the checked supertype) does not catch runtime exceptions
Throwable Hierarchy Diagram
classDiagram
class Throwable {
+String message
+Throwable cause
+void printStackTrace()
+String getMessage()
+Throwable getCause()
}
class Error {
<<Error>>
+void printStackTrace()
}
class Exception {
<<Exception>>
+void printStackTrace()
}
class RuntimeException {
<<RuntimeException>>
+void printStackTrace()
}
class IOException {
<<checked>>
+void printStackTrace()
}
class SQLException {
<<checked>>
+void printStackTrace()
}
class NullPointerException {
<<RuntimeException>>
+void printStackTrace()
}
class IllegalArgumentException {
<<RuntimeException>>
+void printStackTrace()
}
class OutOfMemoryError {
<<Error>>
+void printStackTrace()
}
class StackOverflowError {
<<Error>>
+void printStackTrace()
}
Throwable <|-- Error
Throwable <|-- Exception
Exception <|-- RuntimeException
Exception <|-- IOException
Exception <|-- SQLException
RuntimeException <|-- NullPointerException
RuntimeException <|-- IllegalArgumentException
Error <|-- OutOfMemoryError
Error <|-- StackOverflowError
Hierarchy Explained
| Type | Category | Checked? | Typical Use |
|---|---|---|---|
Throwable | Base class | — | Root of all throwable types |
Error | JVM errors | No | Fatal conditions (OutOfMemory, StackOverflow) |
Exception | Recoverable failures | Yes/No | Depends on subclass |
RuntimeException | Programming errors | No | Bugs (NPE, illegal arguments) |
IOException | I/O failures | Yes | External resource failures |
SQLException | Database failures | Yes | JDBC operation failures |
Checked vs Unchecked
Checked exceptions (subclasses of Exception except RuntimeException) must be either caught or declared in the method signature with throws. The compiler enforces this, making them suitable for recoverable external failures.
Unchecked exceptions include RuntimeException and Error subclasses. The compiler does not require handling, and these generally represent programming bugs or fatal JVM states.
The checked/unchecked distinction was a deliberate design choice borrowed from C++ exception handling, with Java making it more rigorous. The idea was that the compiler should force callers to acknowledge failure modes outside the program’s control — missing files, broken connections, invalid user input. If a method could fail in ways the caller might recover from, the caller should have to say so explicitly. In practice, this philosophy has mixed track record. APIs with deep call chains end up propagating checked exceptions through every layer with verbose throws declarations, and many developers started wrapping checked exceptions in unchecked ones just to avoid the ceremony. If you find yourself adding throws IOException to a dozen methods just to pass it up the stack, that is usually a sign the layering has broken down.
Common checked exceptions you will encounter include IOException for file and stream operations, SQLException for JDBC database calls, ClassNotFoundException when dynamically loading classes, and ParseException when formatting dates or numbers. These are all environmental failures, things that go wrong due to factors outside your code’s control. On the unchecked side, NullPointerException, IllegalArgumentException, IndexOutOfBoundsException, and NumberFormatException show up constantly in real codebases. Dividing by zero gives you ArithmeticException — you cannot do much about it because it means the calling code has a bug, not that something external went wrong.
// Checked — compiler requires handling
public void readFile(String path) throws IOException {
Files.readString(Path.of(path));
}
// Unchecked — no compiler enforcement
public void divide(int a, int b) {
if (b == 0) throw new ArithmeticException("Division by zero");
}
Failure Scenarios
// Scenario 1: Catching Error (WRONG)
try {
recursiveMethod();
} catch (Error e) {
// BAD: Errors are fatal, cannot recover
System.out.println("Caught error: " + e);
}
// Scenario 2: Catching generic Throwable (WRONG)
try {
riskyOperation();
} catch (Throwable t) {
// BAD: Catches AssertionError, OutOfMemoryError, JVM bugs
// Masks real problems
}
// Scenario 3: Catching checked vs unchecked (CORRECT)
try {
Integer.parseInt("abc");
} catch (NumberFormatException e) {
// GOOD: Specific unchecked exception for input validation
System.out.println("Invalid input: " + e.getMessage());
}
Trade-off Table
| Approach | Pros | Cons |
|---|---|---|
Catch Exception | Catches all exceptions | Also catches RuntimeException |
| Catch specific types | Precise handling | May miss edge cases |
Catch Throwable | Catches everything | Catches Errors, masks bugs |
| Propagate unchecked | Caller handles only what matters | Undocumented failure modes |
| Propagate checked | Compiler enforces handling | Verbose signatures |
Security Notes
- Do not expose stack traces in production —
printStackTrace()writes to standard error, which may be logged to files accessible to attackers - Sanitize exception messages before logging — Do not include passwords, session tokens, or PII in error messages
- Avoid exception tunneling — Converting checked exceptions to unchecked without documenting the failure mode obscures error handling
- Failure to catch
Error— In server applications, uncaught errors can cause thread death without proper cleanup
// SECURE: Log without exposing stack trace details
try {
processUserData();
} catch (Exception e) {
logger.error("User data processing failed: {}", e.getClass().getName());
// Do NOT log: e.getMessage() may contain sensitive data
}
Common Pitfalls
- Swallowing exceptions silently — Empty catch blocks hide failures
- Catching
Exceptiontoo broadly — Masks programming bugs - Re-throwing without context — Original exception lost in stack trace
- Confusing Error with Exception — Error should not be caught
- Over-relying on checked exceptions — Creates verbose signatures and tight coupling
Quick Recap
Throwableis the root of all throwable typesErrorrepresents JVM fatal conditions — do not catchExceptionrepresents recoverable failuresRuntimeExceptionis unchecked — indicates programming bugs- Checked exceptions require handling or declaration; unchecked do not
- Never catch
Erroror genericThrowablein application code
Interview Questions
Further Reading
- Try-Catch-Finally — basic exception handling syntax
- Throw and Throws — throwing and declaring exceptions
- Custom Exceptions — creating application-specific exception types
- Try With Resources — automatic resource cleanup with AutoCloseable
- Exception Best Practices — when and how to use exceptions effectively
Conclusion
The Throwable hierarchy is the foundation of Java’s exception handling system. At the root sits Throwable, branching into Error for fatal JVM conditions that should never be caught, and Exception for recoverable failures. The checked/unchecked distinction within Exception determines whether the compiler enforces handling — checked exceptions like IOException and SQLException represent external failures, while RuntimeException and its subclasses indicate programming bugs.
Understanding which type to catch and when is critical. Never catch Error or generic Throwable in application code — this masks fatal conditions and makes debugging impossible. When designing exception handling, prefer catching specific types over broad categories. If you are handling exceptions in practice, review the Try-Catch-Finally patterns to ensure cleanup code runs correctly, and consider Custom Exceptions when you need domain-specific error signaling beyond what built-in types provide.
Category
Related Posts
Abstract Classes in Java
Learn about partially implemented classes that define contracts for subclasses using abstract methods and concrete implementations.
Arithmetic Operators in Java
Master Java arithmetic operators: addition, subtraction, multiplication, division, and modulo with integer division gotchas and operator precedence explained.
Array Basics in Java
Learn Java array fundamentals: declaration, initialization, element access, and the length property explained simply.