Skip to content

try-with-resources Interview Questions & Answers

21 questions Updated 2026-06-20 Share:

Java try-with-resources interview questions — AutoCloseable and Closeable, automatic resource management, suppressed exceptions, close ordering, the effectively-final form, and comparison with finally.

Read the in-depth guideJava Try-With-Resources — Automatic Resource Management Explained(opens in new tab)
21 of 21

Try-with-resources (Java 7+) is a try statement that declares one or more resources in parentheses and closes them automatically when the block finishes — normally or via an exception. It replaces the error-prone manual-finally cleanup pattern, eliminating resource leaks.

// Automatic: br is closed no matter how the block exits
try (BufferedReader br = new BufferedReader(new FileReader("a.txt"))) {
    return br.readLine();
}

A resource is anything that must be released — files, sockets, DB connections, streams. Any object that implements AutoCloseable can be a resource. The problem it solves: developers routinely forgot the finally close, nested it wrong, or let a close() exception swallow the real one.

You had to declare the resource outside the try, close it in a finally, and null-check because the constructor might have failed. It was verbose and easy to get wrong.

BufferedReader br = null;
try {
    br = new BufferedReader(new FileReader("a.txt"));
    return br.readLine();
} finally {
    if (br != null) br.close();   // close itself can throw IOException
}

With two resources you'd nest two try/finally blocks (closing the outer one even if the inner constructor threw). Try-with-resources collapses all of that into one parenthesized declaration — less code, no leaks, and correct exception handling.

AutoCloseable (Java 7, in java.lang) is the single-method interface that makes a class usable as a try-with-resources resource. Its only method is close(), declared to throw the broadest checked exception, Exception.

public interface AutoCloseable {
    void close() throws Exception;
}

Only types implementing AutoCloseable (or its subtype Closeable) may appear in the resource list — anything else is a compile error. The compiler guarantees close() is invoked when the block exits, so implementing this interface is all it takes to plug a custom type into automatic resource management.

Closeable (Java 5, in java.io) predates try-with-resources and was retrofitted to extend AutoCloseable in Java 7, so every Closeable works as a resource. The differences are in the close() contract:

AutoCloseable Closeable
Package java.lang java.io
close() throws Exception (broad) IOException (narrow)
Idempotent? not required required — repeat calls have no effect
Use for any resource I/O streams
public interface Closeable extends AutoCloseable {
    void close() throws IOException;
}

Prefer implementing Closeable when your resource is I/O-related, and AutoCloseable for everything else (e.g. a lock or a transaction).

AutoCloseable is the general abstraction, so its close() throws Exception to allow any resource — a DB connection, a JNI handle — to report whatever failure it has. Closeable is I/O-specific, so it narrows to IOException, which is more precise for stream code.

// AutoCloseable: may throw any checked exception
void close() throws Exception;
// Closeable: I/O resources, narrower contract
void close() throws IOException;

An implementation can always narrow the declared exception (override throws Exception with throws IOException, or throws nothing at all). Throwing nothing is encouraged: a close() that can't fail is far easier to use safely.

Idempotent means calling close() a second (or third) time has no additional effect and doesn't throw. Closeable mandates this; AutoCloseable only recommends it.

Reader r = new FileReader("a.txt");
r.close();
r.close();   // safe — Closeable.close() is idempotent, does nothing

It matters because a resource can be closed both explicitly in your code and automatically by try-with-resources, and you don't want the second close to blow up. When you write your own close(), guarding it with a boolean closed flag makes it idempotent.

The compiler desugars it into an ordinary try/finally with the close call and suppressed-exception bookkeeping baked in. Roughly:

// try (Resource r = ...) { body }
// becomes:
Resource r = ...;
Throwable primary = null;
try {
    body;
} catch (Throwable t) {
    primary = t;
    throw t;
} finally {
    if (r != null) {
        if (primary != null) {
            try { r.close(); }
            catch (Throwable sup) { primary.addSuppressed(sup); }
        } else {
            r.close();   // body succeeded; let close throw normally
        }
    }
}

So close() is in a generated finally, but unlike hand-written finally it records a close() failure as a suppressed exception instead of discarding the original. That bookkeeping is the whole point.

In the reverse order of declaration — last declared is closed first. This mirrors how dependencies stack: a later resource often depends on an earlier one, so it must be torn down first (like nested finally blocks).

try (Resource a = new Resource("A");
     Resource b = new Resource("B");
     Resource c = new Resource("C")) {
    // ... use a, b, c
}
// close order: C, then B, then A

This LIFO order is guaranteed by the spec. For example, a BufferedWriter wrapping a FileWriter is closed before the FileWriter it depends on.

Declare them in the parentheses separated by semicolons; each is closed automatically. This avoids the deeply nested try/finally blocks the old style required.

try (FileInputStream in = new FileInputStream("src.txt");
     FileOutputStream out = new FileOutputStream("dst.txt")) {
    in.transferTo(out);
}   // out closed first, then in — both guaranteed

Each resource is independently closed: even if closing out throws, the runtime still closes in. A trailing semicolon after the last resource is allowed but optional.

When the try body throws and a resource's close() also throws, try-with-resources propagates the body's exception (the more useful one) and attaches the close() exception to it as a suppressed exception, retrievable via Throwable.getSuppressed().

try (var r = new FlakyResource()) {
    throw new RuntimeException("body failed");   // primary
}   // r.close() throws too -> attached as suppressed

// caught primary:
catch (Exception e) {
    for (Throwable s : e.getSuppressed())
        System.out.println("suppressed: " + s);  // close's exception
}

So no exception is lost: the primary surfaces, the secondary rides along. The stack trace prints suppressed ones under a Suppressed: heading.

In a hand-written try/finally, if both the body and close() throw, the finally's exception replaces the body's — the original failure is silently lost, hiding the real bug.

try {
    throw new RuntimeException("REAL cause");
} finally {
    throw new IOException("close failed");  // this WINS; "REAL cause" gone
}

Try-with-resources reverses the priority: the body's exception wins and the close() exception is recorded as suppressed rather than thrown over it. You get the genuine cause as the primary, with the cleanup failure preserved alongside instead of erased.

They're the two Throwable methods (Java 7) behind the suppressed-exception mechanism. addSuppressed(Throwable) attaches a secondary exception to a primary one; getSuppressed() returns the array of attached exceptions.

Throwable[] suppressed = primaryException.getSuppressed();

// what the compiler-generated finally effectively calls:
primaryException.addSuppressed(closeException);

Try-with-resources calls addSuppressed for you automatically, but the methods are public so you can use them manually. You can't suppress a throwable into itself, and passing null throws NullPointerException.

Before Java 9 you had to declare the resource inside the try parentheses. Java 9 lets you reference an already-declared variable there, as long as it's final or effectively final (never reassigned after init).

// Java 9+: reuse an existing effectively-final variable
BufferedReader br = new BufferedReader(new FileReader("a.txt"));
try (br) {           // no re-declaration needed
    return br.readLine();
}   // br still closed automatically

This avoids awkward redundant declarations like try (Reader r2 = r1). The variable must not be reassigned, or the compiler rejects it — the resource must be stable so the runtime closes the right object.

Yes. You can append catch and finally clauses just like a normal try. The key ordering rule: resources are closed first, then the catch/finally run. So a catch sees an already-closed resource.

try (var conn = open()) {
    conn.run();
} catch (SQLException e) {     // runs AFTER conn is closed
    log.error("db error", e);
} finally {
    System.out.println("done"); // runs LAST
}

This matters because the catch can handle exceptions thrown by close() too — by the time it executes, cleanup is complete and you only deal with what propagated out.

Implement AutoCloseable (or Closeable) and put release logic in close(). Make it idempotent and avoid throwing from close() where you can.

class Timer implements AutoCloseable {
    private final long start = System.nanoTime();
    private boolean closed = false;
    @Override public void close() {     // narrowed: throws nothing
        if (closed) return;             // idempotent guard
        closed = true;
        System.out.println("took " + (System.nanoTime() - start) + "ns");
    }
}

try (var t = new Timer()) { doWork(); }  // prints elapsed time on exit

This pattern is great for scoped concerns — timing, locks, transaction commit, MDC context — anything with a clear "enter then guaranteed exit."

If the body finishes without an exception, there's no primary exception to suppress against, so the close() exception is thrown normally out of the try statement. It becomes the exception the caller sees.

try (var r = new FlakyResource()) {
    // body succeeds, no exception here
}   // but close() throws IOException -> THIS propagates to the caller

So a successful body can still produce a failure from cleanup — which is why close() on something like a BufferedWriter (which flushes) genuinely matters: a failed final flush surfaces as a real, non-suppressed exception you must handle.

Yes — every exit path closes the resources: a normal fall-through, an early return, a break/continue out of the block, or a thrown exception. That guarantee is the entire value proposition; closing happens in the compiler-generated finally.

String first(BufferedReader br) throws IOException {
    try (br) {
        return br.readLine();   // br is STILL closed before returning
    }
}

Just like a finally, the close runs before the method actually returns or the exception escapes. There's no code path that can leak the resource.

Resources are initialized left to right. If a later resource's constructor throws, the resources already opened are closed (in reverse order) before the exception propagates — no leak.

try (Resource a = new Resource("A");      // opens OK
     Resource b = openThatThrows();        // throws here
     Resource c = new Resource("C")) {     // never created
    ...
}
// a IS closed; c was never opened; the constructor's exception propagates

This is exactly what the nested-try/finally style struggled to get right by hand. The runtime closes only what was successfully created, in LIFO order.

Try-with-resources is the modern replacement; reach for manual try/finally only for cleanup that isn't a closeable resource.

Aspect try-with-resources try/finally
Closes resource automatic manual close()
Both throw body wins, close suppressed finally wins, body lost
Null safety handled by compiler manual null check
Multiple resources one statement, LIFO nested blocks
Requires AutoCloseable type any cleanup code
try (var r = open()) { use(r); }    // preferred for resources

Use try/finally for non-resource teardown (resetting a flag, restoring thread state); use try-with-resources for anything implementing AutoCloseable.

Nothing breaks. The generated code null-checks each resource before calling close(), so a null resource is simply skipped — no NullPointerException at close time.

try (Reader r = mightReturnNull()) {   // returns null
    if (r != null) r.read();           // your own guard still needed for use
}   // close() is NOT called on null — safe

Note the guard only protects the close, not your use of the resource inside the body — dereferencing a null r there still throws. This null-tolerance is one of the boilerplate cases the old manual if (x != null) x.close() pattern had to handle by hand.

A Lock is not AutoCloseable, and more importantly the lock must be acquired before the try, not in it — otherwise a failure to acquire would still trigger an unlock. The correct idiom keeps lock() outside and unlock() in a finally.

lock.lock();                  // acquire BEFORE try
try {
    criticalSection();
} finally {
    lock.unlock();            // try/finally, NOT try-with-resources
}

You can wrap a lock in a small AutoCloseable helper that unlocks in close(), but you must still acquire before entering the try. This is a classic interview "when is try-with-resources the wrong tool" question — its answer is manual acquisition + try/finally.

More ways to practice

The self-quiz is live. Get notified when mock interviews and new question packs drop.

or
Join our WhatsApp Channel