Try-with-resources: the parenthesized resource list
Student can produce a try-with-resources statement that opens a single resource (Scanner, PrintStream, etc.), explain what the compiler generates (hidden finally with null-safety and suppressed-exception bookkeeping), and identify that the resource type must implement AutoCloseable (or Closeable).
Java 7's try-with-resources statement (JLS §14.20.3) attaches one or more resources to a try block. The compiler generates the cleanup code automatically:
try (Scanner sc = new Scanner(new File(name))) {
while (sc.hasNextLine()) {
// process
}
} catch (FileNotFoundException e) {
System.out.println("Cannot open: " + e.getMessage());
}
The parenthesized expression after try is the resource declaration. The compiler generates:
1. A hidden finally block that calls close() on every resource in reverse-declaration order. 2. Null-safety: if construction throws, the resource is never assigned, and the hidden cleanup skips it. 3. Suppressed-exception bookkeeping: if both the try body and a cleanup close() throw, the cleanup's exception is recorded on the original via addSuppressed(...). The caller still sees the original; the cleanup-failure is available via e.getSuppressed().
The AutoCloseable contract
Try-with-resources works only with types that implement java.lang.AutoCloseable. The interface declares one method:
public interface AutoCloseable {
void close() throws Exception;
}
Every JDK resource you would use in CSCD 210 implements it: Scanner, PrintStream, PrintWriter, FileInputStream, FileOutputStream, BufferedReader, BufferedWriter. (Beyond CSCD 210: JDBC's Connection/Statement/ResultSet and the network sockets in java.net all implement it too: the contract scales from text-file streams to database connections.) Trying to put a non-AutoCloseable type in the parens produces a compile error: "the resource type X does not implement java.lang.AutoCloseable."
The narrower interface java.io.Closeable extends AutoCloseable with the restriction that its close() declares throws IOException. Most I/O classes implement Closeable; either interface works with try-with-resources.
Reading the syntax aloud
The CSCD 210 narration: "try with sc as a Scanner of (new File of name): while (sc has next line) ...". The "with X as a resource" framing matches Python's with statement and tells the reader what to expect: cleanup happens at the end of the block.
What the generated code looks like
A simplified view of what the compiler produces:
// Original try-with-resources:
try (Scanner sc = new Scanner(new File(name))) {
body();
}
// What the compiler generates (approximately):
{
Scanner sc = new Scanner(new File(name));
Throwable primary = null;
try {
body();
} catch (Throwable t) {
primary = t;
throw t;
} finally {
if (sc != null) {
if (primary != null) {
try { sc.close(); }
catch (Throwable suppressed) { primary.addSuppressed(suppressed); }
} else {
sc.close();
}
}
}
}
The generated code is what the explicit try/finally pattern should have been if humans wrote it carefully every time, but did not, which is why the syntax exists.
In other languages
- Python:
with open(...) as f:: direct analog;f.close()is called at end-of-block, even on exceptions. - C++: RAII (resource acquisition is initialization): no syntax needed; destructors handle cleanup at scope exit.
- C#:
using (var f = new StreamReader(name)) { ... }: near-identical to Java's; same compiler-generated cleanup. - Go:
defer f.Close(): different mechanism (deferred function call); same effect.
Try-with-resources replaces try/finally for most cleanup
Student can refactor an explicit try/catch/finally resource-cleanup pattern into the equivalent try-with-resources form, identify the three (rare) cases where try/finally is still the right choice, and explain how suppressed exceptions preserve the primary failure cause.
For any cleanup that consists of calling close() on a resource, try-with-resources is the right tool. The verbose try/finally resource-cleanup pattern should be considered legacy code: Bloch's Effective Java Item 9 is unambiguous: "always use try-with-resources, never try-finally, when working with resources that must be closed."
Side-by-side comparison for a single resource:
// Pre-Java-7 (verbose, error-prone):
Scanner sc = null;
try {
sc = new Scanner(new File(name));
while (sc.hasNextLine()) {
process(sc.nextLine());
}
} catch (FileNotFoundException e) {
System.out.println("Cannot open: " + e.getMessage());
} finally {
if (sc != null) sc.close();
}
// Java 7+ (try-with-resources):
try (Scanner sc = new Scanner(new File(name))) {
while (sc.hasNextLine()) {
process(sc.nextLine());
}
} catch (FileNotFoundException e) {
System.out.println("Cannot open: " + e.getMessage());
}
Six lines turn into four. More importantly:
- No
nullinitial value to manage. - No
if (sc != null)guard. - No "did I remember to close?" cognitive load.
- Suppressed-exception bookkeeping handled (if the body throws and
close()throws, the caller sees the body's exception). - Multiple resources compose cleanly with a comma.
When finally is still the right tool
A small set of cases where the old try/finally is still appropriate:
1. Cleanup that is not a close() call. Restoring a thread's interrupt flag, releasing a ReentrantLock, undoing a temporary System.setProperty(...). The cleanup logic does not fit the AutoCloseable.close() interface. 2. The resource does not implement AutoCloseable. Older libraries sometimes have disconnect() or release() methods without the interface; these need explicit try/finally. 3. The cleanup logic depends on whether an exception occurred. A finally body that needs to check a flag set inside the try body and act differently. Try-with-resources's cleanup is unconditional.
CSCD 210 labs land in case 1 zero times, case 2 zero times, and case 3 zero times. Every CSCD 210 file-I/O lab uses try-with-resources.
Suppressed exceptions: the real win
The masking-cleanup-exception problem that finally carries disappears with try-with-resources. If the body throws IOException and the cleanup throws a second IOException, the caller sees the body's exception, and can access the cleanup's via e.getSuppressed():
try (Scanner sc = new Scanner(new File(name))) {
// ... throws InputMismatchException ...
}
// If sc.close() also throws, the InputMismatchException propagates.
// The close-failure is in InputMismatchException.getSuppressed().
This preserves the cause of the failure (the InputMismatchException is what the program needs to diagnose) while not losing the cleanup-also-failed signal. Bloch Effective Java Item 9 walks through this in detail.
In other languages
- Python:
with open(...) as f:: same idea; cleanup exception handling is similar but uses__exit__return value semantics. - C#:
using (var f = new ...) { ... }: direct analog. - JavaScript: no native equivalent;
try/finallyis the manual idiom.
Multiple resources in one try-with-resources
Student can produce a try-with-resources statement that manages two or more resources, identify that resources close in reverse declaration order, and decide when to split into separate try statements rather than combine.
The parenthesized resource list accepts more than one resource, separated by semicolons. Each declaration is its own AutoCloseable; the compiler generates close calls for all of them in reverse declaration order.
try (
Scanner sc = new Scanner(new File(inputFile));
PrintStream out = new PrintStream(new File(outputFile))
) {
while (sc.hasNextLine()) {
out.println(sc.nextLine().toUpperCase());
}
} catch (FileNotFoundException e) {
System.out.println("Could not open a file: " + e.getMessage());
}
Three properties of the multi-resource form:
1. Semicolons (;) separate the resources, not commas. The CSCD 210 lecture-and-lab convention uses one declaration per line for readability. The compiler accepts a single line with multiple resources too, but multi-line is the standard. 2. Resources close in reverse declaration order. If sc is declared first and out second, out.close() runs first, then sc.close(). This matches the convention for layered resources (BufferedReader wrapping FileReader) where the outer must close before the inner. 3. **If any resource's construction throws, already-constructed resources are still closed.** If out = new PrintStream(...) throws, sc is already constructed; the compiler-generated cleanup closes sc before propagating the exception. The manual try/finally form would need careful ordering to match this guarantee; try-with-resources handles it for free.
Why semicolons, not commas
The choice of ; reflects Java's general rule: semicolons separate statements and declarations. The resource declarations are declarations. Comma would suggest "these are expressions in a list," which is not the intent.
The final declaration may optionally end with a semicolon too:
try (
Scanner sc = new Scanner(new File(inputFile));
PrintStream out = new PrintStream(new File(outputFile)); // trailing ; legal
) { ... }
Both forms compile; the trailing semicolon is stylistic. Some style guides require it for diff-friendliness (adding a third resource does not change the line above).
When the resources depend on each other
For nested resources where the second uses the first (e.g., a Scanner reading from a BufferedReader), the declarations can refer back:
try (
FileReader fr = new FileReader(name);
BufferedReader br = new BufferedReader(fr);
Scanner sc = new Scanner(br)
) {
// ... use sc ...
}
The reverse-close order means sc closes first (which closes br transitively, which closes fr). The chain works correctly even though closes propagate through wrappers: each close() call is idempotent.
When to split into multiple try statements
If two resources have unrelated lifecycles (open one, read, close it, then open the other), keep them in separate try-with-resources blocks:
String content;
try (Scanner sc = new Scanner(new File(input))) {
content = sc.next();
} // sc closed here
try (PrintStream out = new PrintStream(new File(output))) {
out.println(content);
} // out closed here
This is the right shape when the resources do not overlap. Forcing both into one try-with-resources keeps both open longer than necessary and ties their error-handling together.
In other languages
- Python:
with open(a) as inf, open(b) as outf:: same shape, comma instead of semicolon. - C#:
using (var a = ..., var b = ...) { ... }: same; usable on multiple lines. - C++: RAII handles it automatically; the order is destructor order, which is reverse of construction.
- Go: multiple
defercalls: execute in reverse-of-defer order (last deferred is first to run).