Using try-with-resources in Java for Reliable Resource Management
Learn how Java's try-with-resources statement automatically closes resources, handles exceptions and suppressed exceptions, and reduces the risk of resource leaks.
In Java, managing resources like file streams, database connections, and network sockets has always required careful attention to closing them properly. The try-with-resources statement, introduced in Java 7, provides a concise and reliable way to ensure that each resource is closed automatically at the end of the statement. This eliminates the classic pattern of manually closing resources in a finally block and reduces the risk of resource leaks.
The Problem with Manual Resource Management
Before try-with-resources, developers typically wrote code like this:
BufferedReader reader = null; try { reader = new BufferedReader(new FileReader("data.txt")); String line = reader.readLine(); // process line } catch (IOException e) { // handle exception } finally { if (reader != null) { try { reader.close(); } catch (IOException e) { // handle close failure } } }
The finally block ensures the resource is closed even when an exception occurs, but it adds boilerplate and makes the code harder to read. If multiple resources are involved, the nesting grows and the chance of forgetting to close one increases. The try-with-resources statement addresses this by handling closure automatically and consistently.
How try-with-resources Works
The syntax is straightforward:
try (BufferedReader reader = new BufferedReader(new FileReader("data.txt"))) { String line = reader.readLine(); // process line } catch (IOException e) { // handle exception }
Any object that implements java.lang.AutoCloseable can be used. Because java.io.Closeable extends AutoCloseable, all Closeable resources such as streams and readers qualify. The resource is closed automatically when the try block exits, whether normally or due to an exception. The compiler generates the necessary close() calls, so you no longer need an explicit finally block for resource cleanup.
This behavior is not limited to file I/O. It applies to any class implementing AutoCloseable, including JDBC connections, sockets, and custom classes you define.
Declaring Multiple Resources
You can declare multiple resources in the same try statement, separated by semicolons. They are closed in the reverse order of their declaration, which is important when resources depend on each other.
try (BufferedReader reader = new BufferedReader(new FileReader("data.txt")); BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))) { String line; while ((line = reader.readLine()) != null) { writer.write(line); writer.newLine(); } } catch (IOException e) { // handle exception }
Here, writer is closed before reader, because resources are closed in reverse order of declaration. This order matters when resources are layered: a resource that depends on another resource is closed first, while the resource it depends on is still available. In this example the two file resources are independent, but the same rule applies whenever multiple resources are listed.
Exception Handling and Suppressed Exceptions
One subtle but critical behavior of try-with-resources is how exceptions from the close() method interact with exceptions thrown in the try block. If both the try block and the close() method throw exceptions, the exception from the try block is propagated, and the exception from close() is added to it as a suppressed exception. You can retrieve these suppressed exceptions using Throwable.getSuppressed().
Consider this example:
class Resource implements AutoCloseable { public void use() { throw new RuntimeException("Error during use"); } @Override public void close() { throw new RuntimeException("Error during close"); } } public class Main { public static void main(String[] args) { try (Resource r = new Resource()) { r.use(); } catch (RuntimeException e) { System.out.println("Caught: " + e.getMessage()); for (Throwable t : e.getSuppressed()) { System.out.println("Suppressed: " + t.getMessage()); } } } }
Output:
Caught: Error during use
Suppressed: Error during close
This ensures that the primary failure is not masked by a secondary failure during cleanup. With manual cleanup, an exception thrown from close() in a finally block could mask the original exception unless it was explicitly handled.
Writing Custom AutoCloseable Classes
You can create your own resources that integrate with try-with-resources by implementing AutoCloseable. The interface has a single method, close(), which you must implement. It should release any underlying resources and can throw an exception if cleanup fails.
public class DatabaseConnection implements AutoCloseable { private boolean open; public void connect() { open = true; System.out.println("Connected"); } public void query(String sql) { if (!open) throw new IllegalStateException("Connection is closed"); System.out.println("Executing: " + sql); } @Override public void close() { if (open) { open = false; System.out.println("Closed"); } } }
Usage:
try (DatabaseConnection conn = new DatabaseConnection()) { conn.connect(); conn.query("SELECT * FROM users"); }
When the try block exits, close() is invoked automatically. If the try block throws an exception, close() is still called, and any exception from close() is suppressed as described earlier.
Performance and Resource Leak Considerations
The primary benefit of try-with-resources is not speed but correctness. It prevents the most common resource leak caused by forgetting to close a resource. Resource leaks can lead to file descriptor exhaustion, database connection pool starvation, or network socket exhaustion in long-running applications. By ensuring close() is always called, try-with-resources reduces the risk of these production issues.
The small amount of bytecode generated for automatic close handling is unlikely to affect application performance. The real performance risk comes from leaked resources, which degrade an application over time.
When Not to Use try-with-resources
Try-with-resources is not a universal replacement for all resource management patterns. If you need to close a resource conditionally—for example, only when a certain state is reached—or if the resource's lifetime extends beyond a single method call, you may need manual control. In such cases, you can still use a finally block or a dedicated manager class.
Try-with-resources is best suited for resources whose lifetime is confined to the block. If you need to maintain a resource beyond the block or hand it to another component that is responsible for closing it, manual management remains appropriate. For local, short-lived resources, however, it is the idiomatic solution.