JAVA / EXCEPTIONS
try with resources and autoclosable handling
Write try-with-resources blocks for library and custom AutoCloseable types, and read suppressed exceptions when both the body and close() fail.
What you will learn
- Declare resources in the try header so close() runs on every exit path
- Resources close in reverse declaration order, before any catch or finally runs
- Implement AutoCloseable with a close() that declares no checked exception
- Recover close-time failures from Throwable.getSuppressed() instead of losing them
Understanding try with resources and autoclosable handling
A resource is any object holding something the garbage collector cannot reclaim by itself: an OS file handle, a socket, a JDBC connection, a lock. Java models the release step with one method, AutoCloseable.close(), and try-with-resources is the language feature that calls it for you. Anything declared in the parentheses of the try header must be an AutoCloseable, becomes implicitly final, and is closed when control leaves the block, whether by falling off the end, by return, by break, or by a thrown exception. Closeable, the older java.io interface, extends AutoCloseable and narrows close() to throw only IOException, which is why streams fit in the header without forcing you to catch Exception.
Two ordering rules explain most surprises. Resources close in the reverse of their declaration order, because a later resource is usually built on an earlier one: a BufferedReader wrapping a FileReader must flush and finish before the thing underneath it goes away. The generated close calls also happen before the catch and finally clauses attached to that same try statement, so by the time your catch block runs the resources are already closed, and touching them there produces an already-closed failure rather than fresh data.
When the body throws and close() throws too, only one exception can propagate out of the statement. try-with-resources keeps the body's exception as the primary one and attaches the close failure through Throwable.addSuppressed, so the original cause is never discarded. If the body succeeds and close() throws, that close exception propagates on its own and can be caught by the statement's own catch clause. That asymmetry is the point: cleanup you write by hand loses the real exception the moment close() throws, while here the real exception survives and the close failure shows up under a Suppressed: line in the stack trace.
Ownership is the mental model. Putting a variable in the header is a statement that this block owns the resource and nobody outside will use it afterwards, which is why the compiler is allowed to close it at the boundary and why the variable is final.
public class Main {
static class Resource implements AutoCloseable {
private final String name;
Resource(String name) {
this.name = name;
System.out.println("open " + name);
}
void use() {
System.out.println("use " + name);
}
@Override
public void close() {
System.out.println("close " + name);
}
}
public static void main(String[] args) {
try (Resource first = new Resource("first");
Resource second = new Resource("second")) {
first.use();
second.use();
throw new IllegalStateException("body failed with both open");
} catch (IllegalStateException e) {
System.out.println("caught: " + e.getMessage());
} finally {
System.out.println("finally");
}
System.out.println("after the try statement");
}
}The try header hands ownership of a resource to the compiler, which guarantees close() on every exit path, in reverse declaration order, while keeping the body's exception as the primary failure.
Worked examples
Close failure becomes a suppressed exception
Shows that a throwing close() does not replace the exception the body threw.
public class Suppressed {
static class Faulty implements AutoCloseable {
@Override
public void close() {
throw new IllegalStateException("close failed");
}
}
public static void main(String[] args) {
try (Faulty faulty = new Faulty()) {
System.out.println("using " + faulty.getClass().getSimpleName());
throw new RuntimeException("body failed");
} catch (RuntimeException e) {
System.out.println("primary: " + e.getMessage());
for (Throwable s : e.getSuppressed()) {
System.out.println("suppressed: " + s.getMessage());
}
}
}
}Example explained
Line 1close() throws while the body's RuntimeException is already in flight, so the generated code calls primary.addSuppressed(closeException) instead of letting it escape.
Line 2The catch clause therefore sees "body failed", the exception that actually describes what went wrong.
Line 3getSuppressed() returns the close failures in the order they happened; if you never read it, close-time bugs stay invisible.
Line 4e.printStackTrace() would show the same information, indented under a line beginning with "Suppressed:".
Closing an existing variable, and using it afterwards
Uses the Java 9 try (variable) form and shows what happens when you touch the resource after the block.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
public class ExistingVariable {
public static void main(String[] args) throws IOException {
BufferedReader reader = new BufferedReader(new StringReader("alpha\nbeta"));
try (reader) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println("line: " + line);
}
}
try {
reader.readLine();
} catch (IOException e) {
System.out.println("after close: " + e.getMessage());
}
}
}Example explained
Line 1try (reader) works because reader is a local variable that is assigned once and never reassigned, so it is effectively final.
Line 2Only the outer BufferedReader is in the header: closing it closes the StringReader it wraps, so listing both would just close the inner one twice.
Line 3The readLine() after the block hits BufferedReader's closed check and throws IOException("Stream closed"), which proves close() already ran.
Line 4main declares throws IOException because the readLine() inside the body and the implicit close() can both throw it.
A resource whose constructor fails
Demonstrates that resources already opened are closed even when a later initializer in the header throws.
public class PartialOpen {
static class Opened implements AutoCloseable {
Opened() {
System.out.println("Opened created");
}
@Override
public void close() {
System.out.println("Opened closed");
}
}
static class NeverOpens implements AutoCloseable {
NeverOpens() {
throw new IllegalArgumentException("constructor failed");
}
@Override
public void close() {
System.out.println("NeverOpens closed");
}
}
public static void main(String[] args) {
try (Opened opened = new Opened();
NeverOpens broken = new NeverOpens()) {
System.out.println("body never runs");
} catch (IllegalArgumentException e) {
System.out.println("caught: " + e.getMessage());
}
}
}Example explained
Line 1The second initializer throws before the body starts, so nothing inside the block executes.
Line 2Opened had already been constructed, so its close() still runs: a multi-resource header behaves like one nested try statement per resource.
Line 3NeverOpens.close() is never called, because no reference to that half-built object was ever assigned.
Line 4That is why a constructor which grabs a handle and then fails must release the handle itself; try-with-resources cannot reach it.
Important notes
close() may be invoked while the body is unwinding, so it must tolerate a partly used object and be safe to call twice; a close() that throws for routine reasons ends up as a suppressed exception nobody reads.
If a resource expression evaluates to null, the generated code skips the close call rather than throwing NullPointerException, and the try (variable) form requires that variable to be effectively final.
Common mistakes
Creating the resource inside the try body instead of the header: the header is empty, so nothing is generated to close it and the file handle or connection leaks until the process exits.
Writing public void close() throws Exception on a custom resource just because AutoCloseable permits it: every caller is now forced into catch (Exception) or throws Exception, which hides unrelated bugs.
Returning or storing the resource for use after the block, such as returning a ResultSet or reading from the reader in the catch clause: the object is already closed and the next call throws "Stream closed" or IllegalStateException.
Try it yourself
Change, predict, then run
Write a Latch class implementing AutoCloseable that prints "open " + id in its constructor and "close " + id in close(), open three latches in one try header, and throw from the body; predict the printed order before running it. Then make the middle latch's close() throw and print e.getSuppressed().length in the catch clause.
Open the Java workspaceCheck your understanding
A try-with-resources statement declares A then B. The body throws a RuntimeException, and B.close() then throws an IllegalStateException. Which exception reaches the statement's catch (RuntimeException e) clause?
- The exception from B.close(), because closing happens after the body and therefore replaces it.
- The RuntimeException from the body, with the close failure attached to it as a suppressed exception.
- Neither: try-with-resources discards exceptions thrown while closing.
- Both in turn, so the catch clause executes twice.
Show answer
The body's exception is the primary one, and the compiler-generated close call is wrapped so that a failure there is handed to primary.addSuppressed and stays reachable through getSuppressed(). Option 0 describes what hand-written cleanup does when close() throws from it, and preventing exactly that loss of the real exception is the reason try-with-resources exists.