Try-with-Resources for I/O
Try-with-Resources for I/O
Every stream, reader, writer, or channel you open is a finite operating-system resource: a file descriptor. If your code throws before it reaches a manual close() call, that descriptor leaks. Leak enough descriptors and the JVM — or the whole OS — starts refusing to open new files. Try-with-resources, introduced in Java 7 and refined in Java 9, is the language mechanism that guarantees close() is always called, no matter what.
The Problem with Manual close()
The naive pattern looks harmless:
Even wrapping in a try/finally is tedious and error-prone when multiple resources are involved:
Notice the nested try inside finally: if both the body and close() throw, the body's exception is silently discarded. That is exactly the class of hard-to-diagnose bug that try-with-resources was designed to eliminate.
The AutoCloseable Contract
Try-with-resources works with any class that implements java.lang.AutoCloseable (or its sub-interface java.io.Closeable). The single required method is:
All standard I/O classes — InputStream, OutputStream, Reader, Writer, RandomAccessFile, NIO.2 Channels, DirectoryStream, SeekableByteChannel — implement AutoCloseable, so they all work in a try-with-resources block automatically.
Basic Syntax
The resource declaration lives inside the parentheses after try. When the block exits — normally, via return, or via any exception — the runtime calls close() on every declared resource in reverse declaration order.
Multiple Resources in One Block
Declare resources separated by semicolons. They are closed in reverse order (last-declared closed first), mirroring how you would manually nest close calls:
BufferedWriter buffers data in memory; closing it flushes the buffer to disk. If the writer were closed after an exception escapes, data could be lost. Try-with-resources guarantees the flush-and-close happens even on failure.
Suppressed Exceptions
This is the subtle, important part. If the body throws exception A, and then close() also throws exception B, Java does NOT discard A. Instead, B is attached to A as a suppressed exception:
You can inspect suppressed exceptions when debugging:
Java 9: Effectively Final Variables
Java 9 lets you use an already-declared effectively final variable directly in the resource list — no need to re-declare it:
This is handy when the resource is conditionally created before the try block or passed into a method.
Implementing AutoCloseable in Your Own Classes
Any class that wraps an external resource should implement AutoCloseable so callers can use it safely:
IOException inside close(). If flushing the buffer fails (disk full, network share disconnected), the caller should know. Only suppress if you are certain it cannot carry meaningful information.
Idempotent close() and close() Called Twice
The contract for Closeable (the I/O sub-interface) explicitly requires that close() be idempotent: calling it a second time must have no effect and must not throw. AutoCloseable does not make that guarantee for non-I/O resources, so if you implement it yourself for a non-I/O context, document whether repeated calls are safe.
NIO.2 Resources Are Also AutoCloseable
NIO.2 types integrate cleanly:
Trade-offs and Edge Cases
- Resource initialisation failure: If the constructor of the second resource throws, the first is still closed. Java initialises and tracks each resource independently.
- null resources: A
nullin the resource list is silently skipped — no NPE, no close call. Useful when a resource may not have been created yet, but be cautious: it hides bugs where a resource was never opened. - Checked vs unchecked:
close()declared as throwingExceptionforces the caller to handle it. If yourclose()can only throw unchecked exceptions (or nothing at all), declare it asthrows nothingto remove that burden from callers.
Summary
Try-with-resources is the correct, idiomatic way to manage any AutoCloseable resource in Java. It guarantees close() runs unconditionally, handles suppressed exceptions properly rather than discarding them, and eliminates the boilerplate of nested try/finally blocks. Every I/O class in the standard library implements AutoCloseable, and your own resource-holding classes should too. When you open a file, channel, or stream, always use a try-with-resources block — no exceptions (except the ones Java manages for you).