Command Palette

Search for a command to run...

PHASE 7Intermediate Java 7+ ~31 min· topic 6 of 8

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() throws IOException. It extends AutoCloseable.
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 final keyword.

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.

Main.javawhole filejava
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.

Several resources: reverse orderdiagram
Rendering diagram…

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.

Desugared.java (roughly what javac generates)whole filejava
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.

terminal
$ java Main.java
── expected output ──
Exception in thread "main" java.lang.RuntimeException: body failed
at Main.main(Main.java:12)
Suppressed: java.lang.IllegalStateException: close failed: B
at Main$Flaky.close(Main.java:7)
at Main.main(Main.java:11)
Suppressed: java.lang.IllegalStateException: close failed: A
at Main$Flaky.close(Main.java:7)
at Main.main(Main.java:11)

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.

Main.javawhole filejava
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. 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. 2

    Only close fails

    In the suppressed example, the C block shows what happens when only close() fails. Change the first block's body so it doesn't throw (print body ok instead). Predict the primary message and the number of suppressed exceptions.

  3. 3

    Reassign the variable

    In countLines, add reader = new BufferedReader(new StringReader("x")); before the try (reader). Predict the compiler error, compile, then remove it.

Code & diagrams

Open, use, close in reverse, then catch and finally Java 7+ New tab

Both resources are closed before the catch block runs.

Sign in to run this example in your browser.

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 close
Suppressed exceptions keep the real error on top Java 7+ New tab
Sign in to run this example in your browser.

Expected output

primary: body failed
  suppressed: close failed: B
  suppressed: close failed: A
body ok
primary: close failed: C, suppressed: 0
JDK resources and the Java 9 variable form Java 9+ New tab
Sign in to run this example in your browser.

Expected output

first line: tea
lines: 3
after close: Stream closed
written and flushed by close
Real files with NIO (fragment) Java 11+java

Files.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); }.

terminal
$ javac Main.java
── what you'll see ──
Main.java:3: error: incompatible types: try-with-resources not applicable to variable type
try (String s = "x") {
^
(String cannot be converted to AutoCloseable)
1 error

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.

terminal
$ javac Main.java
── what you'll see ──
Main.java:6: error: unreported exception Exception; must be caught or declared to be thrown
try (Timer t = new Timer()) {
^
exception thrown from implicit call to close() on resource variable 't'
1 error

Break #3

Use a variable that is reassigned

In Java 9+ code, assign r twice and then write try (r) { }.

terminal
$ javac Main.java
── what you'll see ──
Main.java:6: error: variable r used as a try-with-resources resource neither final nor effectively final
try (r) { }
^
1 error

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.

  • ▸

    addSuppressed is ignored when the primary exception was created with enableSuppression = false (the protected Java 7 constructor), and addSuppressed(this) throws IllegalArgumentException: Self-suppression not permitted. Since JDK 9, printStackTrace also 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 inner FileOutputStream produces a truncated archive with no error. try-with-resources over each layer closes the outermost first, which is correct.

  • ▸

    ExecutorService implements AutoCloseable since Java 19; its close() waits for submitted tasks to finish. That makes try (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. 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. 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 is void close() throws Exception. java.io.Closeable (whose close() throws IOException) was retrofitted to extend it, so every stream, reader, writer and channel in the JDK works. You can declare several resources separated by ;.

  3. 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 any catch or finally you attached runs, so a catch block always sees the resources already closed. A resource that is null is simply skipped on close.

  4. 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 with A.addSuppressed(B). You can read them with getSuppressed(), and stack traces print them under Suppressed:. If only close() fails, its exception propagates normally.

  5. 5

    Since Java 9, you can use an existing variable instead of declaring a new one: try (reader) { ... }, as long as the variable is final or effectively final. Resource variables declared in the header are implicitly final. Because close() may throw a checked exception, the compiler applies catch-or-declare to it: a resource whose close() declares throws Exception forces you to handle Exception.

  6. 6

    Any class can be a resource. Implement AutoCloseable for your own types that need clean-up (a timer that logs elapsed time, a lock guard, a temporary directory). Make close() safe to call twice (idempotent), which Closeable requires and AutoCloseable strongly recommends, and declare a narrower exception (or none) than Exception so callers aren't forced to catch everything.

Explain it without notes

01

What problem does try-with-resources solve, and what does it require of a resource?

02

In what order are resources closed, and when relative to catch and finally?

03

What are suppressed exceptions, and how do you read them?

04

What changed in Java 9 for try-with-resources?

05

How would you design your own class to be used as a resource?

Practice

01

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.

02

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").

03

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 new is fine for wrappers like BufferedReader whose constructors can't fail.

  • ↔

    A custom AutoCloseable for scoped actions (timers, lock guards) reads nicely, but close() always runs, even on failure; for 'commit only on success' logic you must track success yourself (for example a commit() call setting a flag that close() 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 AutoCloseable class with a narrow close() signature.

  • Done when you can use the Java 9 variable form and explain the effectively final rule.