Question 8 · Section 7

Is the execution of a finally block guaranteed?

The finally block always executes (with rare exceptions - see Middle Level). For beginners, consider finally as guaranteed to execute.

Language versions: English Russian Ukrainian

Junior Level

Basic rule

The finally block always executes (with rare exceptions - see Middle Level). For beginners, consider finally as guaranteed to execute.

try {
    System.out.println("try");
} finally {
    System.out.println("finally"); // Will definitely execute
}
// Output: try \n finally

With an exception

try {
    throw new RuntimeException("Error");
} finally {
    System.out.println("Cleanup"); // Still executes
}
// Output: Cleanup, then exception propagates further

Why finally is needed

For releasing resources - closing files, connections, locks:

FileInputStream fis = null;
try {
    fis = new FileInputStream("file.txt");
    // work with file
} finally {
    if (fis != null) fis.close(); // Will close in any case
}

In modern Java, use try-with-resources - it does this automatically.


Middle Level

When NOT to use finally

For closing resources, use try-with-resources instead of finally with close(). Use finally for: releasing locks (ReentrantLock.unlock()), restoring state (Thread.currentThread().interrupt()), logging.

How it works in bytecode

In old Java (before 6), the jsr (Jump to Subroutine) instruction was used. In modern Java, the compiler copies the finally block bytecode to all method exit branches - after try, after each catch, and in the implicit exception handler.

This increases method size but makes control flow linear.

When finally will NOT execute

  1. System.exit(n) - forceful JVM termination
  2. Runtime.halt(n) - even harsher, doesn’t even run Shutdown Hooks
  3. Infinite loop in try - thread never reaches exit
  4. Deadlock - thread is blocked forever
  5. Kill -9 - SIGKILL signal kills the process at OS level
  6. Daemon Threads - if only daemons remain, JVM terminates without waiting
  7. JVM crash - VirtualMachineError at the moment of transition to finally

Danger: overwriting return

int method() {
    try { return 1; }
    finally { return 2; }
}
// Returns 2! finally overwrites the return value

return in finally executes AFTER the value was already prepared in try. Finally has the last chance to change the result. In bytecode: the value from try is saved to a temporary variable, finally executes, and if it has its own return - it overwrites the result.

Danger: swallowing exceptions

try {
    throw new RuntimeException("Original error");
} finally {
    throw new RuntimeException("Finally error");
}
// Original exception is lost forever!

Senior Level

Shadowing - losing Root Cause

Shadowing - when one exception “hides” another. Root Cause - the very first exception in the chain that caused all others.

If exception A occurred in try, and exception B in finally, then A will be lost:

try {
    // SQLException - DB unavailable
    connection.executeQuery("...");
} finally {
    // NPE - overshadows SQLException
    connection.close(); // connection == null
}
// Logs only show NPE, no info about DB

Solution: try-with-resources adds exceptions from close() to the suppressed list, rather than overwriting the main one.

Correct pattern for locks

Lock lock = new ReentrantLock();
lock.lock(); // Lock BEFORE try
try {
    // critical section
} finally {
    lock.unlock(); // Always release
}

Make sure lock() was before try. If lock() throws, unlock() will try to release an unacquired lock -> IllegalMonitorStateException.

Locking Pitfall in finally

If finally calls a method that can block (writing to log on a full disk), you’ll hang the entire resource cleanup thread.

finally and try-with-resources

try (Resource r = new Resource()) {
    // work
} catch (Exception e) {
    // resources are ALREADY closed here
} finally {
    // resources are also already closed
}

Manually written catch and finally blocks execute after automatic resource closing.

Diagnostics

  • Deadlock in finally - if finally calls a blocking method, thread will hang
  • Keep code in finally as simple and fast as possible
  • Always protect finally from new exceptions
  • For analysis use javap -c - you’ll see how finally is copied to all branches

Interview Cheat Sheet

Must know:

  • finally always executes (with rare exceptions)
  • When finally will NOT execute: System.exit(), Runtime.halt(), infinite loop, deadlock, kill -9, daemon threads, JVM crash
  • return in finally overwrites value from try - returns value from finally
  • Exception in finally overwrites the main exception from try (shadowing)
  • Compiler copies finally bytecode to all branches - increases method size
  • For closing resources use try-with-resources, not finally
  • Use finally for: releasing locks, restoring state, logging
  • catch and finally blocks execute AFTER automatic resource closing in TWR

Frequent follow-up questions:

  • Can finally not execute? - Yes: System.exit(), kill -9, infinite loop, JVM crash
  • What happens with return in finally? - Overwrites return value from try - this is a bug
  • What is exception shadowing? - Exception from finally overwrites the main one from try; solution - try-with-resources with suppressed
  • Correct pattern for locks? - lock.lock() BEFORE try, lock.unlock() in finally

Red flags (NOT to say):

  • “Finally executes absolutely always” - no, System.exit() and kill -9 stop JVM
  • “I use return in finally to return a default value” - this is an anti-pattern, overwrites try result
  • “I catch exception in finally and continue” - original exception is lost forever

Related topics:

  • [[What is try-with-resources]]
  • [[What are suppressed exceptions]]
  • [[What are the requirements for resources in try-with-resources]]
  • [[What is exception chaining]]
  • [[How to properly log exceptions]]