skip to content

How do finally blocks behave while an exception is propagating up the call stack?

level: middleimportance: must knowfreq 70%

answer

  1. finally runs during unwinding, innermost-first
  2. Cleanup guaranteed on the error path
  3. Never return/throw from finally — it swallows the exception
  4. try-with-resources closes before catch, adds suppressed
  5. finally does not stop propagation

basics

~20 s

As an exception travels up through methods, every finally block it passes through runs before the exception continues to the next method. This lets cleanup code execute even when the method is exiting because of an error.

solid answer

~40 s

During propagation each stack frame is unwound, and any finally block associated with a try that is being left runs at that moment, before the exception moves to the caller. So finally blocks execute innermost-first, interleaved with the unwinding. This guarantees cleanup (closing files, releasing locks) on the error path. Two gotchas matter: a return, break, or another throw inside finally will override the in-flight exception, swallowing it silently — so never return or throw from finally. Also, try-with-resources is preferable for closing resources because it closes them before any catch runs and correctly chains a close failure as a suppressed exception rather than masking the original. finally does not stop propagation; after it runs, the original exception keeps climbing unless finally itself altered control flow.

code

java · 12 lines
java
void demo() {
    try {
        throw new IllegalStateException("real bug");
    } finally {
        return; // BUG: swallows IllegalStateException; caller sees a normal return
    }
}

// Prefer try-with-resources for cleanup:
try (var in = new java.io.FileReader("f.txt")) {
    process(in);
} // close() runs before any catch; a close failure becomes a suppressed exception

go deeper

for a junior

Knows finally always runs, including when an exception occurs, and is used for cleanup.

for a middle

Explains that finally runs during unwinding innermost-first and does not by itself stop propagation.

for a senior

Warns that return/throw in finally swallows the in-flight exception and prefers try-with-resources with suppressed exceptions.

for a principal

Sets team conventions banning control flow in finally, mandates try-with-resources, and reasons about suppressed-exception diagnostics in logs.

## Prerequisites A **try/finally** is: `try { ... } finally { ... }`. The `finally` block is **guaranteed to run** when control leaves the `try` block for any reason — normal completion, a `return`, or an exception. **Propagation** is the process by which an uncaught exception climbs the call stack (innermost method to outermost), abandoning each method's stack frame as it goes; that abandonment is **unwinding**. ## When exactly does finally run during propagation? When an exception is thrown inside (or propagates into) a `try` that has a `finally`, the JVM does NOT immediately leave the method. It first executes the `finally` block. Only after `finally` finishes does the exception continue upward. Because propagation is innermost-first, finally blocks also run **innermost-first**, interleaved with each unwound frame. ```java void a() { try { b(); } finally { System.out.println("finally a"); } } void b() { try { c(); } finally { System.out.println("finally b"); } } void c() { throw new RuntimeException("boom"); } ``` If `a()` is called and nobody catches, the output before the trace is: ``` finally b finally a ``` `c` has no finally, so unwinding `c` runs nothing; `b`'s finally runs, then `a`'s finally runs, then the exception goes uncaught. ## The dangerous part: finally can swallow the exception `finally` runs while an exception is *in flight*. If the finally block itself completes abruptly — by `return`, `break`, `continue`, or throwing a new exception — that **replaces** the in-flight exception. The original is discarded with no trace. Example: ```java try { throw new IllegalStateException("real bug"); } finally { return; } // swallows IllegalStateException — caller sees normal return! ``` The caller never learns about `IllegalStateException`. **Rule: never `return` or `throw` from a `finally` block.** ## try-with-resources and suppressed exceptions Before Java 7, closing a resource in `finally` was both verbose and prone to masking: if both the body and `close()` threw, the close exception would replace the body's. **try-with-resources** fixes this: ```java try (var in = new FileReader(path)) { use(in); } ``` The resource is closed automatically. If the body throws AND `close()` throws, the body's exception propagates and the close exception is attached as a **suppressed exception** (retrievable via `Throwable.getSuppressed()`), so neither is lost. This is the modern, preferred form for cleanup. ## Deriving the answer finally runs *during* unwinding, innermost-first, guaranteeing cleanup on the error path; it does not halt propagation; and abrupt completion inside finally silently overrides the in-flight exception — which is why try-with-resources is the safer pattern.

  • What is a suppressed exception and when does it appear?
    When try-with-resources is closing a resource and close() throws while the body already threw, the close exception is attached to the body exception as a suppressed exception (Throwable.getSuppressed()), so the original isn't lost.
  • If finally has a return and the try threw, what does the caller see?
    A normal return. The return in finally completes abruptly and discards the in-flight exception entirely — a classic bug that hides errors.

saying these in an interview costs you the question

  • Saying finally runs only on normal completion — it also runs during propagation.
  • Thinking finally stops the exception from propagating (it doesn't, unless it changes control flow).
  • Returning or throwing inside finally to 'handle' the error — it silently swallows the original.
  • Believing the close exception in old finally-based cleanup is preserved — it usually masks the body exception.

context