try-with-resources

Close connections, streams and clients reliably, in the right order, without losing the original exception when close fails too.

Syntaxtry (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 throws

Any 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 catch or finally clause of the same statement runs. By the time you handle the error, the connection is already back in the pool.
  • A resource that is null is 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, PreparedStatement and ResultSet. With a pool, closing a connection returns it to the pool.
  • Files.lines, Files.list and Files.walk: the returned stream holds an open file or directory handle.
  • InputStream and OutputStream, including an HttpExchange response body.
  • HttpClient (since Java 21) and ExecutorService (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

Try it yourself
Loading editor…
Press Run to execute the code.

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.

Example · java
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 reset

Files.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.

Example · java
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: 2

Java 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.

Example · java
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

  • Be the first to comment on this lesson.