Explain the 'recoverable condition vs programming error' rule for choosing checked vs unchecked custom exceptions, with examples of each.
answer
- Recoverable + caller has useful action → checked
- Programming error / precondition violation → unchecked
- Unrecoverable but not a bug → usually unchecked too
- Test: would the catch block contain real recovery, or just log+rethrow?
- Document with @throws either way
basics
~20 sIf 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 sThe 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
Can repeat the rule: recoverable → checked, bug → unchecked, and give one example of each.
Applies the rule confidently, classifies several conditions correctly, and uses the 'what goes in the catch block' test to decide.
Adds the third (unrecoverable) bucket, explains why not to over-use either kind, and ties the decision to API ergonomics and documentation obligations.
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'