5.3 Resource Management: try-with-resources and AutoCloseable
Key Takeaways
- The try-with-resources statement automatically manages resources implementing AutoCloseable or Closeable, guaranteeing deterministic closure via close() upon block exit.
- Resources declared in the try header are initialized from left to right and automatically closed in reverse declaration order (LIFO) before any explicit catch or finally blocks execute.
- When exceptions occur in both the try body and close() invocations, the try block exception is propagated as primary while close() exceptions are attached as suppressed exceptions.
- Java 9+ permits using existing final or effectively final resource variables directly within the try-with-resources header without requiring fresh local declarations.
5.3 Resource Management: try-with-resources and AutoCloseable
The try-with-resources statement (introduced in Java 7 and enhanced in Java 9) modernized resource management by automating deterministic cleanup of operating system handles, file descriptors, database connections, and network sockets. It eliminates classic resource leakages and avoids the exception-masking anti-pattern inherent in legacy try-finally blocks.
The AutoCloseable and Closeable Interfaces
Any class instantiated or referenced within a try-with-resources declaration header must implement either java.lang.AutoCloseable or java.io.Closeable.
| Interface | Package | Introduced | close() Method Signature | Idempotence Requirement |
|---|---|---|---|---|
java.lang.AutoCloseable | java.lang | Java 7 | void close() throws Exception; | Recommended, but not strictly required. |
java.io.Closeable | java.io | Java 5 | void close() throws IOException; | Strictly required (calling close() multiple times must have no side effects). |
java.lang.AutoCloseable
(void close() throws Exception)
▲
│ extends
java.io.Closeable
(void close() throws IOException)
[!NOTE] Because
CloseableextendsAutoCloseable, everyCloseableobject is also anAutoCloseable. However,Closeable.close()narrows the declared exception type toIOException. Custom implementations ofAutoCloseablethat do not perform I/O should declare specific checked exceptions or no checked exceptions at all (void close()without athrowsclause).
Multi-Resource Declaration and LIFO Close Order
Multiple resources can be declared inside the try parentheses, separated by semicolons (;). A trailing semicolon after the final resource is optional.
class Door implements AutoCloseable {
private final String name;
public Door(String name) { this.name = name; System.out.print("Open-" + name + " "); }
@Override
public void close() { System.out.print("Close-" + name + " "); }
}
public class CloseOrderDemo {
public static void main(String[] args) {
try (Door d1 = new Door("1");
Door d2 = new Door("2");
Door d3 = new Door("3")) {
System.out.print("Inside-Try ");
}
}
}
Execution Output:
Open-1 Open-2 Open-3 Inside-Try Close-3 Close-2 Close-1
The Invariant Rules of Resource Lifecycle:
- Initialization Order: Resources are constructed and initialized from left to right (in declaration order:
d1, thend2, thend3). - Closing Order (LIFO): Resources are closed in reverse order of declaration (
d3, thend2, thend1). - Early Construction Failure: If
Door("2")throws an exception during construction,Door("1")is immediately closed,Door("3")is never created, and the exception propagates outward.
Java 9+ Effectively Final Resource References
Prior to Java 9, every resource used in try-with-resources had to be newly declared inside the try (...) parentheses. Java 9 introduced the ability to place existing final or effectively final references directly into the header:
import java.io.StringReader;
public class EffectivelyFinalResources {
public void process(String data) throws Exception {
final StringReader reader1 = new StringReader(data);
StringReader reader2 = new StringReader(data); // Effectively final
try (reader1; reader2) {
System.out.println(reader1.read());
System.out.println(reader2.read());
}
// ILLEGAL: Modifying reader2 later makes it NOT effectively final
// reader2 = new StringReader("other"); // Breaks compilation of try (reader2)!
}
}
The Suppressed Exceptions Mechanism
In legacy try-finally code, if the try block threw an exception and the finally block's close() call also threw an exception, the close() exception would overwrite and completely discard the original business logic exception.
In try-with-resources, the JVM preserves both exceptions using suppressed exceptions:
- The exception thrown inside the
trybody is treated as the primary exception and propagates outward. - Any exception thrown by a resource's
close()method during automatic cleanup is caught and attached to the primary exception viaThrowable.addSuppressed(Throwable exception). - The caller can retrieve all suppressed exceptions using
Throwable.getSuppressed(), which returns an array ofThrowable[].
class FaultyResource implements AutoCloseable {
private final String id;
public FaultyResource(String id) { this.id = id; }
@Override
public void close() throws Exception {
throw new IllegalStateException("Close failed on: " + id);
}
}
public class SuppressedDemo {
public static void main(String[] args) {
try (var r1 = new FaultyResource("R1");
var r2 = new FaultyResource("R2")) {
throw new IllegalArgumentException("Primary business error");
} catch (Exception e) {
System.out.println("Primary: " + e.getMessage());
for (Throwable s : e.getSuppressed()) {
System.out.println("Suppressed: " + s.getMessage());
}
}
}
}
Console Output:
Primary: Primary business error
Suppressed: Close failed on: R2
Suppressed: Close failed on: R1
[!NOTE] Suppressed exceptions are attached in the reverse order in which resources are closed (
R2closes first, thenR1).
Critical Exam Rule: Resource Close Precedes Catch and Finally
On the 1Z0-830 exam, questions frequently test the exact interleaving of automatic close() calls with explicit catch and finally blocks.
+-----------------------------------------------------------------------------------+
| Exact try-with-resources Statement Execution Order |
| |
| 1. Resource acquisition and initialization in header (Left to Right) |
| 2. Execute body of try block |
| 3. Automatic resource closing via close() (Right to Left / Reverse Order) |
| 4. Explicit catch blocks execute (if an exception occurred in try or close) |
| 5. Explicit finally block executes (always executes last) |
+-----------------------------------------------------------------------------------+
class TraceResource implements AutoCloseable {
@Override
public void close() {
System.out.print("CLOSE ");
}
}
public class ExecutionSequenceTrap {
public static void main(String[] args) {
try (TraceResource r = new TraceResource()) {
System.out.print("TRY ");
throw new RuntimeException();
} catch (RuntimeException e) {
System.out.print("CATCH ");
} finally {
System.out.print("FINALLY ");
}
}
}
Program Output:
TRY CLOSE CATCH FINALLY
Inside the catch block and finally block, the resource is already closed! Furthermore, resource variables declared inside the try (...) header are out of scope inside catch and finally blocks.
Handling null Resource Variables in try-with-resources
A crucial edge case tested on the 1Z0-830 exam is how try-with-resources handles resources that evaluate to null.
[!NOTE] If a resource expression in the
trydeclaration header evaluates tonull, thetry-with-resources statement executes normally, and upon exiting, the runtime safely skips closing that resource without throwing aNullPointerException.
import java.io.StringReader;
public class NullResourceDemo {
public static void main(String[] args) throws Exception {
StringReader reader = null; // null resource reference
try (reader) { // Legal: reader evaluates to null
System.out.println("Inside try body");
} // Exits try block: close() is skipped for reader, NO NullPointerException is thrown!
System.out.println("Completed cleanly");
}
}
However, if an expression that creates the resource throws an exception during initialization, any previously initialized resources in the same try header are closed immediately in reverse order, while subsequent resources are never instantiated:
class Res implements AutoCloseable {
String name;
Res(String name) {
if ("fail".equals(name)) throw new RuntimeException("Init failed: " + name);
this.name = name;
}
public void close() { System.out.println("Closing " + name); }
}
public class MultiInitDemo {
public static void main(String[] args) {
try (Res r1 = new Res("R1");
Res r2 = new Res("fail"); // Throws RuntimeException during init
Res r3 = new Res("R3")) { // Never instantiated!
System.out.println("Inside try");
} catch (Exception e) {
System.out.println("Caught: " + e.getMessage());
}
// Output sequence:
// Closing R1
// Caught: Init failed: fail
}
}
Given the following implementation of two AutoCloseable resources:
What is the exact output displayed on the console?class Res implements AutoCloseable {
String id;
Res(String id) { this.id = id; }
public void close() { System.out.print(id); }
}
public class ResourceOrder {
public static void main(String[] args) {
try (Res r1 = new Res("1"); Res r2 = new Res("2")) {
System.out.print("T");
}
System.out.print("D");
}
}
What is the primary exception thrown out of the try-with-resources block below, and what is its suppressed exception?
class BrokenDevice implements AutoCloseable {
public void close() throws Exception {
throw new IllegalStateException("Device close failed");
}
}
public class DeviceRunner {
public static void main(String[] args) throws Exception {
try (BrokenDevice dev = new BrokenDevice()) {
throw new java.io.IOException("Device write failed");
}
}
}
What is the output of the following program?
public class ResourceFlow {
static class Box implements AutoCloseable {
public void close() { System.out.print("C "); }
}
public static void main(String[] args) {
try (Box b = new Box()) {
System.out.print("A ");
throw new RuntimeException();
} catch (Exception e) {
System.out.print("B ");
} finally {
System.out.print("D ");
}
}
}
Consider the following code using Java 9+ effectively final resource syntax. Why does this code fail to compile?
import java.io.StringReader;
public class BadResourceSyntax {
public void process() throws Exception {
StringReader sr = new StringReader("hello");
try (sr) {
System.out.println(sr.read());
}
sr = new StringReader("world");
}
}