What happens if an exception also occurs in the finally block?
If an exception occurs in finally, it will displace the exception from try. The original error will be lost.
Junior Level
Main effect
If an exception occurs in finally, it will displace the exception from try. The original error will be lost.
try {
throw new RuntimeException("Original error");
} finally {
throw new RuntimeException("Finally error");
}
// Only "Finally error" will be visible
Why it’s dangerous
try {
// Critical error - DB unavailable
connection.executeQuery("SELECT * FROM users");
} finally {
// Error on close - NPE
connection.close(); // connection == null
}
// Logs only show NPE, information about DB is lost
You’ll be debugging NPE when the real problem is infrastructure.
How to avoid
Use try-with-resources - it automatically handles suppressed exceptions:
try (Connection conn = dataSource.getConnection()) {
conn.executeQuery("SELECT * FROM users");
}
// If both try and close() fail - both exceptions are preserved
When NOT to rely on finally for cleanup
- Code in finally can throw exception - if
close(),disconnect(),release()can fail, wrap each call in separatetry-catchinside finally - Multiple resources without TWR - if closing 3+ resources in one finally, use separate try-catch for each, otherwise first exception will interrupt closing of the rest
- Critical transactions - in finally you can’t safely rollback a transaction if the main exception already occurred; use separate
catchblock for rollback - Async context - in
CompletableFuturefinally block may execute in another thread, and exception there will be lost without UncaughtExceptionHandler - Shutdown hooks - on
System.exit()finally may not execute at all - Native resource cleanup - for JNI/native memory use
Cleaner(Java 9+) instead of finally, as native resources may not free correctly
Middle Level
Suppression mechanism (Shadowing) - step by step
JVM carries only one exception object as “active” in the stack at any moment. Here’s what happens step by step:
Step 1: Code in try throws exception A (new RuntimeException("Original")).
Step 2: JVM marks the current stack frame as “throwing exception” and saves a reference to A in a special slot of the frame.
Step 3: Before exiting the method JVM executes the finally block (this is guaranteed by JLS 14.20.2 specification).
Step 4: Code in finally throws exception B (new RuntimeException("Finally")).
Step 5: JVM replaces the reference in the exception slot of the frame from A to B. Reference to A is lost forever - there’s no way to get it from catch or from outside.
Step 6: Exception B propagates up the stack. A becomes unreachable for GC only after the entire stack is unwound.
Visually:
Stack frame (before finally): pendingException = A
Stack frame (after finally): pendingException = B // A is lost!
Concrete code example
public class FinallyShadowing {
public static void main(String[] args) {
try {
System.out.println("Step 1: throw SQLException");
throw new SQLException("Database connection lost");
} finally {
System.out.println("Step 2: finally tries to close resource");
// close() also fails - and this overshadows SQLException
throw new NullPointerException("Connection was null in finally");
}
}
}
Output in stack trace:
Exception in thread "main" java.lang.NullPointerException: Connection was null in finally
at FinallyShadowing.main(FinallyShadowing.java:8)
Note: SQLException is completely absent from the stack trace. You’ll be debugging NPE when the real problem is DB connection loss.
How to preserve the original exception:
try {
throw new SQLException("Database connection lost");
} catch (Exception e) {
try {
// finally logic separately
closeSafely();
} catch (Exception closeEx) {
e.addSuppressed(closeEx); // Preserve both
}
throw e; // Original exception propagates
}
Shadowing via return
finally can “swallow” an exception even without throwing a new one:
public int dangerousMethod() {
try {
throw new RuntimeException("Error");
} finally {
return 42; // Exception swallowed! Method will return 42
}
}
The return instruction in bytecode of finally completes the stack frame before the exception handling mechanism has time to propagate the error. SonarQube marks this as Blocker.
Modern solution: Try-with-resources
Starting from Java 7:
- If exception in
try, then in automaticclose()- exception fromtryremains the main one - Exception from
close()is added as Suppressed - Full picture:
Error A (Suppressed: Error B)
try (Resource r = new Resource()) {
r.doWork(); // IOException #1
} // close() throws IOException #2
// #1 - main, #2 - suppressed
catch (Exception e) {
System.out.println(e.getMessage()); // #1
System.out.println(e.getSuppressed()[0]); // #2
}
Senior Level
Resource Leaks on exception in finally
If exception in finally occurred in the middle of cleanup (closing first of five sockets), the remaining resources will not be closed - execution of finally will be interrupted.
Senior Best Practice: wrap each close() in a separate try-catch inside finally:
finally {
try { resource1.close(); } catch (Exception e) { log.warn("Failed to close 1", e); }
try { resource2.close(); } catch (Exception e) { log.warn("Failed to close 2", e); }
}
Or use try-with-resources - it does this automatically.
Analyzing Logs
If you see strange exceptions from cleanup blocks in logs (SocketClosedException), always check if they’re hiding earlier errors.
Debugger
When step debugging, watch the transition to finally. If the exception variable in the stack frame suddenly changes - you caught Shadowing.
Bytecode analysis
javap -c MyClass
Will show how return in finally overwrites the return value and how exception overwrites the previous one.
Diagnostics
getSuppressed()- check the suppressed exceptions array- Static Analysis - Sonar warns about risky finally blocks
- Log Correlation - link exceptions from
tryandfinallyby Trace ID
Interview Cheat Sheet
Must know:
- Exception from
finallydisplaces exception fromtry- original error lost forever - JVM stores only one pending exception in the frame slot -
finallyoverwrites it returninfinallyalso “swallows” exception - method will return value instead of error- Try-with-resources automatically preserves both exceptions: main + suppressed from
close() - Without TWR wrap each
close()in separatetry-catchinsidefinally - On multiple resources in
finally, first exception will interrupt closing of the rest - resource leak - In async context, exception in
finallymay be lost withoutUncaughtExceptionHandler
Frequent follow-up questions:
- How to see the original exception? - Without TWR, no way - it’s lost. Use try-with-resources
- Why is TWR better than
finally? - Preserves both exceptions via suppressed mechanism - What if
finallydoesreturn? - Exception fromtryis swallowed, method returns value - How to properly close resources without TWR? - Each
close()in separatetry-catchinsidefinally
Red flags (NOT to say):
- “It’s safe to throw exceptions in
finally” - they will overshadow the original error - “I use
finallyfor closing resources” - outdated approach, use TWR - “Return in
finallyis normal practice” - SonarQube marks as Blocker - “Lost exception is not a big deal” - you’ll be debugging the wrong problem
Related topics:
- [[23. What are suppressed exceptions]] - how TWR preserves both exceptions
- [[9. What is try-with-resources]] - automatic resource management
- [[8. Is the execution of a finally block guaranteed]] - when finally may not execute
- [[19. Why you should not swallow exceptions (catch empty)]] - error loss