Topic 7.6
try-with-resources
In one line
try-with-resources declares objects that must be closed (files, sockets, database connections) in parentheses after try. Java closes them automatically, in reverse order, however the block ends, and keeps any failure from close() as a suppressed exception instead of losing the original error.
Think of it like this
Borrowing books from a library with a self-return chute by the exit. You can't leave the building without passing the chute, and the books drop in automatically, the last one you took first. Even if you leave in a hurry because the fire alarm rings, the books still go back. try-with-resources is that chute for things your program borrows from the operating system.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Resource
- An object holding something outside Java's memory, like an open file or a network connection, that must be closed when you're done.
- `AutoCloseable`
- The interface (Java 7) with one method,
close(), that every try-with-resources resource must implement. - `Closeable`
- The older I/O interface (Java 5) whose
close()throwsIOException. It extendsAutoCloseable. - Resource leak
- Forgetting to close a resource, so the operating system's handle stays in use until the program runs out of them.
- Suppressed exception
- An exception from
close()that happened while another exception was already on its way out. It's attached to that first exception instead of replacing it. - Idempotent
- Safe to do more than once with the same result. Calling
close()a second time should do nothing. - File handle (descriptor)
- A number the operating system gives a program for each open file or socket. There's a limit per process, often 1024 by default on Linux.
- Effectively final
- A variable that is never reassigned after it's set, even without the
finalkeyword.
Step by step
01Before Java 7: the hand-written version
Topic 7.2 showed the old pattern: declare the variable outside, open it inside the try, close it in finally after a null check, and wrap close() in its own try so a close failure doesn't hide the real error. It's about twelve lines for one file, and most real code got at least one detail wrong.
02The same thing with try-with-resources
Declare the resource in parentheses after try. Its scope is the try block. When the block ends, for any reason, close() is called for you.
You can still add catch and finally. They run after the resources are closed.
static String firstLine(String path) throws IOException {
try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
return reader.readLine();
} // reader.close() runs here, even if readLine() throws or we return
}03Several resources: reverse order
Resources are opened left to right and closed right to left, like a stack of plates. That order matters: an output stream wrapped in a compressor must be closed before the file underneath it, so the compressor can flush its last bytes into the file.
If the second resource fails to open, the first one (already open) is still closed. Only resources that were successfully initialised get closed.
04What javac generates
try-with-resources is syntactic sugar: the compiler rewrites it into ordinary try/catch/finally code (JLS 14.20.3). The shape is roughly what's shown here. Look at the addSuppressed call: that's the line nobody wrote by hand before Java 7.
Since JDK 11, javac generates slightly more compact code than this, but the behaviour is exactly as specified.
final BufferedReader reader = new BufferedReader(new FileReader(path));
Throwable primary = null;
try {
return reader.readLine();
} catch (Throwable t) {
primary = t;
throw t;
} finally {
if (reader != null) {
if (primary != null) {
try {
reader.close();
} catch (Throwable closeFailure) {
primary.addSuppressed(closeFailure); // keep both, primary wins
}
} else {
reader.close();
}
}
}05Suppressed exceptions in a stack trace
When both the block and close() fail, the trace shows the primary exception and each suppressed one, indented, under Suppressed:. This is the information that used to vanish.
06Write your own resource
Implement AutoCloseable and put the clean-up in close(). Override it with a narrower throws clause (or none), so users don't have to catch Exception.
A common use is a scoped action: start something in the constructor, end it in close(). A lock guard, a timer, a temporary change to a setting, a database transaction that rolls back unless committed.
class Stopwatch implements AutoCloseable {
private final String task;
private final long start = System.nanoTime();
private boolean closed;
Stopwatch(String task) { this.task = task; }
@Override
public void close() { // no 'throws Exception': callers stay simple
if (closed) return; // idempotent
closed = true;
System.out.println(task + " took " + (System.nanoTime() - start) / 1_000_000 + " ms");
}
}
try (Stopwatch sw = new Stopwatch("import")) {
runImport();
}07The wrapper trap
In new BufferedReader(new FileReader(path)), only the BufferedReader is a declared resource. If the BufferedReader constructor threw after the FileReader was opened, the FileReader would never be closed. For BufferedReader that can't realistically happen, but some wrappers can fail in their constructor (ObjectInputStream reads a header from the stream; GZIPInputStream does too).
The safe habit for wrappers that do work in their constructor is one resource per layer: try (InputStream in = Files.newInputStream(p); GZIPInputStream gz = new GZIPInputStream(in)) { ... }. Closing the outer stream also closes the inner one, and closing an already closed stream is harmless.
Try it yourself
- 1
Fail while opening
In the first example, make the constructor throw when the name is
"B"(if (name.equals("B")) throw new IllegalStateException("cannot open B");, after the print). Predict which resources get closed and which lines print. Then run. - 2
Only close fails
In the suppressed example, the
Cblock shows what happens when onlyclose()fails. Change the first block's body so it doesn't throw (printbody okinstead). Predict the primary message and the number of suppressed exceptions. - 3
Reassign the variable
In
countLines, addreader = new BufferedReader(new StringReader("x"));before thetry (reader). Predict the compiler error, compile, then remove it.
Code & diagrams
Both resources are closed before the catch block runs.
Expected output
open A
open B
use A
use B
close B
close A
catch: boom
finally
-- null resource
body runs, null is skipped on closeExpected output
primary: body failed
suppressed: close failed: B
suppressed: close failed: A
body ok
primary: close failed: C, suppressed: 0Expected output
first line: tea
lines: 3
after close: Stream closed
written and flushed by closeFiles.lines keeps the file open until the stream is closed; always use it in try-with-resources.
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Stream;
static void copyNonBlankLines(Path from, Path to) throws IOException {
try (Stream<String> lines = Files.lines(from); // a Stream is AutoCloseable too
BufferedWriter out = Files.newBufferedWriter(to)) {
for (String line : (Iterable<String>) lines::iterator) {
if (!line.isBlank()) { // Java 11
out.write(line);
out.newLine();
}
}
} // out closed first (flushes), then the file behind 'lines'
}Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Use a type that isn't AutoCloseable
Write try (String s = "x") { System.out.println(s); }.
Break #2
A close() that throws Exception
Write class Timer implements AutoCloseable { public void close() throws Exception { } } and use it in try-with-resources in a method with no throws clause.
Break #3
Use a variable that is reassigned
In Java 9+ code, assign r twice and then write try (r) { }.
Myth vs fact
Myth
The garbage collector closes files for me.
Fact
GC reclaims memory, not OS handles, and it runs whenever it likes. Finalizers that closed resources were unreliable and are deprecated for removal (JEP 421). Cleaner exists as a safety net only. Close resources explicitly.
Myth
The catch block of a try-with-resources can still use the resource.
Fact
Resources are closed before catch and finally run, and the resource variable isn't in scope there anyway. Do any work with the resource inside the try block.
Myth
Suppressed exceptions are lost.
Fact
They're attached to the primary exception: getSuppressed() returns them and printStackTrace prints them under Suppressed:. Make sure your logging framework prints them too (the major ones do).
Myth
try-with-resources only works for files.
Fact
It works for anything that implements AutoCloseable: JDBC Connection/Statement/ResultSet, sockets, streams from Files.lines, ExecutorService (Java 19+), Scanner, and your own classes.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
JLS 14.20.3 defines the basic statement precisely and specifies that an extended form (with catch or finally) is translated into a basic try-with-resources nested inside an ordinary try. That's why resources are already closed when your catch runs. Since JDK 11 (JDK-8194978), javac emits a compact form that calls
close()in fewer places, reducing bytecode size; behaviour is unchanged. - ▸
addSuppressedis ignored when the primary exception was created withenableSuppression = false(the protected Java 7 constructor), andaddSuppressed(this)throwsIllegalArgumentException: Self-suppression not permitted. Since JDK 9,printStackTracealso detects cycles among suppressed exceptions. - ▸
Closing order is a correctness issue with layered streams:
GZIPOutputStream.close()writes the GZIP trailer, then closes the underlying stream. Closing only the innerFileOutputStreamproduces a truncated archive with no error. try-with-resources over each layer closes the outermost first, which is correct. - ▸
ExecutorServiceimplementsAutoCloseablesince Java 19; itsclose()waits for submitted tasks to finish. That makestry (var executor = Executors.newVirtualThreadPerTaskExecutor()) { ... }(Java 21) the idiomatic structured way to run many virtual threads, which you'll meet in the concurrency phase.
Remember this
- 1
A resource is an object that holds something outside the JVM's memory: an open file handle, a network socket, a database connection, a lock. The garbage collector frees memory, but it does not promptly give back file handles or connections. If you forget
close(), the program slowly runs out of them (Too many open files, an exhausted connection pool) and fails, sometimes hours later. - 2
The syntax:
try (BufferedReader r = new BufferedReader(new FileReader(path))) { ... }. Each resource's type must implement **java.lang.AutoCloseable** (Java 7), whose single method isvoid close() throws Exception.java.io.Closeable(whoseclose()throwsIOException) was retrofitted to extend it, so every stream, reader, writer and channel in the JDK works. You can declare several resources separated by;. - 3
The rules: resources are initialised left to right; the block runs; then the resources that were successfully created are closed in reverse order, whether the block ended normally, by
return, or by an exception. Closing happens before anycatchorfinallyyou attached runs, so a catch block always sees the resources already closed. A resource that isnullis simply skipped on close. - 4
Suppressed exceptions solve Topic 7.2's lost-exception problem. If the block throws exception A and then
close()throws B, Java keeps A as the exception that propagates and attaches B withA.addSuppressed(B). You can read them withgetSuppressed(), and stack traces print them underSuppressed:. If onlyclose()fails, its exception propagates normally. - 5
Since Java 9, you can use an existing variable instead of declaring a new one:
try (reader) { ... }, as long as the variable isfinalor effectively final. Resource variables declared in the header are implicitlyfinal. Becauseclose()may throw a checked exception, the compiler applies catch-or-declare to it: a resource whoseclose()declaresthrows Exceptionforces you to handleException. - 6
Any class can be a resource. Implement
AutoCloseablefor your own types that need clean-up (a timer that logs elapsed time, a lock guard, a temporary directory). Makeclose()safe to call twice (idempotent), whichCloseablerequires andAutoCloseablestrongly recommends, and declare a narrower exception (or none) thanExceptionso callers aren't forced to catch everything.
Explain it without notes
What problem does try-with-resources solve, and what does it require of a resource?
In what order are resources closed, and when relative to catch and finally?
What are suppressed exceptions, and how do you read them?
What changed in Java 9 for try-with-resources?
How would you design your own class to be used as a resource?
Practice
Write a Connection-like class FakeDb implementing AutoCloseable that prints connect, query <sql> and disconnect, and refuses queries after close with IllegalStateException. Use it in try-with-resources, then try a query on it after the block.
Write static String join(String... parts) that builds the result through a StringWriter wrapped in a PrintWriter declared as two resources, separating parts with |. Print join("a", "b", "c").
Two resources: Gate("outer") and Gate("inner"), where the inner one's close() throws. The body throws IllegalStateException("work failed"). Predict and print the primary message and the suppressed messages.
Trade-offs
- ↔
try-with-resources ties a resource's lifetime to a block, which is perfect for short use; resources that must live longer (a connection pool, a long-lived client) need explicit lifecycle management, usually closed at application shutdown.
- ↔
Declaring each wrapper layer as its own resource is the safest for wrappers whose constructors do I/O, but adds lines; a single declaration with nested
newis fine for wrappers likeBufferedReaderwhose constructors can't fail. - ↔
A custom
AutoCloseablefor scoped actions (timers, lock guards) reads nicely, butclose()always runs, even on failure; for 'commit only on success' logic you must track success yourself (for example acommit()call setting a flag thatclose()checks before rolling back).
Done when you can
Done when you use try-with-resources for every file, stream, socket and connection you open.
Done when you can predict the open/close order with several resources, including when one fails to open.
Done when you can read suppressed exceptions from code and from a stack trace.
Done when you can write an idempotent
AutoCloseableclass with a narrowclose()signature.Done when you can use the Java 9 variable form and explain the effectively final rule.