When would you use a broad catch with more-precise rethrow versus a multi-catch block, and what are the trade-offs?
answer
- Multi-catch = enumerate disjoint types; param implicitly final
- Broad catch + precise rethrow = react to anything, still narrow throws
- Multi-catch alternatives must be disjoint (no subtype overlap)
- Broad catch risks over-catching unchecked/Error
- Specific catches before broad ones (ordering)
basics
~20 sUse multi-catch when you want to name exactly the few exception types you handle. Use a broad catch (with precise rethrow) when you want one block to log or clean up for any exception but still rethrow with a precise contract. Multi-catch is more explicit; broad catch is more convenient.
solid answer
~50 sBoth are Java 7 features that reduce catch-block duplication, but they answer different questions. **Multi-catch** — `catch (IOException | SQLException e)` — is the right tool when you want to *enumerate* the specific exceptions you handle: it is self-documenting, the parameter is implicitly final, and anything not listed propagates untouched. Its limitation is you must list every type, and they must be disjoint (no subtype/supertype pairs). **Broad catch with more-precise rethrow** — `catch (Exception e) { log; throw e; }` — is better when you genuinely want to react to *everything* (e.g. a uniform 'log and rethrow' or resource cleanup at a boundary) while still letting the compiler declare only the precise checked types that can actually occur. Trade-off: the broad catch is less explicit about what can happen and risks accidentally catching unchecked exceptions you didn't intend to swallow. Rule of thumb: enumerate with multi-catch when handling a known set; use broad-catch-precise-rethrow for cross-cutting log/cleanup-then-rethrow.
code
java · 16 lines// Multi-catch: enumerate the known set you handle the same way
try {
process();
} catch (IOException | SQLException e) { // disjoint types, e is implicitly final
cleanup();
throw e; // throws IOException, SQLException
}
// Broad catch + precise rethrow: cross-cutting log-and-rethrow
try {
process(); // throws IOException, SQLException
} catch (Exception e) { // reacts to anything
metrics.increment("process.failure");
log.error("process failed", e);
throw e; // still inferred as IOException, SQLException
}go deeper
Knows multi-catch syntax catch (A | B e) exists to avoid duplicate blocks.
Can choose multi-catch for a known set and explain disjointness; aware broad catch plus precise rethrow is an alternative.
Articulates the trade-offs (explicitness vs convenience, over-catching risk, ordering) and picks the right tool per layer.
Defines team conventions for boundary error handling, weighs maintainability of forcing recompiles on new checked types versus silent absorption, and guards against Throwable/Error over-catching.
## The two Java 7 tools Before Java 7, handling several exception types with the same logic forced **duplicated catch blocks**: ```java try { ... } catch (IOException e) { cleanup(); throw e; } catch (SQLException e) { cleanup(); throw e; } // duplicate body ``` Java 7 introduced two ways to remove this duplication. ### 1. Multi-catch ```java catch (IOException | SQLException e) { cleanup(); throw e; } ``` One block handles a *union* of explicitly listed types. Properties: - The parameter `e` is **implicitly final** — you cannot reassign it. - Its static type is the **least upper bound** (common supertype) of the listed types for the purpose of member access, but a rethrow is typed precisely as the union, so `throws IOException, SQLException` is allowed. - The alternatives must be **disjoint**: you cannot write `catch (IOException | FileNotFoundException e)` because `FileNotFoundException` is already a subtype of `IOException` — the compiler rejects the redundancy. - Anything **not** listed simply isn't caught here and propagates normally. ### 2. Broad catch + more-precise rethrow ```java catch (Exception e) { // catches everything (checked + unchecked) log.error("failed", e); throw e; // compiler infers the precise checked types } ``` Here you deliberately catch a broad type, but because of more-precise rethrow analysis the method can still declare only the precise checked exceptions the try can actually throw (provided `e` is effectively final). ## How to choose **Prefer multi-catch when:** - You handle a **known, bounded set** of exception types and want the code to *document* exactly which ones. - You want unlisted exceptions (including unchecked ones) to flow past this block untouched — multi-catch only intercepts what you name. - Readability and intent matter more than brevity. **Prefer broad catch + precise rethrow when:** - You have a **cross-cutting concern** that applies to *any* failure — e.g. 'log and rethrow at the service boundary,' set a failure metric, mark a transaction rollback-only, or release a resource — and listing every possible type would be brittle or impossible. - You still want a precise `throws` contract so callers aren't forced to handle `Exception`. ## Trade-offs and risks - **Accidental over-catching.** `catch (Exception e)` also catches `RuntimeException` and any unchecked bug. If you *only* rethrow, that's fine (they propagate). But if the broad block does anything that could swallow or alter control flow, you may hide real bugs. Catching `Throwable` is even riskier — it grabs `Error`s like `OutOfMemoryError`. - **Loss of explicitness.** A reader of a broad catch can't tell at a glance what failures are expected; multi-catch is self-documenting. - **Multi-catch rigidity.** If a new checked exception is added to the try, multi-catch won't compile until you add it (which can be good — it forces a decision); broad catch silently absorbs the new type into its inferred set. - **Combining them.** A common pattern: multi-catch for the cases you truly *handle* differently, plus a final broad catch only for log-and-rethrow. Order matters — more specific catches must come before broader ones, or the code won't compile (unreachable catch). ## Practical guidance At a boundary where you translate or log uniformly, broad-catch-precise-rethrow keeps cleanup in one place while preserving an honest contract. Deep inside business logic, where each exception means something specific, multi-catch (or distinct blocks) communicates intent better. Never use a broad catch to *swallow* exceptions — that's the anti-pattern both features are meant to discourage.
- Why does `catch (IOException | FileNotFoundException e)` fail to compile?FileNotFoundException is a subclass of IOException, so it is already covered by the IOException alternative. Java requires multi-catch alternatives to be disjoint; the redundant subtype is a compile error.
- Can you mix multi-catch and more-precise rethrow in the same try?Yes. You can have specific multi-catch blocks for cases you handle distinctly, followed by a broad `catch (Exception e)` that logs and rethrows precisely. The specific blocks must come first to remain reachable.
saying these in an interview costs you the question
- Using multi-catch with overlapping types like IOException | FileNotFoundException (won't compile)
- Catching Throwable when you only meant Exception, grabbing Errors like OutOfMemoryError
- Using a broad catch to silently swallow exceptions instead of rethrowing
- Placing a broad catch before a specific one (unreachable catch compile error)
- Thinking multi-catch lets you reassign the parameter