SwadhyAI Notes

All Java notes

Java Finally Block

›Short versionOpen if you only have a minute.
  • finally runs on the way out of try: after a normal finish, after a catch, and before an uncaught exception leaves the method.
  • A return in try evaluates its value, holds it, runs finally, and then returns the held value if finally finishes normally.
  • Changing a local primitive in finally does not change that held value. Mutating an object through a returned reference does, because the caller shares the object.
  • A return or throw inside finally replaces whatever try was about to do. A pending exception is discarded, and it is not kept as a cause or a suppressed exception.
  • finally does not run if the JVM exits inside the try, or if the process is killed. A finally that never finishes holds the pending return hostage.

is the block Java promises to visit before a try statement is over. The subtle part is what "over" means when try was already leaving, by a return or by a throw. The value and the exception wait. finally runs. Then the thing that was waiting proceeds, unless finally leaves on its own.

It runs on every exit that stays inside the JVM

With no exception, the body runs, then finally, then whatever follows the whole statement. With a matching catch, the catch runs, then finally. With no matching catch, finally still runs, and only then does the exception propagate. A return inside try or catch is delayed the same way.

java
static void divide() {
    try {
        System.out.println("try");
        int result = 10 / 0;
        System.out.println(result);
    } finally {
        System.out.println("finally");
    }
}

The print of result never happens. finally prints, and then ArithmeticException leaves divide. that must happen whether the division worked or not belongs here: unlock, restore a flag, release what the try acquired. A finally that chooses a discount or writes a second result is a second path hidden under a mechanism people read as release.

Two exits skip it. System.exit stops the JVM, so the statement never finishes its protocol. Killing the process, or a crash of the virtual machine, has the same effect: there is no thread left to run the block. A finally that loops forever, or blocks on a call that never returns, does run, and the pending return or exception waits until that block completes. In practice the method never ends.

The return waits

A is a value already computed. In this method the operand is the literal 10. Java holds 10, prints from finally, and then returns 10. The caller never sees the method leave at the return line. It sees the method leave after finally.

java
static int held() {
    try {
        System.out.println("try returns 10");
        return 10;
    } finally {
        System.out.println("finally still runs");
    }
}

If the operand is a local variable, it is read before finally. Assigning a new value to that variable afterward changes the variable, not the value already held.

java
static int primitive() {
    int n = 1;
    try {
        return n;
    } finally {
        n = 2;
    }
}

primitive returns 1. The expression n was evaluated to 1, and that 1 was set aside. n = 2 stores into the local, which is about to disappear with the frame.

A returned reference is different, because the held value is the reference and the object lives on the heap. finally still has that reference. Adding to the list mutates the object the caller is about to receive.

java
import java.util.ArrayList;
import java.util.List;

static List<String> names() {
    List<String> result = new ArrayList<>();
    try {
        result.add("Ada");
        return result;
    } finally {
        result.add("Grace");
    }
}

The caller receives a list that contains both names. Nothing in finally replaced the return. It changed the object the return had already chosen. If the cleanup is "do not show a partial list," build the list and return it after the risky work, or copy it before the return. Do not rely on the reader to notice a mutation under a return.

When finally leaves too

try and finally can each complete abruptly. An from finally wins. A return in finally becomes the method's return. The value try had set aside is dropped.

java
static int overridden() {
    try {
        return 1;
    } finally {
        return 2;
    }
}

Callers of overridden receive 2. The return in finally is legal, and it is the value the method actually returns. javac can warn, when the finally lint is enabled, that the clause cannot complete normally. The same replacement swallows a throw. The exception object is a . It is not stored as a cause, and it is not added as a suppressed exception. The caller sees a normal return of 0.

java
static int swallowed() {
    try {
        throw new IllegalStateException("from try");
    } finally {
        return 0;
    }
}

A throw inside finally does the twin of that. The new exception propagates. The one from try is gone, and the stack trace the caller reads is the throw site inside finally. This is the bug try-with-resources is built to avoid. Closing a resource by hand in finally, and letting close throw, erases the exception that made you close it. The later page keeps the original and attaches the close failure beside it.

try returns a StringBuilder after appending one word. finally appends a second word, and also contains return of a different string. What does the caller receive, and does the caller see the second word?

The caller receives the string returned from finally. That return discards the pending return of the builder. The second append still runs, because it is before the return inside finally, but the builder is no longer what the method returns. If finally only appended and did not return, the caller would receive the same builder and would see both words. The return in finally is what changes the result. The append only changes the object.