Stop leaking file descriptors with Java try-with-resources
Use try-with-resources so the compiler closes files, sockets, and connections even when exceptions interrupt your code.
17 Aug 2025, 12:59 UTC

The open file that never closes
Imagine a startup routine that reads app.config with FileInputStream. The code opens the stream, reads a few lines, and throws an IOException when a required section is missing. You catch the error, log it, and move on. But the stream stays open. If the service restarts repeatedly, open file descriptors accumulate until the operating system refuses new connections with “too many open files.”
The usual fix is a finally block that calls close(). That works, but it adds boilerplate, and if close() itself throws, it can mask the original exception. You end up choosing between hiding the real error and leaking the resource.
Takeaway: use try-with-resources for any resource that implements AutoCloseable. The compiler generates the cleanup code, so close() runs whether the block exits normally or because of an exception.
What the compiler actually does
AutoCloseable is the interface that marks a type as eligible for automatic cleanup. It declares one method: close(). When you write try (Resource r = ...), the compiler creates a hidden variable to hold the resource and wraps the block in a synthetic finally that calls r.close(). If both the block and close() throw, the close() exception is added to the original as a suppressed exception rather than replacing it.
This applies to streams, readers, writers, sockets, database connections, and any custom class that implements AutoCloseable. It does not apply to raw file descriptors or unmanaged memory unless you wrap them first.
Worked example: read a config file safely
This example opens a text configuration file, reads the first line, and deliberately throws if the file is empty. The resource closes even though the exception is thrown.
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class ReadConfig {
public static void main(String[] args) {
Path config = Path.of("app.config");
try (BufferedReader reader = Files.newBufferedReader(config)) {
String line = reader.readLine();
if (line == null) {
throw new IOException("config file is empty");
}
System.out.println(line);
} catch (IOException e) {
System.err.println("caught: " + e.getMessage());
}
}
}
Run this from a terminal in the project directory. You need read permission on app.config; no elevated privileges are required.
- Compile:
javac ReadConfig.java - Run:
java ReadConfig
Expected check: if app.config is empty, the program prints caught: config file is empty and exits. Because BufferedReader implements AutoCloseable, its close() method runs before the catch block handles the exception.
To verify the compiler inserted cleanup, inspect the bytecode: javap -c ReadConfig. Look for a synthetic finally region that invokes close on the reader. This is a static check; it does not run the program.
Risk: if close() throws while an exception is already active, Java attaches the close error as a suppressed exception. The original exception is still thrown, but you should log suppressed exceptions to avoid losing diagnostic information.
Trade-offs and limits
- Requires JDK 7 or later. Older versions will not compile the syntax.
- Only
AutoCloseabletypes can be declared in the resource list. Wrap low-level handles yourself if needed. - Multiple resources in one
tryheader are closed in reverse order, but long lists hurt readability. Keep each block short and focused. - You cannot express custom cleanup logic directly in the header. Create a wrapper class that implements
AutoCloseableand performs the extra work inclose().
Closing checklist
- Replace manual
try-finallyresource handling withtry-with-resourceswhere possible. - Confirm each resource type implements
AutoCloseablebefore using it in the header. - Test the failure path: force an exception inside the block and confirm the resource’s
close()still executes. - Log suppressed exceptions when
close()might throw.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.