Java Try-with-resources
›Short versionOpen if you only have a minute.
- A try header declares AutoCloseable resources. They are initialized left to right and closed right to left, after the body and before your catch.
- If the body throws and close throws as well, the body's exception propagates. Each exception from close is attached with addSuppressed.
- A finally block that lets close throw does the opposite: the close exception replaces the original, and the original is not kept as a cause or as a suppressed exception.
- If the body succeeds and close throws, the close exception is the one your catch sees. There is no body failure to attach it to.
- If a later constructor fails, resources already opened are closed, and close failures are suppressed on the constructor's exception.
- The resource is closed when the statement ends. Returning it gives the caller a closed object. Java 9 lets an effectively final variable appear in the header.
Try-with-resources is a try whose header owns things that must be closed. The language closes them, in a defined order, and it has a rule for the awkward case: the work already failed, and closing failed too. That second failure is a . It rides along. It does not erase the first one.
Closing without a second try
Before Java 7, a reader was closed in finally, and close itself throws IOException, so the cleanup needed another try. If the read had already thrown, a throw from close replaced it. The caller's catch saw the close, and the read error was gone.
The header form declares the resource, uses it in the body, and closes it when the statement ends, whether the body finished, threw, or returned. BufferedReader implements through Closeable. The close exception from a reader is still IOException, so this method still has to catch it or declare it. Closing is not handling. The catch is still yours if you want a sentence for a missing file.
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
static String firstLine(String path) throws IOException {
try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
return reader.readLine();
}
}Several resources go in one header, separated by semicolons. Declare the inner resource first and the wrapper after it. is the reverse, so the wrapper closes while the thing it uses is still open.
try (
FileReader file = new FileReader("input.txt");
BufferedReader reader = new BufferedReader(file)
) {
return reader.readLine();
}The failure that must stay visible
These doors throw on purpose, so the order is visible without a filesystem. enter throws. Then the resources close, inner first, then outer, and each close throws as well.
class Door implements AutoCloseable {
private final String name;
Door(String name) {
this.name = name;
}
void enter() {
throw new IllegalStateException("could not enter");
}
@Override
public void close() {
throw new IllegalStateException(name + " stuck");
}
}
static void visit() {
try (Door outer = new Door("outer"); Door inner = new Door("inner")) {
inner.enter();
} catch (IllegalStateException e) {
System.out.println(e.getMessage());
for (Throwable suppressed : e.getSuppressed()) {
System.out.println("suppressed: " + suppressed.getMessage());
}
}
}visit prints could not enter, then suppressed: inner stuck, then suppressed: outer stuck. The primary exception is the one from the body. Closing still happens, both of them, and each failure is added with addSuppressed on that primary object. printStackTrace shows the same extras under a Suppressed: heading. Your catch runs after the closes, so getSuppressed is already populated when you see the exception. A finally you write on this statement runs after that catch, by the ordinary finally rule.
If enter had succeeded and only the closes threw, there would be no body exception to keep. The first close failure, from inner, would propagate, and outer's close failure would be suppressed on it. If the body succeeds and every close succeeds, nothing is thrown. The suppressed list is how a second failure is preserved. It is not a second channel that always fires.
A constructor that fails halfway
Initialization is left to right. If inner's constructor throws, outer has already been constructed, and it is closed. inner is not closed, because no object was bound. Should outer.close throw too, that exception is suppressed on the constructor failure, which remains the one that propagates.
class Door implements AutoCloseable {
final String name;
Door(String name) {
if (name.equals("inner")) {
throw new IllegalStateException("inner failed to open");
}
this.name = name;
}
@Override
public void close() {
throw new IllegalStateException(name + " stuck");
}
}
try (Door outer = new Door("outer"); Door inner = new Door("inner")) {
outer.toString();
}The catch sees inner failed to open, with outer stuck suppressed. The body never ran. This is the same attachment rule as a body failure: something already went wrong, later cleanup went wrong too, and the first wrong stays primary.
A variable you already have
Java 9 allows an variable in the header. The constructor can sit outside, so a failure to open is a different statement from a failure while reading. The close still happens at the end of the try.
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
static String firstLine(String path) throws IOException {
BufferedReader reader = new BufferedReader(new FileReader(path));
try (reader) {
return reader.readLine();
}
}reader is never assigned again, so it may appear alone in the header. After the statement, the variable is still in scope and the reader is closed. Returning reader from inside the try would return it only after close runs, by the same rule that delays a return until finally has finished. The caller would hold a closed reader. If the caller must own the lifetime, do not put the object in the header. Hand it back and let the caller write the try-with-resources.
close on your own types should tolerate a second call. A session that shuts down on the first close can return on the second. Throwing the second time punishes a caller who closed in a header and also closed in a defensive finally. The interesting exception is the first failure, not a complaint that cleanup happened twice.
The body throws 'read failed'. close then throws 'close failed'. Your catch prints the message and then the suppressed messages. What is primary, and what would a finally block that calls close have shown instead?
Primary is read failed. close failed is suppressed on it, so the catch can print both. A finally that calls close and lets that exception escape would propagate close failed alone. read failed would be discarded. It would not appear as the cause, and it would not appear as a suppressed exception, unless your finally caught the close failure and called addSuppressed on the read failure yourself.