try-with-resources is a Java construct that closes every resource you declare in its header for you — on the happy path, on the exception path, and on an early return. You declare the resource where it is created, list it after the try keyword, and the compiler guarantees the closing code runs even if the body throws.
What does try-with-resources actually promise?
Any object that implements AutoCloseable can be used this way. The interface has a single method, close(), and the construct calls it when the block ends. That covers the obvious cases — files, streams, sockets, database connections — and anything you write yourself.
public class CloseableDemo {
static class Connection implements AutoCloseable {
private final String name;
Connection(String name) {
this.name = name;
System.out.println("opened " + name);
}
@Override
public void close() {
System.out.println("closed " + name);
}
}
public static void main(String[] args) {
try (Connection connection = new Connection("db-primary")) {
System.out.println("using " + connection.name);
}
System.out.println("after the block");
}
}
opened db-primary
using db-primary
closed db-primary
after the block
Note that the close happens before the line after the block, not at the end of the method. The resource's scope is exactly the block, and the closing happens the moment the block is left — which makes the lifetime of the object obvious to anyone reading the code.
What does the old manual pattern get wrong?
Before this construct existed, the pattern was: create the resource, wrap the work in try, close in a finally block. The problem is that people write the finally block incorrectly all the time, and the failure mode is a resource that stays open forever.
public class ManualCloseDemo {
static class Connection {
void close() {
System.out.println("closed db-primary");
}
}
public static void main(String[] args) {
Connection connection = new Connection();
try {
System.out.println("querying");
throw new RuntimeException("query failed");
} catch (RuntimeException e) {
System.out.println("caught: " + e.getMessage());
// the classic bug: the connection is never closed on this path
} finally {
System.out.println("finally block ran");
}
System.out.println("program ends without closing the connection");
}
}
querying
caught: query failed
finally block ran
program ends without closing the connection
Notice that "closed db-primary" never appears. The close call was simply left out of the catch path, so the connection leaks. Leaked connections and files are not tidy little problems: they hold locks, they consume memory, and eventually the operating system refuses to open more.
How does it behave with more than one resource?
You declare them all in the header, separated by semicolons, and they are closed in reverse order — the last one declared is the first one closed. That ordering matters because a resource often depends on the one declared before it.
public class SuppressedDemo {
static class Resource implements AutoCloseable {
private final String name;
private final boolean failOnClose;
Resource(String name, boolean failOnClose) {
this.name = name;
this.failOnClose = failOnClose;
}
@Override
public void close() {
System.out.println("closing " + name);
if (failOnClose) {
throw new IllegalStateException(name + " could not close cleanly");
}
}
}
public static void main(String[] args) {
try (Resource first = new Resource("first", true);
Resource second = new Resource("second", true)) {
System.out.println("working with both");
} catch (IllegalStateException e) {
System.out.println("primary failure: " + e.getMessage());
for (Throwable extra : e.getSuppressed()) {
System.out.println("suppressed: " + extra.getMessage());
}
}
}
}
working with both
closing second
closing first
primary failure: second could not close cleanly
suppressed: first could not close cleanly
Both resources are closed even though the first close threw. The exception from the resource closed first becomes the one you catch, and the second failure is attached to it as a suppressed exception, retrievable with getSuppressed(). Nothing is lost, which is more than the old manual pattern could promise.
How does it work with real files?
Files are the everyday case. A reader is created in the header, the body does the reading, and the file handle is released whether the read succeeds or not.
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class FileReadDemo {
public static void main(String[] args) throws IOException {
Path notes = Path.of("notes.txt");
Files.writeString(notes, "first line\nsecond line\nthird line\n");
try (var reader = Files.newBufferedReader(notes)) {
String line;
int count = 0;
while ((line = reader.readLine()) != null) {
count++;
System.out.println(count + ": " + line);
}
}
Files.delete(notes);
System.out.println("file removed");
}
}
1: first line
2: second line
3: third line
file removed
Save it as FileReadDemo.java, compile it, and run it from a directory where you want the temporary notes.txt to appear:
javac FileReadDemo.java
java FileReadDemo
The var keyword keeps the declaration short while the variable stays strongly typed. If reading can fail for reasons the caller should handle, add throws IOException to the method signature or catch it at the call site — the construct does not decide that for you, it only guarantees the closing. Notice also that there is no catch or finally in that example, and there does not need to be one: a resource block is valid on its own, and it is the right shape whenever the only thing a finally block would have contained was a close call.
Can you use a resource you already have?
Yes. The variable just has to be final or effectively final, because the compiler rewrites the block into something like a try-finally around your code. That means you cannot reassign the variable inside the block, which is a small constraint and a good thing: it stops you from closing a resource and then continuing to use it.
One thing the construct does not do is swallow problems. If the body throws and the close also throws, the body failure stays the primary exception and the close failure becomes suppressed, so you never lose the original problem. And a resource whose close throws does not prevent the remaining resources from being closed, which is exactly the behaviour you want when a batch of files is being written.
One limitation is worth knowing: only objects that implement AutoCloseable can be listed in the header. A class that exposes a close() method but does not implement the interface cannot be used this way, so either implement the interface on it or keep the manual finally pattern for that one case. Most well-behaved resource classes implement it already, so this rarely comes up in practice.
The parts you will reuse
- The resource must implement
AutoCloseable;close()is called automatically when the block ends. - Resources are closed in reverse order of declaration, no matter which path ends the block.
- Failures during closing are attached as suppressed exceptions instead of replacing the real error.
- Prefer this over a manual finally block, because the manual version is easy to get wrong.
- Closing a resource does not decide what to do about failures — handle or declare those yourself.
Try it yourself: write a class Timer that implements AutoCloseable and prints "started" in its constructor and "stopped" in close(). Use it in a try-with-resources block, then throw a RuntimeException inside the block and catch it outside, confirming from the output that "stopped" is printed before the catch message. Then make close() throw an exception as well and print the suppressed exceptions to confirm you can still see both failures.