Definition
finallyis a block that executes after thetryblock (andcatch, if present) regardless of whether an exception occurred.- It is guaranteed to run when: try succeeds, try throws and catch handles it, try throws and catch re-throws, or try/catch contains a return statement.
- It does not run in four situations:
System.exit()shuts down the JVM before finally, a JVM crash (SIGKILL, native error) kills the process,Runtime.halt()stops the JVM immediately, or an infinite loop or deadlock in try prevents reaching finally. - The modern replacement is
try-with-resourcesforAutoCloseableresources, which is cleaner and handles the dual-exception scenario correctly.
The finally block is the end-of-day vault-locking procedure at National Bank of India. No matter what happened during the day (successful transactions, failed transactions, customer complaints), the vault must be locked at closing time. Even if there was a fire alarm (exception), the vault gets locked. But if the entire building collapses (JVM crash) or someone cuts the main power (System.exit), there is no time to lock anything.
Understanding the Concepts
What is a finally block?
The finally block is a section of code that executes after the try block (and any catch block) completes, regardless of whether an exception was thrown. It provides a guaranteed cleanup point for releasing resources like database connections, file handles, and locks.
package com.bank.demo;
import java.math.BigDecimal;
public class FinallyBasicDemo {
public static void main(String[] args) {
System.out.println("=== No exception ===");
try {
BigDecimal balance = new BigDecimal("75000.00");
System.out.println("Balance: Rs." + balance);
} catch (Exception e) {
System.out.println("Error: " + e.getMessage());
} finally {
System.out.println("Finally: audit log written");
}
System.out.println("\n=== With exception ===");
try {
int result = 100 / 0;
System.out.println("Never reached: " + result);
} catch (ArithmeticException e) {
System.out.println("Caught: " + e.getMessage());
} finally {
System.out.println("Finally: cleanup runs after exception too");
}
}
}
=== No exception ===
Balance: Rs.75000.00
Finally: audit log written
=== With exception ===
Caught: / by zero
Finally: cleanup runs after exception too
Does finally run when try contains a return statement?
Yes. The finally block executes even when the try or catch block contains a return statement. The finally block runs before the return value is actually delivered to the caller.
package com.bank.demo;
import java.math.BigDecimal;
public class FinallyWithReturnDemo {
public static BigDecimal getBalance(String accountId) {
try {
System.out.println("Try: fetching balance for " + accountId);
return new BigDecimal("50000.00");
} catch (Exception e) {
System.out.println("Catch: error occurred");
return BigDecimal.ZERO;
} finally {
System.out.println("Finally: runs BEFORE return value is sent to caller");
}
}
public static void main(String[] args) {
BigDecimal balance = getBalance("SAV2001");
System.out.println("Returned balance: Rs." + balance);
}
}
Try: fetching balance for SAV2001
Finally: runs BEFORE return value is sent to caller
Returned balance: Rs.50000.00
When does finally NOT execute?
There are four situations where the finally block does not run.
package com.bank.demo;
public class FinallyNotExecuteDemo {
// 1. System.exit() shuts down the JVM
public static void systemExitExample() {
try {
System.out.println("Before System.exit()");
// System.exit(0); // Uncomment to see: finally does NOT run
System.out.println("(System.exit commented out for demo)");
} finally {
System.out.println("Finally: would NOT run after System.exit()");
}
}
// 2. Runtime.halt() is even more abrupt than System.exit()
// Runtime.getRuntime().halt(1) skips shutdown hooks AND finally
// 3. JVM crash (SIGKILL, native error) kills the process
// Nothing runs. The process is dead.
// 4. Infinite loop in try prevents reaching finally
public static void infiniteLoopExample() {
try {
// while (true) { } // Infinite loop, never reaches finally
System.out.println("(Infinite loop commented out for demo)");
} finally {
System.out.println("Finally: unreachable if loop is infinite");
}
}
public static void main(String[] args) {
systemExitExample();
System.out.println();
infiniteLoopExample();
}
}
Before System.exit()
(System.exit commented out for demo)
Finally: would NOT run after System.exit()
(Infinite loop commented out for demo)
Finally: unreachable if loop is infinite
Why is returning from finally dangerous?
A return statement in finally overrides the return value from try or catch. Worse, if try threw an exception, a return in finally swallows the exception entirely.
package com.bank.demo;
public class FinallyReturnDangerDemo {
// WRONG: finally return overrides try return
public static int wrongReturn() {
try {
return 1; // try says return 1
} finally {
return 2; // finally overrides: caller gets 2
}
}
// WRONG: finally return swallows exception
public static String wrongSwallow() {
try {
throw new RuntimeException("CRITICAL ERROR");
} finally {
return "everything is fine"; // exception is GONE
}
}
// CORRECT: never return from finally
public static int correctApproach() {
int result = 0;
try {
result = 1;
return result;
} finally {
// Cleanup only, no return
System.out.println("Cleanup complete, returning " + result);
}
}
public static void main(String[] args) {
System.out.println("Wrong return: " + wrongReturn());
System.out.println("Wrong swallow: " + wrongSwallow());
System.out.println("Correct: " + correctApproach());
}
}
Wrong return: 2
Wrong swallow: everything is fine
Cleanup complete, returning 1
Correct: 1
How is try-with-resources better than finally?
try-with-resources (Java 7+) automatically closes any resource that implements AutoCloseable. It eliminates the verbose null-check pattern and correctly handles the dual-exception scenario using suppressed exceptions.
package com.bank.demo;
import java.io.BufferedReader;
import java.io.StringReader;
public class FinallyVsTryWithResources {
// OLD: verbose finally pattern
public static void oldPattern() {
BufferedReader reader = null;
try {
reader = new BufferedReader(new StringReader("SAV2001,50000"));
System.out.println("Old: " + reader.readLine());
} catch (Exception e) {
System.out.println("Error: " + e.getMessage());
} finally {
if (reader != null) {
try {
reader.close();
} catch (Exception e) {
System.out.println("Close failed: " + e.getMessage());
}
}
}
}
// NEW: clean try-with-resources
public static void newPattern() {
try (BufferedReader reader = new BufferedReader(new StringReader("CUR3001,75000"))) {
System.out.println("New: " + reader.readLine());
} catch (Exception e) {
System.out.println("Error: " + e.getMessage());
}
// reader.close() called automatically. No finally needed.
}
public static void main(String[] args) {
oldPattern();
newPattern();
}
}
Old: SAV2001,50000
New: CUR3001,75000
What is the execution order of try-catch-finally?
The execution order follows a predictable pattern: try body first, then catch if an exception occurred, then finally regardless, then the return value is delivered to the caller.
package com.bank.demo;
public class ExecutionOrderDemo {
public static String traceOrder(boolean shouldThrow) {
StringBuilder trace = new StringBuilder();
try {
trace.append("try ");
if (shouldThrow) throw new RuntimeException("error");
trace.append("success ");
return trace.append("return-try").toString();
} catch (RuntimeException e) {
trace.append("catch ");
return trace.append("return-catch").toString();
} finally {
trace.append(" finally");
// Do NOT return here. The return from try/catch is preserved.
}
}
public static void main(String[] args) {
System.out.println("No exception: " + traceOrder(false));
System.out.println("With exception: " + traceOrder(true));
}
}
No exception: try success return-try finally
With exception: try catch return-catch finally
What should a senior engineer know?
- try-finally without catch is valid.
try { ... } finally { ... }compiles. Use when you need cleanup but want the exception to propagate uncaught. - System.exit in production. Never call
System.exit()in application code. Use Spring's graceful shutdown or container lifecycle management instead. - Daemon threads and finally. If all non-daemon threads finish, the JVM exits without running finally blocks in daemon threads.
- Thread interruption. If a thread is interrupted during a sleep/wait in the try block, the InterruptedException is thrown and finally still runs normally.
What are common interview follow-up questions?
- Can you have try-finally without catch? (Yes, it is valid.)
- Does finally run if catch re-throws? (Yes.)
- Does finally run if there is a return in try? (Yes, before the return completes.)
- What if both try and finally return? (finally's return wins.)
- What if both try and finally throw? (finally's throw wins and try's exception is lost.)
Visual Overview
┌──────────────────────┐
│ try block │
│ risky code here │
└───────┬───────┬───────┘
│ │
success exception
│ │
│ ┌────▼────────────┐
│ │ catch block │
│ │ handle error │
│ └────┬────────────┘
│ │
└───┬───┘
│
┌──────▼──────────────┐
│ finally block │
│ ALWAYS runs │
│ cleanup resources │
└──────┬──────────────┘
│
▼
return to caller
Does NOT run:
┌─────────────────────────┐
│ System.exit() │
│ JVM crash (kill -9) │
│ Runtime.halt() │
│ Infinite loop in try │
└─────────────────────────┘
Key Takeaway
The finally block is Java's guarantee that cleanup code will run after try and catch, regardless of whether an exception occurred. It executes when try succeeds, when try throws and catch handles it, when catch re-throws, and even when try or catch contains a return statement. The execution order is: try, then catch (if exception), then finally, then the return value is delivered.
Finally does not run in four situations: System.exit() terminates the JVM, a JVM crash (SIGKILL, native error) kills the process, Runtime.halt() stops everything immediately, and an infinite loop or deadlock in the try block prevents reaching finally. In production at National Bank of India, Pankaj never uses System.exit() in application code because it bypasses finally blocks and can leave database connections open.
The most important rule is: never return or throw from a finally block. A return in finally overrides the try/catch return value, and a throw in finally swallows the try's exception entirely. The modern replacement for finally is try-with-resources, which auto-closes resources and correctly handles dual exceptions using suppressed exceptions.
Remember one sentence: finally always runs after try-catch for guaranteed cleanup, except when the JVM itself is shut down or dead.
---