RizTech Academy logo
RizTech Academy
Exceptions and Error HandlingLesson 5 of 620 min

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

  1. Write an AutoCloseable. A Resource class printing on open, use and close. Use two in one try-with-resources and confirm the closing order.

  2. Throw from the body. Confirm the resource is still closed, and that it is closed before your catch block runs.

  3. Make close() throw as well. Print the primary exception and everything from getSuppressed(). Then rewrite the same thing with a hand-written finally and confirm the body's exception disappears entirely.

  4. Convert an old block. Find or write a try/finally that closes something by hand, convert it, and count the lines removed.

  5. Leak a file handle. Call Files.lines without 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 with ulimit -n 256 in the shell first.

  6. Harder — a resource that must be closed in order. Write a DeliveryFileWriter that opens a file, writes a header on construction, and writes a footer with a total in close(). 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 from close() 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