try-with-resources
Close connections, streams and clients reliably, in the right order, without losing the original exception when close fails too.
try (var a = openA(); var b = openB(a)) {
// use a and b
} catch (Exception e) {
// a and b are already closed here
}Anything that holds an operating system or network resource must be closed: files, sockets, JDBC connections, statements and result sets. Forgetting is how services run out of file handles or exhaust their connection pool after a few hours under load. try-with-resources closes them for you.
try (Connection conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
// use conn and ps
} // ps.close() then conn.close(), even if the body throwsAny object whose class implements AutoCloseable can go in the header. Closeable from java.io is a subinterface whose close throws IOException.
The rules
- Resources are closed in the reverse order they were opened, so a statement closes before the connection it came from.
- They are closed when the block ends, before any
catchorfinallyclause of the same statement runs. By the time you handle the error, the connection is already back in the pool. - A resource that is
nullis skipped, not closed. - Since Java 9 the header can name an existing variable that is final or effectively final:
try (reader) { ... }.
Suppressed exceptions
If the body throws and then close throws as well, the body's exception is the one that propagates; the close failure is attached to it and available through getSuppressed(). The hand-written try/finally version gets this wrong: an exception from close() in finally replaces the original, so the logs show a harmless close error instead of the real failure.
What needs it in back-end code
- JDBC
Connection,PreparedStatementandResultSet. With a pool, closing a connection returns it to the pool. Files.lines,Files.listandFiles.walk: the returned stream holds an open file or directory handle.InputStreamandOutputStream, including anHttpExchangeresponse body.HttpClient(since Java 21) andExecutorService(since Java 19) when you create short-lived ones. Closing an executor waits for submitted tasks to finish.
Do not put a long-lived object such as a shared HttpClient or a DataSource in a try-with-resources header inside a request handler. It would be closed after the first request. Create those once at startup and close them on shutdown.
Example
When to use it
- Return a JDBC connection to the pool on every code path, including failed queries.
- Count error lines in a log file with Files.lines without leaking a file handle per request.
- Close an HttpExchange response body after writing JSON so the client sees the end of the response.
More examples
When close fails too: suppressed exceptions
The duplicate-key error, the one that explains the failure, is kept and the close error rides along. A logger prints suppressed exceptions under the main stack trace, so neither is lost.
class FlakyConnection implements AutoCloseable {
@Override
public void close() {
throw new IllegalStateException("close failed: socket already reset");
}
}
void main() {
try (var conn = new FlakyConnection()) {
throw new IllegalStateException("insert failed: duplicate key");
} catch (IllegalStateException e) {
IO.println("primary: " + e.getMessage());
for (Throwable s : e.getSuppressed()) {
IO.println("suppressed: " + s.getMessage());
}
}
}
// Output:
// primary: insert failed: duplicate key
// suppressed: close failed: socket already resetFiles.lines must be closed
Files.lines reads lazily and keeps the file open until the stream is closed. In a long-running service, skipping the try-with-resources leaks one file handle per call.
void main() throws IOException {
Path log = Files.createTempFile("access", ".log");
try {
Files.write(log, List.of("GET /users 200", "GET /orders 500", "POST /orders 201", "GET /health 500"));
try (Stream<String> lines = Files.lines(log)) {
long errors = lines.filter(l -> l.endsWith(" 500")).count();
IO.println("server errors: " + errors);
}
} finally {
Files.deleteIfExists(log);
}
}
// Output:
// server errors: 2Java 9 form with an existing variable
The variable must be final or effectively final. This form is handy when the resource is built by a helper or passed in, and the last line proves it really was closed.
void main() throws IOException {
BufferedReader reader = new BufferedReader(new StringReader("id,name\n1,ada\n2,linus"));
try (reader) {
IO.println("header: " + reader.readLine());
String row;
while ((row = reader.readLine()) != null) {
IO.println("row: " + row);
}
}
try {
reader.readLine();
} catch (IOException e) {
IO.println("after the block: " + e.getMessage());
}
}
// Output:
// header: id,name
// row: 1,ada
// row: 2,linus
// after the block: Stream closed
Discussion