Java try-with-resources — Automatic Resource Management

4 minute read

try-with-resources (Java 7+) automatically closes resources when the block exits — no finally needed. It’s cleaner, safer, and eliminates an entire class of resource-leak bugs.


The Problem It Solves

Traditional resource management is verbose and error-prone:

// ❌ OLD WAY — manual cleanup
BufferedReader reader = null;
try {
    reader = new BufferedReader(new FileReader("data.txt"));
    String line = reader.readLine();
    System.out.println(line);
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if (reader != null) {
        try {
            reader.close();  // close itself can throw!
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

With try-with-resources:

// ✅ MODERN WAY — automatic cleanup
try (BufferedReader reader = new BufferedReader(new FileReader("data.txt"))) {
    String line = reader.readLine();
    System.out.println(line);
} catch (IOException e) {
    e.printStackTrace();
}
// reader.close() called automatically

Syntax

try (ResourceType resource = new ResourceType()) {
    // use resource
} catch (ExceptionType e) {
    // handle
}
// resource is automatically closed here

The resource must implement AutoCloseable (or its parent Closeable).


The AutoCloseable Interface

public interface AutoCloseable {
    void close() throws Exception;
}

Any class implementing AutoCloseable can be used with try-with-resources:

Class What It Closes
FileReader / FileWriter File handles
BufferedReader / BufferedWriter Buffered streams
InputStream / OutputStream Byte streams
Connection Database connections
PreparedStatement SQL statements
ResultSet Query results
Socket Network connections
Scanner Input scanner

Multiple Resources

Declare multiple resources separated by semicolons — closed in reverse order:

try (
    FileInputStream fis = new FileInputStream("input.txt");
    FileOutputStream fos = new FileOutputStream("output.txt");
    BufferedInputStream bis = new BufferedInputStream(fis);
    BufferedOutputStream bos = new BufferedOutputStream(fos)
) {
    byte[] buffer = new byte[1024];
    int bytesRead;
    while ((bytesRead = bis.read(buffer)) != -1) {
        bos.write(buffer, 0, bytesRead);
    }
}
// Closed in reverse: bos → bis → fos → fis

Why Reverse Order?

Outer resources often depend on inner ones. Closing inner first would break the outer:

Open:  fis → fos → bis → bos
Close: bos → bis → fos → fis  (reverse — safe)

Java 9 Enhancement — Effectively Final Variables

Java 9 lets you use pre-declared resources if they’re effectively final:

// Java 7-8: must declare in try()
BufferedReader reader = new BufferedReader(new FileReader("data.txt"));

// ❌ Java 7-8: compile error
try (reader) { ... }

// ✅ Java 9+: works if reader is effectively final
try (reader) {
    System.out.println(reader.readLine());
}

Useful when resources are passed as parameters:

public void process(Connection conn) throws SQLException {
    try (conn) {  // Java 9+ — conn is effectively final
        // use connection
    }
    // conn is closed automatically
}

Suppressed Exceptions

What happens when both the try block AND close() throw exceptions?

public class FaultyResource implements AutoCloseable {
    public void doWork() {
        throw new RuntimeException("from doWork");
    }

    @Override
    public void close() {
        throw new RuntimeException("from close");
    }
}

try (FaultyResource r = new FaultyResource()) {
    r.doWork();
}

Result:

  • The try block exception (from doWork) is the primary exception.
  • The close() exception (from close) is a suppressed exception.
catch (RuntimeException e) {
    System.out.println(e.getMessage());         // "from doWork"
    Throwable[] suppressed = e.getSuppressed();
    System.out.println(suppressed[0].getMessage()); // "from close"
}

With old-style finally, the close() exception would replace the original — losing critical information. try-with-resources preserves both.


Creating Custom AutoCloseable Classes

public class DatabaseConnection implements AutoCloseable {
    private final String url;
    private boolean open;

    public DatabaseConnection(String url) {
        this.url = url;
        this.open = true;
        System.out.println("Connected to " + url);
    }

    public void query(String sql) {
        if (!open) throw new IllegalStateException("Connection closed");
        System.out.println("Executing: " + sql);
    }

    @Override
    public void close() {
        if (open) {
            open = false;
            System.out.println("Disconnected from " + url);
        }
    }
}

Usage:

try (var db = new DatabaseConnection("jdbc:mysql://localhost/mydb")) {
    db.query("SELECT * FROM users");
    db.query("SELECT * FROM orders");
}
// Output:
// Connected to jdbc:mysql://localhost/mydb
// Executing: SELECT * FROM users
// Executing: SELECT * FROM orders
// Disconnected from jdbc:mysql://localhost/mydb

Closeable vs AutoCloseable

Feature AutoCloseable Closeable
Package java.lang java.io
close() throws Exception IOException
Idempotent requirement No Yes (should be safe to call multiple times)
Used by try-with-resources I/O classes

Closeable extends AutoCloseable — all Closeable types work with try-with-resources.


Practical Patterns

Database Query

try (
    Connection conn = dataSource.getConnection();
    PreparedStatement stmt = conn.prepareStatement("SELECT * FROM users WHERE id = ?")
) {
    stmt.setLong(1, userId);
    try (ResultSet rs = stmt.executeQuery()) {
        if (rs.next()) {
            return new User(rs.getString("name"), rs.getString("email"));
        }
    }
}

Reading a File Line by Line

try (var reader = new BufferedReader(new FileReader("data.csv"))) {
    String line;
    while ((line = reader.readLine()) != null) {
        processLine(line);
    }
}

Modern File Reading (NIO)

try (Stream<String> lines = Files.lines(Path.of("data.txt"))) {
    lines.filter(line -> !line.isBlank())
         .map(String::trim)
         .forEach(System.out::println);
}

Without catch — Just Cleanup

try-with-resources doesn’t require a catch block:

try (var conn = getConnection()) {
    execute(conn);
}
// conn closed automatically — exception propagates to caller

Common Mistakes

Mistake Problem Fix
Not using try-with-resources Resource leaks Use it for all AutoCloseable
Closing manually in finally too Double close Let try-with-resources handle it
Ignoring suppressed exceptions Lost error info Check getSuppressed()
Resource not AutoCloseable Can’t use in try-with-resources Implement the interface
Assigning to resource var in try body Breaks auto-close Declare resource in try() header

Key Takeaways

  1. Always use try-with-resources for AutoCloseable objects — files, streams, connections.
  2. Resources are closed in reverse declaration order.
  3. Suppressed exceptions from close() are preserved, not lost.
  4. Java 9+ allows effectively final variables in the try() header.
  5. Implement AutoCloseable in your own classes that hold resources.
  6. try-with-resources makes finally blocks for cleanup obsolete.

What’s Next?

Next: Exception Handling Best Practices — patterns, anti-patterns, and production-grade error handling.

Updated: