☕ Java
finally
The finally block contains code that must execute regardless of whether the try block completed normally, threw an exception that was caught, or threw an exception that was not caught. It is the mechanism for guaranteed cleanup — closing files, releasing locks, returning connections to a pool, rolling back transactions. The finally block runs in all scenarios except two: if the JVM exits (System.exit()) or if the thread is killed by an Error like StackOverflowError. Understanding when exactly finally runs, the interaction between finally and return statements, exception suppression when finally itself throws, and the modern alternative of try-with-resources is essential for writing correct resource management code in Java.
finally Semantics — When It Runs and Why
The finally block executes after the try block and after any matching catch block, in every case: when the try block completes normally, when an exception is caught by a catch block, when an exception is thrown but not caught, when a return statement executes in the try block, and when a return statement executes in a catch block. The only guarantees against finally running are System.exit(), Runtime.halt(), or the thread being forcibly killed by an OutOfMemoryError or StackOverflowError that the JVM cannot recover from.
The "finally always runs" guarantee is what makes it suitable for cleanup code. A database connection opened at the start of a method must be closed at the end, regardless of what happened in between — exceptions thrown, early returns, normal completion. Without finally, every possible exit path (every return statement, every potentially throwing call) would require its own cleanup call, which is both verbose and error-prone — it is easy to miss an exit path.
The typical pattern places resource acquisition before the try block and resource release in the finally block. This pattern ensures that the resource is released exactly once regardless of what the try block does, as long as acquisition succeeded. If acquisition itself throws an exception, the finally block is not entered (there is no try block to guard), but the resource was never acquired so there is nothing to release.
Java
// ── finally runs in all cases ────────────────────────────────────────
public void demonstrateFinally(boolean throwException) {
System.out.println("Before try");
try {
System.out.println("In try");
if (throwException) {
throw new RuntimeException("test");
}
System.out.println("End of try (no exception)");
} catch (RuntimeException e) {
System.out.println("In catch: " + e.getMessage());
} finally {
System.out.println("In finally"); // ALWAYS runs
}
System.out.println("After try-catch-finally");
}
// demonstrateFinally(false) output:
// Before try
// In try
// End of try (no exception)
// In finally
// After try-catch-finally
// demonstrateFinally(true) output:
// Before try
// In try
// In catch: test
// In finally
// After try-catch-finally
// ── finally when exception is NOT caught ─────────────────────────────
public void withUncaughtException() {
try {
throw new IOException("uncaught");
// No catch block for IOException
} finally {
System.out.println("Finally runs even without catch");
// After finally, IOException propagates up the call stack
}
}
// ── Resource acquisition before try, release in finally ───────────────
Connection conn = null;
try {
conn = dataSource.getConnection(); // acquire BEFORE try
conn.setAutoCommit(false);
// ... business logic ...
conn.commit();
} catch (SQLException e) {
if (conn != null) {
try { conn.rollback(); } catch (SQLException ex) { /* ignore */ }
}
throw new DataException("Operation failed", e);
} finally {
if (conn != null) {
try { conn.close(); } // ALWAYS release
catch (SQLException e) { log.warn("Failed to close connection", e); }
}
}finally and return — A Subtle Interaction
When a return statement appears inside a try or catch block, the finally block executes before the method actually returns. The return value is computed and stored, the finally block runs, and then the return happens. This means the finally block can observe (and in some cases interfere with) the return.
If the finally block contains a return statement, it overrides any return statement in the try or catch block. The method returns the finally block's value, and the try or catch block's return value is silently discarded. This is almost always unintentional and is a significant source of bugs. The compiler does not warn about this in Java, but static analysis tools like FindBugs, SpotBugs, and SonarQube flag it.
Similarly, if an exception is propagating out of the try or catch block (either uncaught or being rethrown), and the finally block executes a return statement, the exception is silently swallowed. The return suppresses the propagating exception without any indication. This is perhaps the most dangerous interaction between finally and control flow, and it explains why finally blocks should generally contain only cleanup code with no return statements.
Java
// ── finally executes before return ───────────────────────────────────
public int returnFromTry() {
try {
System.out.println("try block");
return 1; // return value computed, stored...
} finally {
System.out.println("finally block"); // ...finally runs first...
}
// ...then method returns 1
}
// Output: "try block", "finally block"
// Returns: 1
// ── return in finally OVERRIDES return in try — dangerous ─────────────
public int dangerousReturn() {
try {
return 1; // return 1 is computed and staged
} finally {
return 2; // OVERRIDES — method returns 2, not 1!
}
}
System.out.println(dangerousReturn()); // 2 — not 1!
// ── return in finally SUPPRESSES exceptions — extremely dangerous ──────
public String suppressingFinally() {
try {
throw new RuntimeException("important error");
} finally {
return "value"; // exception is SILENTLY DISCARDED — catastrophic!
}
}
// No exception propagates — the RuntimeException is completely lost
// Returns "value" as if nothing went wrong
// ── Best practice: no return in finally ──────────────────────────────
// WRONG:
public int wrongFinally() {
try {
return computeResult();
} finally {
cleanup();
return DEFAULT; // BAD — hides exceptions, overrides real result
}
}
// CORRECT: cleanup only, no return
public int correctFinally() {
try {
return computeResult();
} finally {
cleanup(); // cleanup only — no return, no throw (if possible)
}
}try-with-resources — The Modern Alternative
Try-with-resources, introduced in Java 7, is the recommended replacement for the try-finally pattern for resource management. Any object implementing AutoCloseable (which Closeable extends) can be declared in the try statement's resource list. The JVM automatically calls close() on each resource when the try block exits — whether normally or exceptionally — in the reverse order of their declaration.
Try-with-resources is strictly superior to manual finally blocks for resource management in three ways: it is less code, it handles the exception suppression problem correctly, and it closes resources in reverse acquisition order (important for nested resources that depend on each other).
The exception suppression problem arises when both the try block and the finally block throw exceptions. With a manual finally block, the exception from finally replaces the exception from the try body — the original exception is lost. Try-with-resources handles this correctly using exception suppression: if close() throws an exception while another exception is already propagating, the close() exception is added to the original exception's suppressed exception list using addSuppressed(). Both exceptions are preserved; neither is lost. The suppressed exceptions are accessible via getSuppressed().
Java
// ── Manual finally — verbose and has suppression problem ─────────────
// This is what try-with-resources replaces:
InputStream in = null;
OutputStream out = null;
try {
in = new FileInputStream("input.txt");
out = new FileOutputStream("output.txt");
byte[] buf = new byte[8192];
int n;
while ((n = in.read(buf)) != -1) out.write(buf, 0, n);
} finally {
// Reversed order of acquisition:
if (out != null) try { out.close(); } catch (IOException e) { /* suppress */ }
if (in != null) try { in.close(); } catch (IOException e) { /* suppress */ }
}
// 12 lines of boilerplate for resource management
// ── try-with-resources — automatic, correct, concise ─────────────────
try (InputStream in = new FileInputStream("input.txt");
OutputStream out = new FileOutputStream("output.txt")) {
byte[] buf = new byte[8192];
int n;
while ((n = in.read(buf)) != -1) out.write(buf, 0, n);
}
// JVM automatically calls: out.close(), then in.close() (reverse order)
// Even if the body throws an exception
// ── Exception suppression — how try-with-resources handles dual throws ─
class BrokenResource implements AutoCloseable {
@Override
public void close() throws Exception {
throw new Exception("close() threw");
}
}
try (BrokenResource r = new BrokenResource()) {
throw new RuntimeException("body threw");
// body throws RuntimeException
// JVM calls r.close(), which throws Exception
// try-with-resources adds close() exception as SUPPRESSED:
// RuntimeException is propagated
// Exception from close() is r.getSuppressed()[0]
} catch (RuntimeException e) {
System.out.println("Primary: " + e.getMessage()); // body threw
System.out.println("Suppressed:" + e.getSuppressed()[0].getMessage()); // close() threw
// BOTH exceptions preserved — neither lost
}
// ── Custom AutoCloseable ──────────────────────────────────────────────
public class DatabaseTransaction implements AutoCloseable {
private final Connection conn;
private boolean committed = false;
public DatabaseTransaction(Connection conn) throws SQLException {
this.conn = conn;
conn.setAutoCommit(false);
}
public void commit() throws SQLException {
conn.commit();
committed = true;
}
@Override
public void close() throws SQLException {
if (!committed) conn.rollback(); // rollback if not committed
conn.setAutoCommit(true);
conn.close();
}
}
// Usage — automatic rollback on exception:
try (DatabaseTransaction tx = new DatabaseTransaction(
dataSource.getConnection())) {
orderRepo.save(order);
paymentRepo.record(payment);
tx.commit();
} // auto-close: rolls back if exception, nothing if committedRelated Topics in Exception Handling
Exception Basics
An exception is an event that disrupts the normal flow of a program during execution. Java's exception system is a structured mechanism for signalling, propagating, and handling error conditions — a significant improvement over the older approach of returning special error codes that callers could silently ignore. Understanding exceptions means understanding the class hierarchy that categorises them, the distinction between checked and unchecked exceptions, what the JVM does when an exception is thrown, how the call stack is unwound, and what information an exception object carries. This entry covers the full exception hierarchy, the checked vs unchecked distinction and the reasoning behind it, exception propagation through the call stack, and the information model of exception objects.
try-catch
The try-catch statement is Java's primary mechanism for handling exceptions. Code that might throw an exception is placed in the try block; one or more catch blocks follow, each specifying the exception type to handle and providing the handling logic. When an exception is thrown inside the try block, execution of the try block immediately stops and the JVM searches the catch blocks in order for the first one whose type matches the thrown exception. Understanding the control flow precisely — what runs, what stops, and in what order — is essential for writing correct exception handling code. This entry covers try-catch control flow, exception type matching, catching by supertype, variable scope, and the key principles for writing good catch blocks.
Multiple catch
A single try block can be followed by multiple catch blocks, each handling a different exception type. Multiple catch blocks allow code to respond differently to different kinds of failures — recovering from an expected missing file, but reporting and rethrowing an unexpected database error. Java 7 introduced multi-catch syntax, allowing a single catch block to handle multiple unrelated exception types, eliminating the duplicate code that arises when different exceptions require the same handling. This entry covers the ordering rules, multi-catch syntax, the type of the caught variable in multi-catch, and the design patterns for structuring multiple exception handlers.
throw
The throw statement explicitly throws an exception object, immediately terminating normal execution and initiating exception propagation. throw is used to signal that a method cannot fulfil its contract because a precondition is violated, an invalid state has been detected, or an operation has failed. Understanding when to throw, what to throw, how to create informative exception objects, and how throwing interacts with the call stack is the foundation of using exceptions as a design tool rather than just an error mechanism. This entry covers the throw statement mechanics, throwing at the right abstraction level, creating informative exception messages, exception chaining, and the principles for deciding when to throw versus returning a special value.