Why this matters
- The checked-versus-unchecked distinction decides whether the compiler forces a caller to deal with your failure, which is an API design decision rather than a technical one.
- Catching the wrong branch of the hierarchy turns a recoverable problem into a hidden one, or a fatal problem into a corrupted program that keeps running.
- Reading a stack trace correctly is the single highest-value debugging skill, and most people read it in the wrong direction.
The hierarchy
RuntimeException sits under Exception but escapes the checked rule — that single exception to the rule is what the whole checked/unchecked distinction rests on.
Throwableis the root. Only its subtypes may appear in athrowor acatch.Errorsignals a condition outside your program's control — the heap is exhausted, a stack overflowed, a class is missing. Do not catch these.Exceptionsignals an application-level problem. Everything here is fair game to handle.RuntimeException, a subclass ofException, is the unchecked branch. The compiler does not require handling it.- Every other
Exceptionsubclass is checked, and the compiler insists you either catch it or declare it.
Checked versus unchecked
The compiler's rule is purely structural: is the type a RuntimeException or an Error? If not, it
is checked.
| Aspect | Checked | Unchecked |
|---|---|---|
| Examples | IOException, SQLException, ClassNotFoundException | NullPointerException, IllegalArgumentException, IllegalStateException |
| Compiler requires handling | Yes — catch or declare | No |
| Signals | An expected external failure | A programming error, usually |
| Caller can recover | Often — retry, fall back, report | Rarely — the code needs fixing |
| Appears in the signature | Must, via throws | Optional, for documentation |
Examples
CheckedIOException, SQLException, ClassNotFoundExceptionUncheckedNullPointerException, IllegalArgumentException, IllegalStateExceptionCompiler requires handling
CheckedYes — catch or declareUncheckedNoSignals
CheckedAn expected external failureUncheckedA programming error, usuallyCaller can recover
CheckedOften — retry, fall back, reportUncheckedRarely — the code needs fixingAppears in the signature
CheckedMust, via throwsUncheckedOptional, for documentation
The intent is that checked means 'something outside went wrong' and unchecked means 'the code is wrong'.
// Checked: the compiler will not let you ignore it
public String readConfig(Path path) throws IOException {
return Files.readString(path);
}
// Unchecked: no declaration required, though documenting it is good practice
public void setAge(int age) {
if (age < 0) {
throw new IllegalArgumentException("Age cannot be negative: " + age);
}
this.age = age;
}
Common exceptions and what they really mean
Unchecked — nearly always a bug in your code
NullPointerException— a method or field was accessed onnull. Since Java 14 the message names the exact expression, which makes this far easier to fix.IllegalArgumentException— a caller passed a value the method cannot accept. The right choice for argument validation.IllegalStateException— the method is valid but not now. Callingnext()on an exhausted iterator, for example.IndexOutOfBoundsException— an index outside a collection or array;ArrayIndexOutOfBoundsExceptionis its array-specific form.ClassCastException— a downcast to a type the object is not.NumberFormatException— a subclass ofIllegalArgumentException, thrown byInteger.parseInton unparseable text.ConcurrentModificationException— a collection was structurally modified while being iterated.ArithmeticException— integer division by zero. Floating-point division givesInfinityinstead.UnsupportedOperationException— the operation exists on the type but not on this implementation, as withList.of(...).add(...).
Checked — the outside world failed
IOException— any failed input or output.FileNotFoundExceptionis the common specific form.SQLException— a database rejected or could not complete the statement.ClassNotFoundException— a reflective lookup by name found nothing on the classpath.InterruptedException— a waiting thread was asked to stop waiting. Never swallow this one.
Errors — do not catch
OutOfMemoryError— the heap or metaspace cannot satisfy an allocation.StackOverflowError— usually unbounded recursion.NoClassDefFoundError— a class present at compile time is missing at run time; almost always a packaging problem.ExceptionInInitializerError— a static initialiser threw, so the class can never be used.
Reading a stack trace
Exception in thread "main" java.lang.IllegalStateException: Cart is empty
at com.prepzone.cart.Cart.checkout(Cart.java:47)
at com.prepzone.order.OrderService.place(OrderService.java:23)
at com.prepzone.web.OrderController.submit(OrderController.java:15)
Caused by: java.sql.SQLException: Connection timed out
at com.prepzone.db.ConnectionPool.borrow(ConnectionPool.java:88)
... 3 more
How to read it
- The first line is the exception type and message — what went wrong.
- The topmost
atline is where it was thrown. That is where you look first, not the bottom. - Lines below are the call chain that led there, most recent first.
Caused byis the original failure, wrapped by the one above. The lowestCaused byis usually the real root cause.... 3 moremeans the remaining frames are identical to the enclosing trace and were elided.
Wrapping preserves the cause
When you translate an exception into one appropriate for your layer, always pass the original as the cause.
public Order findById(String id) {
try {
return repository.load(id);
} catch (SQLException e) {
// Right: the original cause travels with it
throw new OrderLookupException("Could not load order " + id, e);
}
}
// Wrong: the SQLException and its stack trace are gone forever
catch (SQLException e) {
throw new OrderLookupException("Could not load order " + id);
}
The second version produces a stack trace that points at your own code and tells you nothing about the timeout, the constraint violation or the deadlock that actually happened. This is the most expensive single mistake in exception handling, because it destroys the information at the exact moment you will need it.
Throwing well
public void transfer(Account from, Account to, BigDecimal amount) {
Objects.requireNonNull(from, "from account is required");
Objects.requireNonNull(to, "to account is required");
if (amount.signum() <= 0) {
throw new IllegalArgumentException("Transfer amount must be positive, was " + amount);
}
if (from.balance().compareTo(amount) < 0) {
throw new InsufficientFundsException(from.id(), amount, from.balance());
}
}
- Fail fast. Validate at the entry point, before any state changes.
- Put the offending value in the message. "Invalid amount" costs a debugging session; "Transfer amount must be positive, was -50" does not.
- Choose the most specific type available.
IllegalArgumentExceptionfor a bad argument,IllegalStateExceptionfor bad timing. - Use
Objects.requireNonNullfor null checks; it throws a properly wordedNullPointerExceptionin one line.
Common misreadings
- "Unchecked exceptions cannot be caught." They can be caught exactly like checked ones. The difference is only that the compiler does not insist.
- "
ErrorandExceptionare related by inheritance." Both extendThrowable, but neither extends the other. - "
NullPointerExceptionis checked." It is aRuntimeException, so unchecked. - "Catching
Exceptionis a reasonable default." It hides bugs you wanted to see. Catch what you can actually handle. - "The bottom of a stack trace is the error." The top frame is where it was thrown; the deepest
Caused byis the root cause. - "Throwing is expensive, so avoid exceptions." Filling in the stack trace costs something, which is why exceptions should not be control flow — but a genuine error path is exactly what they are for.
Quick recall
Everything you need if you only revisit this box.
Throwablesplits intoError(JVM-level, do not catch) andException(application-level).RuntimeExceptionand its subclasses are unchecked; every otherExceptionis checked and must be caught or declared.- Checked means "the outside world may fail and you can recover"; unchecked means "the code is wrong".
- Never
catch (Throwable)— it swallowsError. - Read a trace top down: first
atline is the throw site, the deepestCaused byis the root cause. - Always pass the original exception as the cause when wrapping. Dropping it destroys the evidence.
- Fail fast, include the offending value in the message, and pick the most specific exception type.
Test yourself
Answer these before moving on — recall is what makes it stick.