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.
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.
// 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
Model Answer: "Exception and Error both extend Throwable. Exception represents recoverable failures that can be caught and handled — like IOException or SQLException. Error represents fatal JVM conditions — like OutOfMemoryError or StackOverflowError — that applications should not attempt to catch. Errors indicate the program itself is in an invalid state."
Model Answer: "Checked exceptions are subclasses of Exception but not RuntimeException. The compiler requires them to be caught or declared with throws. They represent recoverable external failures like file not found. Unchecked exceptions include RuntimeException and Error subclasses. The compiler does not require handling. RuntimeException typically indicates programming bugs like null pointers or invalid arguments."
Model Answer: "Catching Throwable catches both Exception and Error. Errors are fatal JVM conditions that cannot be meaningfully handled, and catching them masks real bugs. Additionally, catching AssertionError (which extends Error) would interfere with test frameworks. Always catch the most specific type possible."
Model Answer: "The hierarchy is: Throwable → Exception (checked) and Throwable → Error. RuntimeException extends Exception. Common unchecked exceptions like NullPointerException and IllegalArgumentException extend RuntimeException. Common errors include OutOfMemoryError and StackOverflowError which extend Error."
Model Answer: "Generally no. OutOfMemoryError indicates the JVM is exhausted and the program cannot safely continue. Even if caught, attempting to allocate new objects will likely fail again. The appropriate response is to let the error propagate, log it for diagnostics, and terminate gracefully if possible. Some long-running servers use custom allocators to handle partial failures, but this is not typical application code."
Model Answer: "Yes, by catching Throwable, which is the common supertype of both Error and Exception. However, this is almost always wrong in application code because it masks fatal JVM errors like OutOfMemoryError and StackOverflowError. Only use this pattern if you are writing low-level infrastructure code that needs to handle all possible failure modes."
Model Answer: "The compiler tracks checked exceptions — subclasses of Exception that are not RuntimeException subclasses. When a method throws a checked exception, either the caller must catch it in a try-catch block, or the method must declare it in its throws clause. Without this handling, code fails to compile. This enforcement ensures that recoverable failures like I/O errors are not accidentally ignored."
Model Answer: "getMessage() returns the string passed to the exception constructor, useful for debugging. getLocalizedMessage() is intended for locale-specific messages by default it delegates to getMessage() but subclasses can override it to provide localized error descriptions for end users."
Model Answer: "RuntimeException is an unchecked exception — the compiler does not enforce handling or declaration requirement for any subclasses of RuntimeException or Error. This is because RuntimeException typically represents programming bugs (NPE, illegal arguments, index out of bounds) that indicate the program is in an invalid state and should not be recovered from at runtime. Forcing declaration would pollute method signatures with implementation details."
Model Answer: "If a catch block catches an exception but performs no action — either no code or just a comment — the exception is silently swallowed. The program continues as if no exception occurred, which corrupts state and makes bugs hard to diagnose. A catch block should either recover from the failure, log the exception and rethrow, or wrap and rethrow with added context."
Model Answer: "Yes. If a static initializer throws an exception — checked or unchecked — the class is marked as uninitialized. Any attempt to use that class results in an ExceptionInInitializerError (which wraps the original exception). This prevents the class from being used in an invalid state. The wrapped exception can be retrieved via getCause() on the ExceptionInInitializerError."
Model Answer: "When overriding a method, the overriding method cannot declare new checked exceptions beyond those declared in the parent. It can throw a narrower exception type (a subtype of the parent's exception) or throw none. This ensures that code using the parent type reference still handles all possible exceptions. If a parent declares throws IOException, the child can throw FileNotFoundException but not SQLException."
Model Answer: "RuntimeException extends Exception. All RuntimeException subclasses are unchecked — the compiler does not require handling or declaration. The Exception branch splits into checked exceptions (RuntimeException's siblings) and unchecked RuntimeExceptions. This is why catching Exception catches both checked exceptions and RuntimeExceptions unless you specifically exclude RuntimeException."
Model Answer: "Yes. A bare try block with only a finally (no catch) is valid — cleanup runs regardless of what happens in the try block, and the exception propagates if not caught. This is equivalent to the pattern used by try-with-resources before Java 7. The try block must be followed by at least one catch or a finally."
Model Answer: "The catch block argument type determines which exceptions can be caught. A catch(IOException e) block catches IOException and any subclass (FileNotFoundException, SocketException). A catch(Exception e) block catches all exceptions including RuntimeExceptions. A catch(Throwable t) block catches everything including Error subclasses."
Model Answer: "ExceptionInInitializerError wraps an exception thrown during class initialization (static initializers or static field initializers). It prevents the class from being used because initialization failed. The original exception is available via getCause(). This error type itself extends LinkageError, not Exception, meaning it indicates a programming environment problem rather than a runtime failure in normal program logic."
Model Answer: "NullPointerException is unchecked. It extends RuntimeException, which is a subclass of Exception but not a checked exception. The compiler does not require handling or declaration. NullPointerException indicates a programming bug — typically dereferencing null — and is not considered a recoverable external failure."
Model Answer: "fillInStackTrace() populates the stack trace for the exception by capturing the current execution stack. It is called automatically when an exception is constructed. You rarely need to call it directly, but doing so before re-throwing an exception transfers the stack trace from the original throw point to the re-throw point — which can hide the real origin of the error. Only use it deliberately."
Model Answer: "Error subclasses (StackOverflowError, OutOfMemoryError, InternalError) indicate fatal JVM conditions. The program is in an invalid state and cannot safely recover. Catching these errors masks the problem and lets execution continue into potentially dangerous territory. Even if you catch OutOfMemoryError, for example, allocating new objects will likely fail again. Let errors propagate and terminate the process cleanly."
Model Answer: "Use checked exceptions (extends Exception, not RuntimeException) for recoverable external failures that the caller is expected to handle — file not found, connection refused, invalid user input. Use unchecked exceptions (extends RuntimeException) for programming bugs and invalid states that should not occur in correct programs — null dereference, illegal argument, index out of bounds. When in doubt, prefer unchecked — checked exceptions create verbose APIs and tight coupling."
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
Summary
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.