Resource cleanup with try/finally
Student can produce the classic try/catch/finally resource-cleanup pattern (variable declared outside, initialized to null, assigned inside try, closed inside finally with null-guard), identify the cleanup-exception-masking problem, and explain when try-with-resources is the right replacement.
The pre-Java-7 idiom for guaranteeing a resource (file handle, network socket, database connection) is closed regardless of exceptions:
Scanner sc = null;
try {
sc = new Scanner(new File(name));
while (sc.hasNextLine()) {
// process line: may throw
}
} catch (FileNotFoundException e) {
System.out.println("Cannot open: " + e.getMessage());
} finally {
if (sc != null) {
sc.close();
}
}
Four structural commitments:
1. **The resource variable is declared outside the try. Declaring inside (Scanner sc = new Scanner(...)) would scope sc to the try body, making it invisible in finally. The outside-declaration form is the canonical workaround. 2. The variable is initialized to null. If new Scanner(...) throws, the variable stays at null: the finally block's if (sc != null) guard prevents NPE from calling close() on the unassigned reference. 3. The assignment is inside the try. Only this line is what might throw; the surrounding code (declaration above, cleanup below) cannot. 4. The cleanup is inside finally, not in the catch or after the try.** Cleanup must run on every exit path. Putting it in catch only handles the FileNotFoundException case; putting it after the try-statement skips it entirely when an exception propagates.
Why this is the pre-Java-7 idiom
Java 7 added try-with-resources, which collapses all four of the above commitments into one syntactic form:
try (Scanner sc = new Scanner(new File(name))) {
while (sc.hasNextLine()) {
// process: may throw
}
} catch (FileNotFoundException e) {
System.out.println("Cannot open: " + e.getMessage());
}
The compiler generates the finally block, the null check, and the close() call automatically. The CSCD 210 lab style uses try-with-resources for every Scanner and PrintStream in modern code. The explicit form still matters for two reasons: (a) older code uses it, so you need to recognize it on sight; (b) knowing what it does makes the shortcut readable.
The hidden cost of nested resources
If two resources are opened in the same try, the explicit try/finally shape gets ugly:
Scanner sc = null;
PrintStream out = null;
try {
sc = new Scanner(new File(in));
out = new PrintStream(new File(out));
// ... process ...
} catch (FileNotFoundException e) {
// ...
} finally {
if (out != null) out.close();
if (sc != null) sc.close();
}
The two null checks, the order of closes (reverse of opens: sometimes important), and the verbose declarations multiply. Try-with-resources handles multiple resources cleanly with comma-separated declarations.
The "swallowed cleanup exception" problem
If both the try body and the close() call throw, the cleanup's exception masks the original. The pattern:
try {
sc = new Scanner(new File(name));
sc.nextInt(); // throws InputMismatchException
} finally {
sc.close(); // throws IllegalStateException (Scanner already corrupted)
}
// Caller sees the IllegalStateException; the InputMismatchException is lost.
This is one of the canonical reasons Bloch's Effective Java Item 9 recommends try-with-resources: it preserves the original exception and adds the cleanup's exception as a suppressed exception (visible via e.getSuppressed()), keeping both available for diagnosis.
In other languages
- C++: RAII: destructors run at scope exit automatically. The try/finally pattern is unnecessary.
- Python:
try: ... finally: f.close()is the explicit form;with open(...) as f:is the try-with-resources equivalent. - Go:
defer f.Close()schedules the close at function return, regardless of exit path. - Rust:
droptraits run at scope exit (similar to C++ RAII).
The finally block runs on every exit path
Student can predict whether the finally block executes given the try body's outcome (normal, caught exception, uncaught exception, return), and identify the design intent ("cleanup regardless of how we exit") that justifies adding a finally block.
A try statement may have one optional finally block. Its body runs regardless of how the try exits: normal completion, an exception that a catch handles, or an exception that propagates past all the catches.
try {
final Scanner sc = new Scanner(new File(name));
final int n = sc.nextInt();
sc.close();
} catch (FileNotFoundException e) {
System.out.println("Could not open: " + e.getMessage());
} finally {
System.out.println("This always prints.");
}
Five exit paths from a try:
1. Normal completion of the try body: finally runs, then control continues past the whole try statement. 2. An exception inside the try body, caught by a matching catch: catch runs, then finally runs, then control continues past the whole try statement. 3. An exception inside the try body with no matching catch: finally runs, then the exception continues to propagate. 4. A return inside the try body: finally runs before the return takes effect; the return then completes. 5. A return inside a catch body: same: finally runs before the return completes.
In all five cases, finally runs. This is the guarantee that makes finally the right place for cleanup code that must happen regardless of success.
What goes inside finally
The canonical use is resource cleanup: closing a Scanner, releasing a database connection, returning a borrowed object to a pool. The resource-cleanup pattern covers this in detail. The motivation: if an exception fires mid-read, the Scanner is still open, the OS still holds the file descriptor, and without finally, the cleanup is skipped.
Scanner sc = null;
try {
sc = new Scanner(new File(name));
while (sc.hasNextLine()) {
// process: may throw
}
} catch (FileNotFoundException e) {
// ...
} finally {
if (sc != null) {
sc.close(); // runs whether the loop completed or exploded
}
}
The if (sc != null) guard handles the case where the constructor threw: sc remains null, and close() on null would throw a fresh NPE inside the cleanup.
Why finally exists when try-with-resources exists
Java 7 added try-with-resources, which folds the open-and-close pattern into the syntax. For 80% of CSCD 210 cleanup cases, try-with-resources is the right tool. finally is still valuable when:
- The cleanup is not a simple
close()call (e.g., resetting a global state). - The resource does not implement
AutoCloseable. - The cleanup logic depends on whether an exception occurred (rare).
The CSCD 210 lab style favors try-with-resources for files; teaches finally first so the concept of guaranteed cleanup is visible before the syntactic shortcut.
try without catch but with finally
A try/finally block without any catch is legal:
try {
// ... may throw a checked exception ...
} finally {
// ... cleanup ...
}
This shape lets the exception propagate (the caller handles it) while guaranteeing cleanup runs. It is the "I want to clean up but not handle" pattern, common when the body is best understood as side-effect free.
In other languages
- Python:
try: ... finally: ...: identical semantics;with(the try-with-resources analog) is also available. - C++: no
finally; RAII (destructors at scope exit) does the job. - JavaScript:
try { ... } finally { ... }: same. - Go:
deferkeyword schedules cleanup to run when the surrounding function returns; same intent, different syntax.