finally Block
The finally block is Java’s guarantee that certain code will run no matter what — whether an exception is thrown, caught, or not thrown at all. It’s the right place to put cleanup logic like closing files, releasing database connections, or resetting state.
Why finally Exists
When you open a resource (a file, a network socket, a database connection), you must close it eventually. But exceptions can interrupt normal flow at any point. Without finally, you’d need duplicate cleanup code in every possible code path. finally solves this cleanly: it runs after the try block (and any matching catch block) finishes, no matter which path was taken.
public class FinallyDemo {
public static void main(String[] args) {
try {
System.out.println("Opening resource...");
int result = 10 / 2;
System.out.println("Result: " + result);
} catch (ArithmeticException e) {
System.out.println("Caught: " + e.getMessage());
} finally {
System.out.println("Closing resource (always runs).");
}
}
}
Output:
Opening resource...
Result: 5
Closing resource (always runs).
No exception was thrown, yet finally still ran.
Syntax
A finally block always attaches to a try. You can combine it with catch, or use try + finally without any catch at all:
// With catch
try { ... }
catch (Exception e) { ... }
finally { ... }
// Without catch — let the exception propagate, but still clean up
try { ... }
finally { ... }
Note: A
finallyblock without acatchis valid and sometimes intentional — you want the exception to propagate to the caller while still guaranteeing cleanup in the current method.
finally Runs Even When an Exception Is Thrown
public class FinallyOnException {
public static void main(String[] args) {
try {
System.out.println("Before exception");
int result = 10 / 0; // ArithmeticException
System.out.println("After exception"); // never reached
} catch (ArithmeticException e) {
System.out.println("Caught: " + e.getMessage());
} finally {
System.out.println("finally always executes.");
}
}
}
Output:
Before exception
Caught: / by zero
finally always executes.
Execution order is: try body → exception thrown → matching catch → finally.
finally With a return Statement
Here’s one of the trickiest corners of finally. If both the try/catch and the finally block contain a return statement, the finally return wins — it silently discards the original return value.
public class FinallyReturn {
static int compute() {
try {
return 1; // would return 1...
} finally {
return 2; // ...but this overrides it
}
}
public static void main(String[] args) {
System.out.println(compute()); // prints 2
}
}
Output:
2
Warning: Returning from a
finallyblock suppresses any exception that was in flight. This makes debugging very hard. Avoidreturn,break, orcontinueinsidefinallyunless you have a very specific reason.
When finally Does NOT Execute
There are exactly two situations where finally is skipped:
System.exit()is called — the JVM shuts down immediately.- The JVM crashes or is killed — for example, by an OS signal or
Runtime.getRuntime().halt().
public class FinallySkipped {
public static void main(String[] args) {
try {
System.out.println("Before exit");
System.exit(0); // JVM shuts down here
} finally {
System.out.println("This never prints.");
}
}
}
Output:
Before exit
Note: An infinite loop inside
tryalso preventsfinallyfrom running — but that’s because thetryblock never finishes, not becausefinallyis skipped.
Exception in finally Itself
If the finally block throws its own exception, it replaces any exception that was already in flight from the try block. The original exception is lost.
public class FinallyException {
public static void main(String[] args) {
try {
throw new RuntimeException("Original");
} finally {
throw new RuntimeException("From finally"); // replaces "Original"
}
}
}
Running this prints a stack trace for "From finally" — the "Original" exception is silently swallowed.
Warning: Never let a
finallyblock throw unchecked exceptions. If your cleanup code can fail, wrap it in its owntry-catchinsidefinally.
finally {
try {
connection.close();
} catch (SQLException e) {
System.err.println("Failed to close connection: " + e.getMessage());
}
}
try-with-resources: The Modern Alternative
Since Java 7, exception handling has a better pattern for resource management: try-with-resources. Any class implementing AutoCloseable is automatically closed at the end of the try block, even if an exception occurs — and it handles the “exception in close” case gracefully by suppressing it (you can retrieve suppressed exceptions via Throwable.getSuppressed()).
import java.io.*;
public class TryWithResources {
public static void main(String[] args) {
try (BufferedReader reader = new BufferedReader(new FileReader("data.txt"))) {
System.out.println(reader.readLine());
} catch (IOException e) {
System.out.println("Error: " + e.getMessage());
}
// reader.close() is called automatically — no finally needed
}
}
Tip: Prefer try-with-resources over manual
finallycleanup whenever you’re working withCloseableorAutoCloseableresources. It’s less code and handles suppressed exceptions properly.
finally is still useful for non-resource cleanup: resetting flags, decrementing counters, logging, or anything not tied to AutoCloseable.
Execution Order Summary
| Scenario | try | catch | finally |
|---|---|---|---|
| No exception | Runs fully | Skipped | Runs |
| Exception thrown, caught | Runs until throw | Runs | Runs |
| Exception thrown, not caught | Runs until throw | Skipped | Runs, then exception propagates |
System.exit() called | Runs until exit | — | Skipped |
Under the Hood
At the bytecode level, finally is implemented by duplicating the finally block at every possible exit point from the try region. The Java compiler generates a separate copy of the finally bytecode for the normal path, for each catch path, and for uncaught exceptions. Early JVMs used a jsr/ret subroutine mechanism, but modern compilers (since Java 6) inline the copy at each exit point instead — this is simpler for the JIT to optimize.
The JIT compiler can often hoist finally cleanup entirely out of the exception table and inline it directly. For the common case (no exception thrown), this means zero overhead: the finally body runs sequentially just like ordinary code after the try.
If you inspect class files with javap -c, you’ll see an exception table at the end of each method. Each row maps a bytecode range to a handler address. The finally block appears as a catch-all handler (catch type any) covering the entire try range, which is how the JVM ensures it always executes.
Related Topics
- try-catch Block — the foundation that finally attaches to
- Multiple catch Blocks — handle different exception types before finally runs
- throw Keyword — manually trigger exceptions that finally will still intercept
- throws Keyword — declare propagated exceptions; finally runs before propagation
- Custom Exceptions — build your own exception hierarchy to use with try-catch-finally
- final vs finally vs finalize — three similar-sounding keywords with very different purposes