Skip to content
Java exceptions 5 min read

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 finally block without a catch is 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 catchfinally.

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 finally block suppresses any exception that was in flight. This makes debugging very hard. Avoid return, break, or continue inside finally unless you have a very specific reason.

When finally Does NOT Execute

There are exactly two situations where finally is skipped:

  1. System.exit() is called — the JVM shuts down immediately.
  2. 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 try also prevents finally from running — but that’s because the try block never finishes, not because finally is 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 finally block throw unchecked exceptions. If your cleanup code can fail, wrap it in its own try-catch inside finally.

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 finally cleanup whenever you’re working with Closeable or AutoCloseable resources. 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

Scenariotrycatchfinally
No exceptionRuns fullySkippedRuns
Exception thrown, caughtRuns until throwRunsRuns
Exception thrown, not caughtRuns until throwSkippedRuns, then exception propagates
System.exit() calledRuns until exitSkipped

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.

Last updated June 13, 2026
Was this helpful?