Java Tutorial
🔍

Java try-with-resources

Java try-with-resources

try-with-resources is the compiler-managed solution to the problem of reliably closing resources after use. Declare a resource in the parentheses after try, use it in the block, and the compiler generates the close call automatically — whether the block completes normally, throws an exception, or hits a return. The two failure modes of manual try-finally resource management — forgetting to close and silently swallowing the original exception when close() itself throws — are both handled correctly by try-with-resources.

What Is try-with-resources?

Introduced in Java 7, try-with-resources is a try block with a resource declaration list in parentheses. Any object declared there must implement java.lang.AutoCloseable. The compiler generates code that calls close() on each declared resource when the try block exits, in reverse order of declaration, regardless of how the block exits.

SYNTAX:
  try (ResourceType resource = new ResourceType(...)) {
      // use resource
  }
  // resource.close() called automatically here

MULTIPLE RESOURCES:
  try (ResourceType1 r1 = new ResourceType1();
       ResourceType2 r2 = new ResourceType2()) {
      // use r1 and r2
  }
  // r2.close() called first, then r1.close() — REVERSE order of declaration

COMBINED WITH catch AND finally:
  try (Connection conn = dataSource.getConnection()) {
      Statement stmt = conn.createStatement();
      return stmt.executeQuery(sql);
  } catch (SQLException sqlException) {
      throw new DataAccessException("Query failed", sqlException);
  } finally {
      metrics.recordQueryAttempt(); // always runs — even after auto-close
  }
  // conn.close() is called BEFORE catch and finally run

JAVA 9 ENHANCEMENT — effectively final variable:
  Connection conn = dataSource.getConnection();
  // conn must be effectively final (never reassigned after this line)
  try (conn) { // no re-declaration needed
      return conn.createStatement().executeQuery(sql);
  }
  // conn.close() called automatically

Basic Overview — Why try-with-resources Exists

THE TWO PROBLEMS WITH MANUAL try-finally:

PROBLEM 1 — Forgotten close():
  Connection conn = dataSource.getConnection();
  try {
      return conn.createStatement().executeQuery(sql);
  } finally {
      // Developer forgot conn.close() — connection leaks every call
      // After enough calls: "Too many connections" error in production
  }

PROBLEM 2 — Exception suppression in finally:
  Connection conn = dataSource.getConnection();
  try {
      conn.executeQuery(sql);       // throws SQLException("Query failed")
  } finally {
      conn.close();                 // ALSO throws IOException("Socket error")
      // IOException from close() REPLACES the original SQLException
      // Caller receives "Socket error" — the real cause "Query failed" is LOST
  }

HOW try-with-resources SOLVES BOTH:

  SOLUTION TO PROBLEM 1:
  Compiler generates the close() call — you cannot forget it
  Even if a developer never writes conn.close(), the bytecode has it

  SOLUTION TO PROBLEM 2:
  If try body throws AND close() throws, try-with-resources:
    — Keeps the original exception as the primary exception
    — Attaches the close() exception as a SUPPRESSED exception
    — Callers retrieve it with: primaryException.getSuppressed()
  The original failure is never lost

AUTOCLOSEABLE CONTRACT:
  public interface AutoCloseable {
      void close() throws Exception;
  }
  Any class implementing this can be used in try-with-resources.
  Closeable extends AutoCloseable (close() throws IOException specifically).
  All JDK I/O classes, JDBC types, and many libraries implement one of these.

How try-with-resources Works Internally

The Java compiler transforms a try-with-resources block into an equivalent try-finally structure — but one that handles exception suppression correctly.

COMPILER TRANSFORMATION (Java 7+):

  SOURCE (simple form):
    try (Connection conn = dataSource.getConnection()) {
        return conn.executeQuery(sql);
    }

  GENERATED BYTECODE (equivalent):
    Connection conn = dataSource.getConnection();
    Throwable primaryException = null;
    try {
        return conn.executeQuery(sql);
    } catch (Throwable tryException) {
        primaryException = tryException;
        throw tryException;
    } finally {
        if (conn != null) {
            if (primaryException != null) {
                try {
                    conn.close();
                } catch (Throwable closeException) {
                    // SUPPRESSION: close exception attached to primary — NOT replacing it
                    primaryException.addSuppressed(closeException);
                }
            } else {
                conn.close(); // no try exception — close exception propagates normally
            }
        }
    }

  KEY INSIGHT:
  The compiler generates the primaryException variable to track whether
  the try body threw. If it did, close() exceptions are suppressed onto it.
  If the try body succeeded, a close() exception propagates normally.
  This is the suppression handling that manual try-finally cannot do easily.

MULTIPLE RESOURCES — reverse close order:
  try (A a = new A(); B b = new B()) { ... }

  Generates:
    A a = new A();
    try {
        B b = new B();
        try { body } finally { b.close(); }  ← inner: B closed first
    } finally { a.close(); }                 ← outer: A closed after B
  The last declared resource is closed first — reverse order.

Core Operations with Examples

Single Resource — The Simplest Form

// File: TryWithResourcesBasicDemo.java import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.io.StringReader; public class TryWithResourcesBasicDemo { // BufferedReader implements Closeable (extends AutoCloseable) // close() is called automatically when the try block exits static String readFirstLine(String content) throws IOException { // StringReader simulates a file reader for this demo try (BufferedReader reader = new BufferedReader(new StringReader(content))) { String line = reader.readLine(); System.out.println(" Read: " + line); return line; // close() called automatically AFTER return } // No finally needed — reader is guaranteed closed here } // Comparing the old try-finally with modern try-with-resources static String readFirstLineLegacy(String content) throws IOException { BufferedReader reader = new BufferedReader(new StringReader(content)); try { return reader.readLine(); } finally { reader.close(); // must remember this — compiler does NOT help } } public static void main(String[] args) throws IOException { System.out.println("=== Single resource — read first line ==="); String content = "Hello from Bengaluru\nSecond line\nThird line"; System.out.println("Result: " + readFirstLine(content)); System.out.println(); // Showing that close() runs even when exception is thrown System.out.println("=== Resource closed even when exception occurs ==="); try (BufferedReader reader = new BufferedReader(new StringReader("")) { @Override public String readLine() throws IOException { System.out.println(" readLine() called — about to throw"); throw new IOException("Simulated read failure"); } @Override public void close() throws IOException { System.out.println(" close() called automatically — resource cleaned up"); super.close(); } }) { reader.readLine(); // throws IOException } catch (IOException ioe) { System.out.println("Caught: " + ioe.getMessage()); } System.out.println("Execution continues after try-with-resources"); } }
Output:
=== Single resource — read first line ===
  Read: Hello from Bengaluru
Result: Hello from Bengaluru

=== Resource closed even when exception occurs ===
  readLine() called — about to throw
  close() called automatically — resource cleaned up
Caught: Simulated read failure
Execution continues after try-with-resources

Multiple Resources — Reverse Close Order

// File: MultipleResourcesDemo.java public class MultipleResourcesDemo { // Custom AutoCloseable for demonstration — shows close() call order static class TrackedResource implements AutoCloseable { private final String name; private final boolean shouldFailOnClose; TrackedResource(String name, boolean shouldFailOnClose) throws Exception { this.name = name; this.shouldFailOnClose = shouldFailOnClose; System.out.println(" OPEN: " + name); } void use() { System.out.println(" USE: " + name); } @Override public void close() throws Exception { System.out.println(" CLOSE: " + name); if (shouldFailOnClose) { throw new Exception("Close failed for: " + name); } } } static void demonstrateReverseOrder() throws Exception { System.out.println("--- Three resources: close order is REVERSE of declaration ---"); try (TrackedResource r1 = new TrackedResource("Resource-1", false); TrackedResource r2 = new TrackedResource("Resource-2", false); TrackedResource r3 = new TrackedResource("Resource-3", false)) { r1.use(); r2.use(); r3.use(); System.out.println(" Try block complete"); } // r3 closed first, then r2, then r1 — LIFO order } static void demonstrateExceptionSuppression() throws Exception { System.out.println("--- Try throws + close throws: suppression in action ---"); try (TrackedResource failOnClose = new TrackedResource("FailCloseResource", true)) { failOnClose.use(); throw new Exception("PRIMARY exception from try body"); } catch (Exception primaryException) { System.out.println("Primary: " + primaryException.getMessage()); System.out.println("Suppressed count: " + primaryException.getSuppressed().length); for (Throwable suppressed : primaryException.getSuppressed()) { System.out.println(" Suppressed: " + suppressed.getMessage()); } // PRIMARY exception is NOT replaced — close exception is attached to it } } static void demonstrateMultipleCloseFailures() throws Exception { System.out.println("--- Multiple close failures: all attached as suppressed ---"); try (TrackedResource r1 = new TrackedResource("FailResource-1", true); TrackedResource r2 = new TrackedResource("FailResource-2", true)) { throw new Exception("PRIMARY exception from try body"); } catch (Exception primaryException) { System.out.println("Primary: " + primaryException.getMessage()); System.out.println("Suppressed count: " + primaryException.getSuppressed().length); for (Throwable suppressed : primaryException.getSuppressed()) { System.out.println(" Suppressed: " + suppressed.getMessage()); } } } public static void main(String[] args) throws Exception { demonstrateReverseOrder(); System.out.println(); demonstrateExceptionSuppression(); System.out.println(); demonstrateMultipleCloseFailures(); } }
Output:
--- Three resources: close order is REVERSE of declaration ---
  OPEN: Resource-1
  OPEN: Resource-2
  OPEN: Resource-3
  USE:  Resource-1
  USE:  Resource-2
  USE:  Resource-3
  Try block complete
  CLOSE: Resource-3
  CLOSE: Resource-2
  CLOSE: Resource-1

--- Try throws + close throws: suppression in action ---
  OPEN: FailCloseResource
  USE:  FailCloseResource
  CLOSE: FailCloseResource
Primary: PRIMARY exception from try body
Suppressed count: 1
  Suppressed: Close failed for: FailCloseResource

--- Multiple close failures: all attached as suppressed ---
  OPEN: FailResource-1
  OPEN: FailResource-2
  CLOSE: FailResource-2
  CLOSE: FailResource-1
Primary: PRIMARY exception from try body
Suppressed count: 2
  Suppressed: Close failed for: FailResource-2
  Suppressed: Close failed for: FailResource-1

Custom AutoCloseable Classes

Any class that implements AutoCloseable can be used in try-with-resources. This is the right pattern for any object that holds a resource that needs deterministic cleanup — not just JDK I/O types.

// File: CustomAutoCloseableDemo.java public class CustomAutoCloseableDemo { // Custom connection pool entry — must be returned to pool on close() static class PooledConnection implements AutoCloseable { private final String connectionId; private boolean returned = false; PooledConnection(String connectionId) { this.connectionId = connectionId; System.out.println(" [POOL] Borrowed: " + connectionId); } String execute(String query) { if (returned) { throw new IllegalStateException("Connection already returned to pool"); } return "result-of-[" + query + "]-via-" + connectionId; } @Override public void close() { if (!returned) { returned = true; System.out.println(" [POOL] Returned: " + connectionId); } } } // Metrics timer — records duration on close() static class TimedOperation implements AutoCloseable { private final String operationName; private final long startNanos; TimedOperation(String operationName) { this.operationName = operationName; this.startNanos = System.nanoTime(); System.out.println(" [TIMER] Started: " + operationName); } @Override public void close() { long durationMs = (System.nanoTime() - startNanos) / 1_000_000; System.out.println(" [TIMER] Completed: " + operationName + " in " + durationMs + "ms"); } } // Transaction scope — commits or rolls back on close() static class TransactionScope implements AutoCloseable { private final String transactionId; private boolean committed = false; TransactionScope(String transactionId) { this.transactionId = transactionId; System.out.println(" [TX] Begin: " + transactionId); } void commit() { committed = true; System.out.println(" [TX] Committed: " + transactionId); } @Override public void close() { if (!committed) { System.out.println(" [TX] Rolled back: " + transactionId); } } } public static void main(String[] args) { System.out.println("=== Custom pooled connection ==="); try (PooledConnection conn = new PooledConnection("conn-007")) { System.out.println(" " + conn.execute("SELECT * FROM products LIMIT 5")); } // returns to pool automatically System.out.println(); System.out.println("=== Timed operation ==="); try (TimedOperation timer = new TimedOperation("processPaymentBatch")) { // Simulate processing work Thread.sleep(5); System.out.println(" Processing complete"); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } // timer.close() records duration System.out.println(); System.out.println("=== Transaction scope — committed ==="); try (TransactionScope tx = new TransactionScope("TX-8821")) { System.out.println(" Updating inventory..."); tx.commit(); } // close() sees committed=true — no rollback System.out.println(); System.out.println("=== Transaction scope — rolled back on exception ==="); try (TransactionScope tx = new TransactionScope("TX-8822")) { System.out.println(" Updating inventory..."); throw new RuntimeException("Inventory DB unavailable"); } catch (RuntimeException rte) { System.out.println(" Caught: " + rte.getMessage()); } // close() sees committed=false — rolls back } }
Output:
=== Custom pooled connection ===
  [POOL] Borrowed: conn-007
  result-of-[SELECT * FROM products LIMIT 5]-via-conn-007
  [POOL] Returned: conn-007

=== Timed operation ===
  [TIMER] Started: processPaymentBatch
  Processing complete
  [TIMER] Completed: processPaymentBatch in 6ms

=== Transaction scope — committed ===
  [TX] Begin: TX-8821
  Updating inventory...
  [TX] Committed: TX-8821

=== Transaction scope — rolled back on exception ===
  [TX] Begin: TX-8822
  Updating inventory...
  [TX] Rolled back: TX-8822
  Caught: Inventory DB unavailable

Basic Example

public class Main { static class Resource implements AutoCloseable { private final String name; Resource(String name) { this.name = name; System.out.println("Opening: " + name); } void use() { System.out.println("Using: " + name); } @Override public void close() { System.out.println("Closing: " + name); } } public static void main(String[] args) { try (Resource resource = new Resource("File-1")) { resource.use(); } System.out.println("Done"); } }

Output

Opening: File-1
Using: File-1
Closing: File-1
Done

Resource implements AutoCloseable, so it can be declared inside the try (...) parentheses. As soon as the try block finishes — even though nothing here throws — the compiler automatically calls resource.close() before moving on to "Done". Notice there is no finally block and no manual close() call anywhere in main: try-with-resources guarantees the resource is closed exactly once, whether the block completes normally or an exception is thrown inside it.

Performance Considerations

try-with-resources has identical runtime performance to an equivalent try-finally block. The compiler generates the same bytecode structure — the only difference is that try-with-resources generates correct exception suppression handling that manual try-finally requires explicit code to achieve.

PERFORMANCE FACTS:

  try-with-resources vs try-finally (equivalent manual code):
    Zero runtime overhead difference
    Same bytecode operations at the JVM level
    Same method call stack depth

  WHERE PERFORMANCE MATTERS:
  — close() method call frequency: each resource's close() is called once
  — Connection pool return cost: depends on the pool implementation
  — File/stream flush cost: depends on buffer state at close time

  WHAT TO WATCH FOR:
  — Never put expensive computation inside close() of a hot-path resource
  — Never suppress close() failures with an empty catch inside close()
  — Multiple resources: each declared resource allocates its own object
    before the try body — if the Nth constructor fails, resources 1 to N-1
    are still closed automatically (the partially-initialized list is safe)

  PARTIAL INITIALIZATION SAFETY:
    try (A a = new A();   ← succeeds
         B b = new B();   ← succeeds
         C c = new C()) { ← THROWS during construction
        // never entered
    }
    Result: b.close() then a.close() — both run safely
    c was never fully initialized — its close() is NOT called (no object exists)

Best Practices

Declare every AutoCloseable resource in try-with-resources — never in a separate variable outside the try. Declaring the resource outside and using it inside loses the automatic close guarantee. If an exception occurs between the declaration and the try block — rare but possible — the resource leaks. The declaration in the try parentheses is the only safe form for AutoCloseable resources.

Design custom AutoCloseable implementations so close() is idempotent. close() may be called more than once in some scenarios — once by try-with-resources and once manually. An idempotent close() that tracks whether it has already been closed prevents IOException or IllegalStateException from a double-close. The JDK's own InputStream.close() is idempotent; follow the same contract.

Never swallow exceptions inside close() with an empty catch block. If close() can fail meaningfully, the failure should be logged at minimum. Silent suppression inside close() makes connection leaks, file corruption, and incomplete flushes invisible. If close() throws, let the exception propagate or log and suppress explicitly — never ignore it.

Use the Java 9 effectively-final variable form when the resource already exists. Connection conn = getExistingConnection(); try (conn) { ... } avoids re-declaring a variable you already have. The requirement: the variable must be effectively final (never reassigned after the point where it is used in the try header). This form is syntactically cleaner when the resource is received as a method parameter or retrieved before the try block.

Common Mistakes

Mistake 1 — Declaring the Resource Outside try-with-resources

// WRONG — resource declared outside the try; no automatic close guarantee Connection conn = dataSource.getConnection(); // leaks if code before try throws try (conn) { // Java 9 syntax works here, but only if conn is effectively final return conn.createStatement().executeQuery(sql); } catch (SQLException e) { throw new DataAccessException(e); } // ALSO WRONG — Java 7/8 form with declaration outside: Connection conn = dataSource.getConnection(); try { return conn.createStatement().executeQuery(sql); } finally { conn.close(); // legacy — prefer declaration inside try() } // CORRECT — declare inside try-with-resources parentheses try (Connection conn = dataSource.getConnection()) { return conn.createStatement().executeQuery(sql); } catch (SQLException e) { throw new DataAccessException(e); }

Mistake 2 — Assuming Resources Are Closed in Declaration Order

// WRONG assumption — resources close in REVERSE order (LIFO) try (DatabaseConnection dbConn = new DatabaseConnection(); CacheConnection cacheConn = new CacheConnection()) { // After this block: developer expects dbConn to close first // ACTUAL: cacheConn closes FIRST, then dbConn } // WHY IT MATTERS: // If cacheConn.close() flushes data to dbConn, it must close BEFORE dbConn // Reverse order ensures dependent resources close before their dependencies // Declare dbConn first (closes last), cacheConn second (closes first) — correct // CORRECT declaration order when cacheConn depends on dbConn: try (DatabaseConnection dbConn = new DatabaseConnection(); // closes LAST CacheConnection cacheConn = new CacheConnection(dbConn)) { // closes FIRST // cacheConn.close() can safely use dbConn (still open at that point) }

Mistake 3 — Using null in try-with-resources

// WRONG — passing null to try-with-resources causes NullPointerException during close Connection conn = possiblyNullConnection(); // might return null try (conn) { // if conn is null, close() throws NullPointerException on exit conn.executeQuery(sql); } // CORRECT — null-check before entering try-with-resources Connection conn = possiblyNullConnection(); if (conn == null) { throw new IllegalStateException("Connection not available"); } try (conn) { conn.executeQuery(sql); } // OR: use a factory that never returns null try (Connection conn = getOrCreateConnection()) { // guaranteed non-null conn.executeQuery(sql); }

Mistake 4 — Not Implementing AutoCloseable for Resource-Holding Classes

// WRONG — connection-holding class does not implement AutoCloseable // Callers must manually call release() — easily forgotten class CacheClient { private final RedisConnection redisConn; CacheClient(String host) { this.redisConn = new RedisConnection(host); } String get(String key) { return redisConn.get(key); } void release() { redisConn.close(); } // manual — callers forget this } // CORRECT — implement AutoCloseable; callers use try-with-resources class CacheClient implements AutoCloseable { private final RedisConnection redisConn; CacheClient(String host) { this.redisConn = new RedisConnection(host); } String get(String key) { return redisConn.get(key); } @Override public void close() { redisConn.close(); // compiler reminds callers via try-with-resources } } // Callers: // try (CacheClient client = new CacheClient("redis.internal")) { // return client.get("session:user-001"); // }

Interview Questions

Q1. What is try-with-resources and which Java version introduced it?

try-with-resources was introduced in Java 7. It is a try block that declares one or more resources in parentheses — any object implementing java.lang.AutoCloseable. The compiler generates code to call close() on each resource when the block exits, regardless of whether it exits normally, via exception, or via return. It solves two problems with manual try-finally: the risk of forgetting to call close(), and the exception suppression problem where a close() failure in finally replaces the original exception.

Q2. What is the AutoCloseable interface and what does it require?

java.lang.AutoCloseable is the contract that enables try-with-resources. It declares one method: void close() throws Exception. Any class implementing this interface can be declared in try-with-resources parentheses. java.io.Closeable extends AutoCloseable with a narrower signature: void close() throws IOException. JDK I/O classes implement Closeable; JDBC types (Connection, Statement, ResultSet) implement AutoCloseable. When designing a class that holds a resource needing deterministic cleanup, implementing AutoCloseable is the correct pattern.

Q3. In what order are multiple resources closed in try-with-resources?

Resources are closed in reverse order of declaration — Last-In-First-Out. If you declare try (A a = new A(); B b = new B()), b.close() is called first, then a.close(). The rationale is that B was declared after A and may depend on A — closing B while A is still open ensures B.close() can safely use A if needed. This mirrors the stack-like relationship between dependent resources and matches the order natural for connection → statement → result set.

Q4. How does try-with-resources handle the case where both try body and close() throw?

If the try body throws and close() also throws, try-with-resources keeps the original exception as the primary exception and attaches the close() exception as a suppressed exception via primaryException.addSuppressed(closeException). Callers retrieve suppressed exceptions with exception.getSuppressed(). This is the key correctness advantage over manual try-finally: a close() failure in finally silently replaces the original exception. With try-with-resources, the original exception is never lost.

Q5. Can try-with-resources be used with catch and finally blocks?

Yes. try-with-resources can be combined with catch and finally in the same structure. The execution order is: try body → automatic close() calls (reverse order) → catch (if applicable) → finally. The resources are closed before catch and finally execute. This means close() errors can be caught by the catch block if they propagate, and finally runs after all resources are closed — which is the expected behaviour for cleanup that must always happen beyond resource management.

Q6. What is the Java 9 enhancement to try-with-resources?

Java 9 allows using an effectively final variable in the try-with-resources parentheses without re-declaring it. Before Java 9, you had to declare the resource inside the parentheses: try (Connection conn = dataSource.getConnection()). In Java 9+, if you already have an effectively-final reference (never reassigned), you can write try (conn) directly. The variable must not be reassigned between its declaration and the try header. This reduces verbosity when a resource is received as a parameter or retrieved before the try block.

FAQs

What is the difference between AutoCloseable and Closeable?

java.lang.AutoCloseable is the root interface for try-with-resources. Its close() method declares throws Exception — it can throw any checked exception. java.io.Closeable extends AutoCloseable and narrows the declaration to throws IOException. I/O classes implement Closeable; JDBC and other non-I/O resource classes implement AutoCloseable directly. For custom resource classes, implement AutoCloseable and declare the narrowest possible exception on close() — or no checked exception if close() cannot fail.

Does try-with-resources call close() if an exception occurs during resource construction?

If the Nth resource constructor throws, resources 1 through N-1 that were successfully constructed are closed. Resource N was never successfully created, so its close() is not called. The compiler generates the close calls only for resources that were successfully instantiated. This partial-initialization safety is one of the correctness guarantees that makes try-with-resources preferable to manual management.

Can a null resource be used in try-with-resources?

Java 7-8: no — passing null to the try-with-resources mechanism causes NullPointerException when close() is attempted. Java 9+: using an effectively-final variable that is null still causes the same problem. Always ensure the resource reference is non-null before entering try-with-resources. If the resource might legitimately be absent, null-check before and throw or return early.

What happens if close() throws an unchecked exception?

If close() throws an unchecked RuntimeException and the try body completed normally, the unchecked exception propagates to the caller — same as a checked exception. If the try body also threw, the unchecked close exception is attached as suppressed. There is no special treatment for unchecked vs checked in suppression handling — both are suppressed equally.

Can I use try-with-resources for a resource that does not implement AutoCloseable?

No — the resource must implement AutoCloseable. If you are working with a legacy class that holds a resource but does not implement AutoCloseable, you have two options: wrap it in a custom AutoCloseable adapter that delegates close() to the legacy cleanup method, or use manual try-finally. The adapter approach is cleaner for repeated use: class LegacyResourceAdapter implements AutoCloseable { ... @Override public void close() { legacy.release(); } }.

Can try-with-resources be nested, and how does that affect close order?

Yes. Multiple levels of try-with-resources blocks can be nested. Each block manages its own resources independently. The inner block's resources are closed when the inner try exits; the outer block's resources are closed when the outer try exits. This is distinct from declaring multiple resources in one try-with-resources block — nested blocks are completely independent scopes. Nesting is useful when an inner resource needs to be scoped within an outer block: a statement scoped within a connection, for example, can each be in separate blocks if different error handling is needed at each level.

Summary

try-with-resources solves the two fundamental problems of resource management with try-finally: the risk of forgetting close() and silent exception suppression when close() itself throws. The compiler generates the close calls automatically, closes multiple resources in reverse order of declaration, and attaches close failures as suppressed exceptions on the primary exception rather than replacing it.

The AutoCloseable interface is the enabling contract: any class implementing void close() throws Exception can be used in try-with-resources. Custom resource-holding classes should implement AutoCloseable to enable callers to use this syntax. The Java 9 effectively-final variable form reduces verbosity for resources that already exist before the try block.

Three rules govern correct usage: declare resources inside the try parentheses, not outside; know that multiple resources close in reverse order; and make close() idempotent so double-close does not throw. The only remaining use case for manual try-finally is non-AutoCloseable resources — ReentrantLock, custom state management — where try-with-resources cannot be applied without a wrapper.

What to Read Next

Practice Programs

Put this topic into practice — each program has full code, output and a step-by-step explanation you can run online.

Browse all Java programs →