try-with-resources and closing things properly
Files, network connections, database handles: things the operating system gives you a limited number of and expects back. Forget to return one and a long-running server eventually fails with "too many open files" — hours after the code that leaked it ran.
Java has a construct that makes this automatic. It is also, as a bonus, the fix for an exception-losing bug in the old way of doing it.
The old way
Resource r = new Resource("E");
try {
r.use();
throw new IllegalStateException("body failed");
} finally {
r.close();
}
Correct, verbose, and it has a hole. If close() also throws, the exception
from close() replaces the one from the body — and the body's exception, which
is the one that tells you what actually went wrong, is gone.
With two resources the correct hand-written version needs nested try/finally
blocks, a null check on each, and a try around each close. Almost nobody
wrote it correctly.
try-with-resources
try (Resource a = new Resource("A"); Resource b = new Resource("B")) {
a.use();
b.use();
}
opened A
opened B
used A
used B
closed B
closed A
Resources declared in the brackets are closed automatically when the block ends.
In reverse order of opening — which matters when one wraps another, as a
BufferedWriter wraps a FileWriter.
And they are closed whether the block completes or throws:
try (Resource c = new Resource("C")) {
c.use();
throw new IllegalStateException("body failed");
} catch (IllegalStateException e) {
System.out.println("caught: " + e.getMessage());
}
opened C
used C
closed C
caught: body failed
Note the order: closed before the catch ran. The resource is released as
control leaves the try block, before any catch or finally you attached.
Suppressed exceptions
Here is the part the old way got wrong. When the body throws and close()
throws:
try (Resource d = new FailingResource("D")) {
d.use();
throw new IllegalStateException("body failed first");
} catch (Exception e) {
System.out.println("primary : " + e.getMessage());
for (Throwable s : e.getSuppressed()) {
System.out.println("suppressed: " + s.getMessage());
}
}
primary : body failed first
suppressed: close() failed for D
The body's exception wins, and the close failure is attached rather than
discarded. Both survive. In a printed stack trace the second appears under
Suppressed:, below the main trace.
That is the right priority — the body's failure is almost always the real problem — and getting it right by hand is fiddly enough that nobody should.
AutoCloseable
Anything in the brackets must implement AutoCloseable:
public interface AutoCloseable {
void close() throws Exception;
}
Your own classes can:
static class Resource implements AutoCloseable {
@Override
public void close() {
System.out.println("closed " + name);
}
}
Declare close() without throws if it cannot fail — an override may narrow
the exception list, and it saves every caller from handling an Exception that
never comes.
Closeable is the older java.io interface; it extends AutoCloseable and
narrows close() to throws IOException. Implement AutoCloseable unless you
are writing a stream.
Things in the JDK that are AutoCloseable and that you will meet:
InputStream, OutputStream, Reader, Writer, Scanner, Connection,
Statement, ResultSet, and the Stream returned by Files.lines.
Effectively final resources
Since Java 9, an already-declared variable can be used directly:
Resource existing = openIt();
try (existing) {
existing.use();
}
It must be final or effectively final — the same rule as lambda capture, for a related reason: the construct must be sure the variable still refers to the thing it is going to close.
Where it matters most
Files.lines returns a Stream that holds an open file handle. This is the
one people miss, because a stream does not look like a resource:
try (Stream<String> lines = Files.lines(path)) {
return lines.filter(l -> !l.isBlank()).toList();
}
Without the try-with-resources the handle stays open until garbage collection,
and a loop over a few thousand files exhausts the operating system's limit.
Files.readAllLines does not have this problem — it reads everything and closes
— which is the right choice unless the file is too large to hold in memory.
Do not wrap a Scanner on System.in. As the input lesson said, closing it
closes System.in for the rest of the program's life. That is the one case where
the usual advice is wrong.
Check your work
In what order are resources closed? Reverse order of declaration, which matters when one wraps another.
When are they closed relative to a catch block? Before it. The resource is
released as control leaves the try block.
What happens when both the body and close() throw? The body's exception
propagates; the close failure is attached to it and retrievable with
getSuppressed(). In a printed trace it appears under Suppressed:.
What did the old finally version do in the same situation? The close
exception replaced the body's, losing the one that actually mattered.
What must a resource implement? AutoCloseable. Declare close() without
throws if it cannot fail.
Why does Files.lines need a try-with-resources when Files.readAllLines does
not? Files.lines returns a lazy stream holding an open file handle;
readAllLines reads everything and closes immediately.
Which resource should you not close? A Scanner wrapping System.in —
closing it closes standard input for the rest of the program.
Practice 3, the suppressed exception. With try-with-resources you get
primary: body failed first and suppressed: close() failed for D. With the
hand-written finally calling close(), the close failure replaces the body's
and body failed first never appears anywhere — no log line, no trace, nothing
to search for. That silent replacement is the bug the construct exists to fix.
Practice 5, the file handle. Files.lines without a try-with-resources will
usually appear to work, because the garbage collector eventually closes the
handle. It fails when you open thousands in a loop, with Too many open files —
and the stack trace points at the open that failed, not at any of the ones that
leaked. That distance is what makes resource leaks hard.
Practice
-
Write an
AutoCloseable. AResourceclass printing on open, use and close. Use two in one try-with-resources and confirm the closing order. -
Throw from the body. Confirm the resource is still closed, and that it is closed before your
catchblock runs. -
Make
close()throw as well. Print the primary exception and everything fromgetSuppressed(). Then rewrite the same thing with a hand-writtenfinallyand confirm the body's exception disappears entirely. -
Convert an old block. Find or write a
try/finallythat closes something by hand, convert it, and count the lines removed. -
Leak a file handle. Call
Files.lineswithout closing it, in a loop over the same file a few thousand times, and see what happens. Then add the try-with-resources. If your machine's limit is high, lower it withulimit -n 256in the shell first. -
Harder — a resource that must be closed in order. Write a
DeliveryFileWriterthat opens a file, writes a header on construction, and writes a footer with a total inclose(). Use it in a try-with-resources, throw halfway through the body, and confirm the footer is still written and the file is valid. Then decide whether writing a footer fromclose()is a good idea at all — it is the kind of thing that seems clever until the body fails and the footer records a total for data that was never written.
Next: null itself, and designing so that NullPointerException cannot happen.
Stuck on this lesson?
Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.
About the internship