skip to content

Explain the 'recoverable condition vs programming error' rule for choosing checked vs unchecked custom exceptions, with examples of each.

level: middleimportance: must knowfreq 70%

answer

  1. Recoverable + caller has useful action → checked
  2. Programming error / precondition violation → unchecked
  3. Unrecoverable but not a bug → usually unchecked too
  4. Test: would the catch block contain real recovery, or just log+rethrow?
  5. Document with @throws either way

basics

~20 s

If the caller can do something useful to recover, make it checked so they're forced to handle it. If the failure is a bug the caller should have prevented, or can't recover from, make it unchecked.

solid answer

~50 s

The core rule, from Effective Java, is: use checked exceptions for conditions from which the caller can reasonably recover, and unchecked exceptions for programming errors. A 'recoverable' condition is one where the caller has a sensible alternative action — retry, fall back, prompt the user — so forcing them to handle it (via the compiler) is helpful. Examples: a network timeout (retry), an invalid user-supplied file (ask for another), insufficient funds in a transfer (tell the user). A 'programming error' is a precondition violation the caller should simply not have caused — null where null is illegal, bad index, illegal state. These map to unchecked (IllegalArgumentException, IllegalStateException, NullPointerException). The test: would a try/catch at the call site contain meaningful recovery code, or just logging/rethrow? If meaningful recovery, checked is justified; otherwise unchecked keeps the API clean.

go deeper

for a junior

Can repeat the rule: recoverable → checked, bug → unchecked, and give one example of each.

for a middle

Applies the rule confidently, classifies several conditions correctly, and uses the 'what goes in the catch block' test to decide.

for a senior

Adds the third (unrecoverable) bucket, explains why not to over-use either kind, and ties the decision to API ergonomics and documentation obligations.

for a principal

Treats borderline cases (recoverable-for-some-callers, edge validation) as policy decisions, and can design dual APIs (throwing vs Optional/try-variant) to avoid forcing inappropriate handling.

## Why this rule exists Java is the rare mainstream language with two flavors of exception, and the only thing that separates them is whether the **compiler forces the caller to deal with them**. So the real question is: *for this particular failure, do I want to compel every caller to write handling code?* The 'recoverable vs programming error' rule is the heuristic that answers it. ## Defining the two categories **Recoverable condition.** A runtime situation that is not a bug — the code did everything right but the world didn't cooperate, AND the caller has a *useful* response available. 'Useful response' means more than logging: retry, switch to a backup, ask the user for new input, return a default, queue for later. Because such a response is possible and desirable, you use a **checked** exception so the compiler guarantees the caller at least acknowledges it. *Examples worth making checked:* - A file the user supplied is malformed → prompt for another file. - A remote service times out → retry or fall back to cache. - A bank transfer hits insufficient funds → report it to the user. **Programming error.** A failure caused by misuse of the API — a violated *precondition*. The caller passed something illegal or called a method in an illegal state. The right fix is to **correct the code**, not to write recovery logic, so forcing a `catch` adds noise without value. These are **unchecked**. *Examples that should be unchecked:* - `null` passed where the contract forbids it → `NullPointerException`. - Index outside array bounds → `IndexOutOfBoundsException`. - Calling `next()` on an exhausted iterator → `IllegalStateException`. - An argument outside the legal range → `IllegalArgumentException`. ## A third bucket: unrecoverable conditions Some non-bug conditions are still impossible (or pointless) to recover from at the call site — e.g. a critical resource is permanently unavailable. Even though these aren't programming errors, forcing a `catch` no one can act on is just ceremony, so the pragmatic choice is usually **unchecked** (often wrapping the original cause). `Error` is the extreme end of this (e.g. `OutOfMemoryError`) — the app shouldn't try to handle it at all. ## The practical test Ask: *if I write `try { ... } catch (ThisException e) { ... }` at a typical call site, what goes in the catch block?* - If it would contain **real recovery** (retry, fallback, user prompt) → the condition is recoverable → **checked** is justified. - If it would just **log and rethrow**, or there's nothing sensible to do → don't force it → **unchecked**. ## Why not just make everything checked? Because checked exceptions impose costs: `throws` clauses propagate up the call chain, they leak low-level concerns into high-level APIs (a leaky abstraction), they don't fit Java's functional interfaces (lambdas/streams), and they tempt developers into swallowing exceptions with empty catch blocks — which is worse than a clear unchecked failure. So the rule isn't 'prefer checked'; it's 'make checked only the failures a caller genuinely should be forced to handle, and make everything else unchecked.' ## Why not just make everything unchecked? Because then a genuinely recoverable, important failure becomes invisible in the type system — a caller can forget it exists and ship code that crashes in production on a condition they could have handled. Checked exceptions, used sparingly, encode 'you really need to deal with this' into the contract in a way the compiler enforces. The art is reserving them for exactly those cases. ## Documentation either way Whichever you choose, document the exception in Javadoc with `@throws`, explaining the condition and the expected recovery. This is *mandatory* for unchecked exceptions (the type system won't advertise them) and good practice for checked ones.

  • Is a user typing an invalid email into a form a 'recoverable condition' or a 'programming error'?
    It depends on where validation happens. Invalid external input that reaches a service boundary is a legitimate runtime condition you might surface (checked is defensible). But if your own code already validated and an internal method still receives garbage, that's a programming error and should be unchecked. Most teams validate at the edge and use unchecked exceptions internally.
  • How do you decide for a condition that's recoverable for some callers but not others?
    Lean toward unchecked plus clear documentation, or offer both styles (e.g. a throwing method and a tryX/Optional-returning variant). Forcing every caller to handle a failure only some can recover from spreads boilerplate; let callers opt in to handling via documented unchecked exceptions or alternative APIs.

saying these in an interview costs you the question

  • Treating 'recoverable' as 'can be caught' — everything can be caught; recoverable means a useful action exists
  • Making validation/precondition failures checked
  • Claiming you should make everything checked for safety
  • Forgetting to document unchecked exceptions (the compiler won't)
  • Confusing 'logged' with 'recovered'

context