How finally can silently change your answer
A return in finally discards the real result - including an exception.
Open this lesson in the learning hubKey points
- The return value is computed before finally runs. A finally block that mutates a local variable therefore does not change what is returned, because the value was already taken.
- But a
returninside finally replaces the pending result entirely - and if an exception was propagating, it is discarded without trace. - That is one of the few ways to lose an exception completely: no stack trace, no log, no evidence it ever happened.
- A
breakorcontinueinside finally does the same thing, which is why most style checkers forbid all three outright. - The mutation rule differs for objects: reassigning the variable in finally has no effect, but mutating the object it points to does, because the reference was captured, not the contents.
- Try-with-resources avoids the whole area. It closes resources without a finally block, and it records the close failure as a suppressed exception rather than replacing the original.
Example
import java.util.ArrayList;
import java.util.List;
public class FinallySemantics {
// The value is captured BEFORE finally runs, so this returns 1.
static int mutateLocal() {
int x = 1;
try {
return x; // the VALUE 1 is taken here
} finally {
x = 2; // too late - the return value is already 1
}
}
// A return in finally REPLACES the pending result.
static int returnInFinally() {
try {
return 1;
} finally {
return 2; // wins - 1 is discarded
}
}
// And it discards a propagating exception entirely.
static int swallowsException() {
try {
throw new IllegalStateException("this is lost forever");
} finally {
return -1; // the exception vanishes; no trace at all
}
}
// Reassignment does nothing, but MUTATION is visible.
static List<String> mutateObject() {
List<String> items = new ArrayList<>();
try {
items.add("a");
return items; // the REFERENCE is captured
} finally {
items.add("b"); // same object -> the caller sees this
}
}
public static void main(String[] args) {
System.out.println("mutateLocal() = " + mutateLocal());
System.out.println("returnInFinally() = " + returnInFinally());
System.out.println("swallowsException()= " + swallowsException());
System.out.println("mutateObject() = " + mutateObject());
// try-with-resources RECORDS the close failure instead of losing it.
try {
try (AutoCloseable bad = () -> { throw new IllegalStateException("close failed"); }) {
throw new RuntimeException("body failed");
}
} catch (Exception e) {
System.out.println("primary = " + e.getMessage());
for (Throwable s : e.getSuppressed()) {
System.out.println("suppressed = " + s.getMessage());
}
}
}
}
Never return from finally - it discards the real result, and a propagating exception disappears without trace.
This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Core Java course, and every lesson in it is listed on the Core Java contents page.