skip to content

How does the checked vs. unchecked distinction affect how an exception propagates and what the compiler requires?

level: middleimportance: should knowfreq 62%

answer

  1. Runtime propagation identical for both
  2. Checked = compiler 'catch or specify'
  3. throws chain marks a checked exception's path
  4. RuntimeException/Error = unchecked, no declaration
  5. Wrap checked in unchecked to slip past APIs

basics

~20 s

Both kinds propagate up the stack the same way at runtime. The difference is the compiler: a checked exception must be either caught or declared with throws on every method that lets it pass through, while an unchecked exception (RuntimeException/Error) needs no declaration.

solid answer

~50 s

Propagation mechanics are identical for both: the exception unwinds frames until caught or the thread dies. The distinction is purely compile-time enforcement. Checked exceptions (subclasses of Exception that are not RuntimeException) participate in the 'catch or specify' rule: any method through which such an exception can propagate must list it in its throws clause, or catch it. So a checked exception leaves a visible trail of throws declarations along the call chain. Unchecked exceptions — RuntimeException and its subclasses, plus Error — are exempt; they propagate silently through methods that declare nothing. This is why a NullPointerException can surface many frames up with no throws annotations anywhere. Practically, checked exceptions force callers to acknowledge recoverable conditions, while unchecked ones model programming bugs or unrecoverable states. Wrapping a checked exception in an unchecked one is a common way to let it propagate past APIs that can't declare it.

go deeper

for a junior

Knows checked exceptions must be caught or declared while runtime exceptions need not be.

for a middle

Explains the catch-or-specify rule produces a throws chain and that runtime propagation is the same for both kinds.

for a senior

Discusses when to wrap checked into unchecked, lambda/stream constraints, and API contract design around checked exceptions.

for a principal

Sets exception-policy guidelines (when to use checked vs unchecked), considers framework conventions, and reasons about API evolution and backward compatibility of throws clauses.

## The exception type hierarchy Everything throwable extends `java.lang.Throwable`. Two branches matter: - `Error` — serious problems the application normally shouldn't catch (e.g. `OutOfMemoryError`). **Unchecked.** - `Exception` — application-level problems. Within it: - `RuntimeException` and its subclasses (e.g. `NullPointerException`, `IllegalArgumentException`) are **unchecked**. - Every other `Exception` (e.g. `IOException`, `SQLException`) is **checked**. **Checked** vs **unchecked** describes whether the *compiler* polices the exception, not how it behaves at runtime. ## The 'catch or specify' rule (checked only) For a checked exception, the compiler enforces: every method in which such an exception **can occur or pass through** must either 1. **catch** it (a try/catch that handles it), or 2. **specify** it in its `throws` clause, e.g. `void read() throws IOException`. Because propagation passes a checked exception through each caller, **each** of those callers must also catch-or-specify it. The result is a visible chain of `throws IOException` declarations from the throw site up to wherever someone finally handles it. ```java void level3() throws IOException { throw new IOException("disk"); } void level2() throws IOException { level3(); } // must declare void level1() { try { level2(); } catch (IOException e) { /* handled here */ } } ``` ## Unchecked exceptions: no paperwork `RuntimeException`/`Error` are exempt from catch-or-specify. A method may throw or let them propagate **without any `throws` clause**. That is why a `NullPointerException` can bubble up through ten methods that declare nothing and still appear at the top — propagation is identical, only the compiler is silent. ## Runtime propagation is the SAME for both This is the crux: at runtime the JVM does not care whether an exception was checked or unchecked. It unwinds frames innermost-first looking for a matching catch exactly the same way (and runs finally blocks the same way). The checked/unchecked label has **zero** effect once the program is running; it only shaped what the compiler allowed you to write. ## Why the distinction exists - **Checked**: model conditions a well-written caller could reasonably recover from (a missing file, a network blip). Forcing declaration makes the failure part of the API contract. - **Unchecked**: model programming errors (bad arguments, null derefs) or unrecoverable states — failures you usually can't sensibly handle locally. ## Wrapping to change the rules When an API signature can't declare a checked exception (e.g. it implements an interface method without `throws`), you wrap it: `throw new RuntimeException(e);`. The original is preserved as the **cause** (`getCause()`), and it now propagates as unchecked. This is common with lambdas/streams, whose functional interfaces don't declare checked exceptions. ## Deriving the answer Same runtime propagation for both; the only difference is the compiler's catch-or-specify mandate on checked exceptions, which leaves throws declarations along the propagation path, while unchecked exceptions travel silently.

  • Why can a NullPointerException propagate through methods that declare no throws clause?
    NullPointerException is a RuntimeException, which is unchecked. The compiler does not require declaring or catching unchecked exceptions, so they propagate freely with no throws annotations.
  • How do you propagate a checked exception out of a lambda used in a Stream?
    Functional interfaces like Function don't declare checked exceptions, so you catch it inside the lambda and rethrow it wrapped in an unchecked exception (e.g. RuntimeException), preserving the original as the cause.

saying these in an interview costs you the question

  • Claiming unchecked exceptions propagate differently at runtime — propagation is identical.
  • Saying every method must declare RuntimeException — unchecked needs no declaration.
  • Thinking 'checked' means it can be caught and 'unchecked' means it can't — both are catchable.
  • Believing Error is checked because it extends Throwable — Error is unchecked.

context