Multi-catch & Rethrowing
Multi-catch & Rethrowing
By lesson five you know how to throw and declare exceptions. This lesson covers two closely related techniques that make your error-handling code cleaner and more expressive: catching several exception types in a single block, and deliberately rethrowing — or wrapping — an exception so that the right layer of your program sees the right kind of error.
The problem with repetitive catch blocks
Suppose you are parsing user input and writing to a file. Both operations can fail in different ways, but your recovery action is identical: log the problem and return a default value. The naive approach leads to duplication:
Java 7 introduced multi-catch to eliminate this repetition.
Multi-catch syntax
Separate the exception types with a pipe character (|) inside a single catch clause. The parameter is implicitly final.
The two types are unrelated in the hierarchy, yet they share one handler. Java infers the catch parameter as the most specific common supertype — here Exception — which is why the parameter is implicitly final: you cannot reassign a reference whose exact runtime type the compiler cannot predict.
catch blocks, the types in a pipe list are not evaluated top-to-bottom. They are all equally considered. You cannot list a supertype alongside one of its subtypes (e.g., Exception | IOException) because that would make the subtype unreachable; the compiler rejects it with a compile-time error.
When multi-catch is the right tool
- The recovery logic is genuinely identical for all listed types.
- The types are unrelated (no parent-child relationship between them).
- You still want to preserve the original exception in a log or chain.
If the handling differs — say you want to retry on IOException but not on NumberFormatException — keep separate blocks.
Rethrowing the same exception
Sometimes you catch an exception only to add a log entry or release a resource, then let it propagate up unchanged. You do that with a plain throw e;:
Since Java 7, the compiler is smart enough to know that only IOException can be thrown from the try block, so even though the catch parameter is typed as Exception, rethrowing it is allowed in a method declared throws IOException. This is called precise rethrow.
Exception wrapping (chaining)
When a low-level exception leaks out of a high-level method, the caller is forced to know about an implementation detail it should not care about. The solution is to wrap the original exception inside a new one that fits the abstraction, while preserving the cause for debugging.
The constructor signature used above is the standard one for wrappable exceptions:
Now the stack trace printed by e.printStackTrace() shows both the high-level RepositoryException and the original SQLException under the heading "Caused by:", giving you everything you need for debugging without exposing the SQL layer to callers.
throw new RepositoryException("Could not load user") — without the e — silently discards the root cause and makes bugs much harder to diagnose in production.
Rethrowing inside a multi-catch
You can combine both techniques. Here a utility method catches two unrelated exceptions, logs them with a single handler, then rethrows each wrapped as a domain exception:
catch (Exception e) { throw e; } — delete the block entirely. Pointless catch-rethrow clutters code and can even change the checked/unchecked contract in ways that surprise callers.
Summary
- Multi-catch (
catch (A | B e)) eliminates duplicate handlers when the recovery is identical; types must be unrelated in the hierarchy. - Rethrowing lets a layer log or release resources and then pass the exception up unchanged.
- Exception wrapping translates a low-level exception into one that fits the current abstraction layer, using the
causeconstructor argument to preserve the original for debugging. - Always include the original exception as the cause when wrapping — never discard it silently.