Java Try-with-Resources

Try-with-resources is a special form of the try statement, introduced in Java 7, that automatically closes any resource implementing AutoCloseable once the block finishes — whether it finishes normally or because of an exception. Before Java 7, closing files, database connections, sockets, and streams required verbose try/finally blocks, and it was easy to accidentally skip a close() call when an exception struck at the wrong moment. Try-with-resources moves that cleanup logic into the language itself, so you can no longer forget it.

Overview / How It Works

Any class that implements the java.lang.AutoCloseable interface can be used as a resource in a try-with-resources statement. AutoCloseable declares a single method, close(), which is called automatically when control leaves the try block. A related, older interface, java.io.Closeable (used by streams and readers/writers), extends AutoCloseable but narrows the exception thrown by close() to IOException. Both work identically with try-with-resources.

You declare one or more resources inside the parentheses right after the try keyword. Each resource is opened in the order it’s written, and each is closed automatically in the reverse order — the same way stack frames unwind. This matters when resources depend on each other (for example, a buffered stream wrapping a file stream): you want the wrapper closed before the thing it wraps.

Since Java 9, you don’t have to declare a brand-new variable inside the try header — you can reference an existing effectively final variable (one that is never reassigned after initialization). Whether declared inline or reused, the compiler treats the resource variable as final for the scope of the try block, which is why you cannot reassign it inside the body.

Internally, the compiler desugars a try-with-resources statement into an ordinary try/finally block. Each resource gets a hidden finally clause that null-checks it and calls close(). If the try body throws an exception and a resource’s close() also throws, the close-time exception is not lost — it is attached to the original exception via addSuppressed() and can be retrieved later with getSuppressed(). This is one of the most important, and most overlooked, behaviors of the feature.

Syntax

try (ResourceType resource1 = new ResourceType(...);
     ResourceType resource2 = new ResourceType(...)) {
    // use resource1 and resource2
} catch (ExceptionType e) {
    // optional - handle exceptions from the body or from opening a resource
} finally {
    // optional - runs after all resources have been closed
}
Part Meaning
(ResourceType r = ...; ...) One or more resource declarations, separated by semicolons; each type must implement AutoCloseable.
Resource variable Implicitly (or explicitly) final — it cannot be reassigned inside the try block.
Closing order Resources close in the reverse order they were declared, after the try block ends (normally or via exception).
catch Optional; runs after resources are closed, and can catch exceptions from the body, from opening a resource, or from close() itself.
finally Optional; runs after the resources are closed and after any catch block.

Examples

Example 1: A basic custom resource

public class Main {
    static class Connection implements AutoCloseable {
        private final String name;

        Connection(String name) {
            this.name = name;
            System.out.println("Opening connection: " + name);
        }

        void query(String sql) {
            System.out.println("Running query on " + name + ": " + sql);
        }

        @Override
        public void close() {
            System.out.println("Closing connection: " + name);
        }
    }

    public static void main(String[] args) {
        try (Connection conn = new Connection("db1")) {
            conn.query("SELECT * FROM users");
        }
        System.out.println("Done.");
    }
}

Output:

Opening connection: db1
Running query on db1: SELECT * FROM users
Closing connection: db1
Done.

The Connection is opened when the try statement runs, used inside the block, and then close() is invoked automatically as soon as the block ends — no explicit close() call was written anywhere in main.

Example 2: Multiple resources close in reverse order

public class Main {
    static class Resource implements AutoCloseable {
        private final String name;

        Resource(String name) {
            this.name = name;
            System.out.println("Open " + name);
        }

        @Override
        public void close() {
            System.out.println("Close " + name);
        }
    }

    public static void main(String[] args) {
        try (Resource a = new Resource("A");
             Resource b = new Resource("B");
             Resource c = new Resource("C")) {
            System.out.println("Using A, B, C");
        }
    }
}

Output:

Open A
Open B
Open C
Using A, B, C
Close C
Close B
Close A

Notice that even though A, B, and C were opened in that order, they close in the reverse order: C, then B, then A. This matters whenever one resource depends on another still being open during cleanup.

Example 3: Reading text with a real I/O resource

import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;

public class Main {
    public static void main(String[] args) {
        String data = "line one\nline two\nline three";

        try (BufferedReader reader = new BufferedReader(new StringReader(data))) {
            String line;
            int count = 0;
            while ((line = reader.readLine()) != null) {
                count++;
                System.out.println(count + ": " + line);
            }
        } catch (IOException e) {
            System.out.println("Failed to read: " + e.getMessage());
        }
    }
}

Output:

1: line one
2: line two
3: line three

This mirrors real-world usage: a BufferedReader wraps another source (here an in-memory StringReader; in practice often a FileReader) and is guaranteed to be closed after the loop, even if readLine() throws an IOException.

Under the Hood

When javac compiles a try-with-resources statement, it rewrites it roughly into nested try/finally blocks with null checks, one per resource, innermost resource closed first. Conceptually, a single-resource version becomes something like: open the resource, run the try body in a nested try, and in its finally clause call resource.close() if the resource reference isn’t null.

The tricky part is exception interaction. If the try body throws an exception and then close() also throws while cleaning up, Java doesn’t discard either one. The exception from the body becomes the primary exception that propagates, and the exception from close() is recorded as a suppressed exception on it, retrievable via Throwable.getSuppressed(). If the body completes normally but close() throws, that exception propagates normally as the only exception. This design ensures you never silently lose information about a failed cleanup.

Common Mistakes

Mistake 1: Manually closing instead of using try-with-resources

Wrong — if an exception happens between opening and the manual close() call, the resource never gets closed:

public class Main {
    static class Resource implements AutoCloseable {
        Resource() {
            System.out.println("Open");
        }

        void doWork() {
            throw new RuntimeException("boom");
        }

        @Override
        public void close() {
            System.out.println("Close");
        }
    }

    public static void main(String[] args) {
        try {
            Resource r = new Resource();
            r.doWork();
            r.close();
        } catch (RuntimeException e) {
            System.out.println("Caught: " + e.getMessage());
        }
        System.out.println("Done");
    }
}

Output:

Open
Caught: boom
Done

Notice "Close" never prints — doWork() threw before the manual r.close() line was reached, so the resource leaked. This is exactly the bug class try-with-resources exists to prevent.

Corrected — let try-with-resources guarantee the close:

public class Main {
    static class Resource implements AutoCloseable {
        Resource() {
            System.out.println("Open");
        }

        void doWork() {
            throw new RuntimeException("boom");
        }

        @Override
        public void close() {
            System.out.println("Close");
        }
    }

    public static void main(String[] args) {
        try (Resource r = new Resource()) {
            r.doWork();
        } catch (RuntimeException e) {
            System.out.println("Caught: " + e.getMessage());
        }
        System.out.println("Done");
    }
}

Output:

Open
Close
Caught: boom
Done

Now "Close" prints before the exception is caught — the resource is closed as the try block unwinds, regardless of the exception.

Mistake 2: Ignoring suppressed exceptions

If both the try body and close() throw, the exception from close() doesn’t vanish, but it’s easy to miss it if you don’t inspect getSuppressed():

public class Main {
    static class FaultyResource implements AutoCloseable {
        @Override
        public void close() {
            throw new RuntimeException("close failed");
        }
    }

    public static void main(String[] args) {
        try (FaultyResource r = new FaultyResource()) {
            throw new RuntimeException("body failed");
        } catch (RuntimeException e) {
            System.out.println("Caught: " + e.getMessage());
            for (Throwable suppressed : e.getSuppressed()) {
                System.out.println("Suppressed: " + suppressed.getMessage());
            }
        }
    }
}

Output:

Caught: body failed
Suppressed: close failed

The original body exception ("body failed") is what you catch; the close-time exception ("close failed") is attached as a suppressed exception rather than replacing it or being silently dropped.

Mistake 3: Reassigning the resource variable

Because the resource variable is implicitly final, trying to reassign it inside the try block is a compile error, not a runtime bug:

try (Resource r = new Resource("A")) {
    r = new Resource("B"); // compile error: cannot assign a value to final variable r
}

If you need a new resource mid-block, declare a second resource in the same try header instead, or restructure into nested try-with-resources statements.

Best Practices

  • Always prefer try-with-resources over manual close() calls for anything that implements AutoCloseable — it’s shorter and leak-proof.
  • When writing your own AutoCloseable classes, make close() idempotent (safe to call more than once) in case cleanup logic elsewhere also calls it.
  • Avoid throwing checked exceptions from your own close() methods unless truly necessary — it forces every caller to add a catch clause.
  • Declare all related resources in a single try-with-resources statement rather than nesting several try blocks; it keeps the reverse-closing order correct and the code flatter.
  • When debugging a cleanup failure that seems to disappear, always check e.getSuppressed() — the real cause of a close failure is often hiding there.
  • Don’t keep a resource open longer than needed; scope the try-with-resources statement as tightly as possible around the code that actually uses the resource.

Practice Exercises

  • Write a class Logger that implements AutoCloseable, printing "Logger opened" in its constructor and "Logger closed" in close(). Use it in a try-with-resources block to print three log messages between opening and closing.
  • Take Example 2 (resources A, B, C) and make resource B‘s close() throw a RuntimeException. Add a catch block and print any suppressed exceptions. Predict the full output before running it.
  • Write a try-with-resources statement with two resources where opening the second resource (in its constructor) throws an exception. Verify, using print statements, whether the first resource still gets closed.

Summary

  • Try-with-resources automatically calls close() on any resource implementing AutoCloseable, whether the block exits normally or via an exception.
  • Multiple resources declared in one try header are closed automatically in reverse order of declaration.
  • The resource variable is implicitly final and cannot be reassigned inside the try block.
  • If both the try body and a resource’s close() throw, the body’s exception is primary and the close exception is attached as a suppressed exception, retrievable with getSuppressed().
  • Prefer try-with-resources over manual try/finally cleanup — it removes an entire class of resource-leak bugs.