HuntExams Academy logo
JavaAdvanced#exceptions

What happens if an exception is thrown in a finally block?

Answer

The exception thrown by finally replaces whatever was already in flight. If try or catch was throwing, that original exception is discarded and the new one propagates \u2014 the real cause of the failure disappears, which makes production bugs very hard to debug. A return inside finally does the same thing: it swallows the pending exception. Why: finally executes as part of leaving the block, and Java keeps only one exception on the current propagation path, so the last one thrown wins. The classic real-world case is close() in a finally block: the body failed for a genuine reason, then close() also failed, and only the close failure reaches your logs. Correct approaches: 1. Use try-with-resources. Closing is automatic, and when close() throws while the body is already throwing, the close exception is attached as a suppressed exception instead of replacing the original \u2014 read it with getSuppressed(). 2. If you must write finally, wrap risky cleanup in its own try/catch and log the secondary failure rather than letting it escape. 3. Never put return, break, continue or throw in finally. Static-analysis tools such as SonarQube and ErrorProne flag exactly this. 4. If you need both exceptions, call primary.addSuppressed(secondary) before rethrowing. Interview follow-ups: "Which exception does the caller see?" \u2014 the one thrown by finally. "How do you keep both?" \u2014 try-with-resources, or addSuppressed.

Example

// The original cause is LOST
static void bad() {
    try {
        throw new IllegalStateException("real cause");
    } finally {
        throw new RuntimeException("from finally");   // this one wins
    }
}
// caller sees: RuntimeException: from finally

// try-with-resources keeps BOTH
class Res implements AutoCloseable {
    public void close() { throw new RuntimeException("close failed"); }
}
try (Res r = new Res()) {
    throw new IllegalStateException("real cause");
} catch (Exception e) {
    System.out.println(e.getMessage());                     // real cause
    System.out.println(e.getSuppressed()[0].getMessage());  // close failed
}

// Safe manual cleanup
Connection conn = null;
try {
    conn = ds.getConnection();
    // work
} finally {
    if (conn != null) {
        try { conn.close(); }
        catch (SQLException e) { log.warn("close failed", e); }  // never escapes
    }
}

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.

Related Interview Questions

1
JavaIntermediate#exceptions

What is multi-catch in Java 7+?

Open
2
JavaIntermediate#concurrency

What is a race condition and how do you prevent it?

Open
3
JavaAdvanced#concurrency

What is a deadlock and how can it be avoided?

Open