Detailed explanation
If an exception escapes a finally block, it replaces the outcome that was pending when Java entered finally. If try or catch was already throwing an exception, the caller receives the exception from finally instead. The original exception is not automatically retained as a cause or suppressed exception. For example, if try throws IllegalStateException("real cause") and finally throws RuntimeException("cleanup failed"), only the RuntimeException propagates unless your code explicitly preserves the earlier failure.
The precise rule concerns abrupt completion, not simply “the last exception wins.” Java executes finally before completing an exit from the associated try or catch. If finally completes normally, the pending exception, return, or other transfer of control continues. If finally completes abruptly, its reason replaces the earlier reason. Therefore, an exception escaping finally also overrides a pending return. Conversely, a return inside finally can discard a pending exception and make the method appear successful. An exception thrown and caught entirely within finally does not replace the pending outcome if finally subsequently completes normally.
This matters most during cleanup. A database operation might fail first, followed by Connection.close() failing inside finally. Losing the database failure can hide the information needed to diagnose the incident. Prefer try-with-resources for resources implementing AutoCloseable. If the body throws and closing also throws, Java preserves the body exception as primary and attaches closing failures as suppressed exceptions, accessible through getSuppressed(). Resources close in reverse declaration order. If the body completes normally but closing throws, the closing exception propagates rather than being suppressed merely because it came from cleanup.
When manual cleanup is unavoidable, handle cleanup failures deliberately. Catching and logging a secondary failure can preserve an already-pending exception, but blindly logging every cleanup failure may hide a failure when the main operation succeeded. To preserve both, track the primary exception and attach a distinct cleanup exception with primary.addSuppressed(secondary); without a primary exception, propagate or otherwise handle the cleanup failure according to the method’s contract. Interviewers look for this distinction and for avoiding control-flow statements in finally that override the intended outcome.
Key points to remember
- An exception escaping finally overrides a pending exception or return.
- Ordinary finally does not automatically preserve the original exception.
- If finally completes normally, the original pending outcome continues.
- Try-with-resources preserves a body exception and suppresses closing failures.
- Avoid return, break, continue, and explicit throws in finally that mask earlier outcomes.
Common mistakes candidates make
- Claiming the original exception automatically becomes the cause of the finally exception.
- Saying every exception thrown inside finally replaces the original, even when it is caught locally.
- Logging and ignoring all cleanup failures without considering whether the main operation succeeded.
Likely follow-up questions
What happens if try returns a value but finally throws?
The return expression is evaluated before finally runs, but the method does not return that value. The exception escaping finally propagates to the caller instead.
How do suppressed exceptions differ from an exception’s cause?
A cause usually represents the underlying failure that led to another exception. A suppressed exception records an additional failure, such as cleanup failing while another exception is already propagating; inspect it with getSuppressed().
What happens if multiple resources fail to close in try-with-resources?
Resources are closed in reverse declaration order. If the body threw, closing failures are suppressed on that exception; otherwise, the first closing failure becomes primary and subsequent closing failures are suppressed on it.
Interview tip: State which exception reaches the caller, illustrate it with a body failure followed by a close failure, and then explain how try-with-resources preserves both.
