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

1// File: TryWithResourcesBasicDemo.java 2 3import java.io.BufferedReader; 4import java.io.FileReader; 5import java.io.IOException; 6import java.io.StringReader; 7 8public class TryWithResourcesBasicDemo { 9 10 // BufferedReader implements Closeable (extends AutoCloseable) 11 // close() is called automatically when the try block exits 12 static String readFirstLine(String content) throws IOException { 13 // StringReader simulates a file reader for this demo 14 try (BufferedReader reader = new BufferedReader(new StringReader(content))) { 15 String line = reader.readLine(); 16 System.out.println(" Read: " + line); 17 return line; // close() called automatically AFTER return 18 } 19 // No finally needed — reader is guaranteed closed here 20 } 21 22 // Comparing the old try-finally with modern try-with-resources 23 static String readFirstLineLegacy(String content) throws IOException { 24 BufferedReader reader = new BufferedReader(new StringReader(content)); 25 try { 26 return reader.readLine(); 27 } finally { 28 reader.close(); // must remember this — compiler does NOT help 29 } 30 } 31 32 public static void main(String[] args) throws IOException { 33 34 System.out.println("=== Single resource — read first line ==="); 35 String content = "Hello from Bengaluru\nSecond line\nThird line"; 36 System.out.println("Result: " + readFirstLine(content)); 37 38 System.out.println(); 39 40 // Showing that close() runs even when exception is thrown 41 System.out.println("=== Resource closed even when exception occurs ==="); 42 try (BufferedReader reader = new BufferedReader(new StringReader("")) { 43 @Override 44 public String readLine() throws IOException { 45 System.out.println(" readLine() called — about to throw"); 46 throw new IOException("Simulated read failure"); 47 } 48 49 @Override 50 public void close() throws IOException { 51 System.out.println(" close() called automatically — resource cleaned up"); 52 super.close(); 53 } 54 }) { 55 reader.readLine(); // throws IOException 56 } catch (IOException ioe) { 57 System.out.println("Caught: " + ioe.getMessage()); 58 } 59 System.out.println("Execution continues after try-with-resources"); 60 } 61}
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

1// File: MultipleResourcesDemo.java 2 3public class MultipleResourcesDemo { 4 5 // Custom AutoCloseable for demonstration — shows close() call order 6 static class TrackedResource implements AutoCloseable { 7 private final String name; 8 private final boolean shouldFailOnClose; 9 10 TrackedResource(String name, boolean shouldFailOnClose) throws Exception { 11 this.name = name; 12 this.shouldFailOnClose = shouldFailOnClose; 13 System.out.println(" OPEN: " + name); 14 } 15 16 void use() { 17 System.out.println(" USE: " + name); 18 } 19 20 @Override 21 public void close() throws Exception { 22 System.out.println(" CLOSE: " + name); 23 if (shouldFailOnClose) { 24 throw new Exception("Close failed for: " + name); 25 } 26 } 27 } 28 29 static void demonstrateReverseOrder() throws Exception { 30 System.out.println("--- Three resources: close order is REVERSE of declaration ---"); 31 try (TrackedResource r1 = new TrackedResource("Resource-1", false); 32 TrackedResource r2 = new TrackedResource("Resource-2", false); 33 TrackedResource r3 = new TrackedResource("Resource-3", false)) { 34 r1.use(); 35 r2.use(); 36 r3.use(); 37 System.out.println(" Try block complete"); 38 } 39 // r3 closed first, then r2, then r1 — LIFO order 40 } 41 42 static void demonstrateExceptionSuppression() throws Exception { 43 System.out.println("--- Try throws + close throws: suppression in action ---"); 44 try (TrackedResource failOnClose = new TrackedResource("FailCloseResource", true)) { 45 failOnClose.use(); 46 throw new Exception("PRIMARY exception from try body"); 47 } catch (Exception primaryException) { 48 System.out.println("Primary: " + primaryException.getMessage()); 49 System.out.println("Suppressed count: " + primaryException.getSuppressed().length); 50 for (Throwable suppressed : primaryException.getSuppressed()) { 51 System.out.println(" Suppressed: " + suppressed.getMessage()); 52 } 53 // PRIMARY exception is NOT replaced — close exception is attached to it 54 } 55 } 56 57 static void demonstrateMultipleCloseFailures() throws Exception { 58 System.out.println("--- Multiple close failures: all attached as suppressed ---"); 59 try (TrackedResource r1 = new TrackedResource("FailResource-1", true); 60 TrackedResource r2 = new TrackedResource("FailResource-2", true)) { 61 throw new Exception("PRIMARY exception from try body"); 62 } catch (Exception primaryException) { 63 System.out.println("Primary: " + primaryException.getMessage()); 64 System.out.println("Suppressed count: " + primaryException.getSuppressed().length); 65 for (Throwable suppressed : primaryException.getSuppressed()) { 66 System.out.println(" Suppressed: " + suppressed.getMessage()); 67 } 68 } 69 } 70 71 public static void main(String[] args) throws Exception { 72 demonstrateReverseOrder(); 73 System.out.println(); 74 demonstrateExceptionSuppression(); 75 System.out.println(); 76 demonstrateMultipleCloseFailures(); 77 } 78}
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.

1// File: CustomAutoCloseableDemo.java 2 3public class CustomAutoCloseableDemo { 4 5 // Custom connection pool entry — must be returned to pool on close() 6 static class PooledConnection implements AutoCloseable { 7 private final String connectionId; 8 private boolean returned = false; 9 10 PooledConnection(String connectionId) { 11 this.connectionId = connectionId; 12 System.out.println(" [POOL] Borrowed: " + connectionId); 13 } 14 15 String execute(String query) { 16 if (returned) { 17 throw new IllegalStateException("Connection already returned to pool"); 18 } 19 return "result-of-[" + query + "]-via-" + connectionId; 20 } 21 22 @Override 23 public void close() { 24 if (!returned) { 25 returned = true; 26 System.out.println(" [POOL] Returned: " + connectionId); 27 } 28 } 29 } 30 31 // Metrics timer — records duration on close() 32 static class TimedOperation implements AutoCloseable { 33 private final String operationName; 34 private final long startNanos; 35 36 TimedOperation(String operationName) { 37 this.operationName = operationName; 38 this.startNanos = System.nanoTime(); 39 System.out.println(" [TIMER] Started: " + operationName); 40 } 41 42 @Override 43 public void close() { 44 long durationMs = (System.nanoTime() - startNanos) / 1_000_000; 45 System.out.println(" [TIMER] Completed: " + operationName + 46 " in " + durationMs + "ms"); 47 } 48 } 49 50 // Transaction scope — commits or rolls back on close() 51 static class TransactionScope implements AutoCloseable { 52 private final String transactionId; 53 private boolean committed = false; 54 55 TransactionScope(String transactionId) { 56 this.transactionId = transactionId; 57 System.out.println(" [TX] Begin: " + transactionId); 58 } 59 60 void commit() { 61 committed = true; 62 System.out.println(" [TX] Committed: " + transactionId); 63 } 64 65 @Override 66 public void close() { 67 if (!committed) { 68 System.out.println(" [TX] Rolled back: " + transactionId); 69 } 70 } 71 } 72 73 public static void main(String[] args) { 74 75 System.out.println("=== Custom pooled connection ==="); 76 try (PooledConnection conn = new PooledConnection("conn-007")) { 77 System.out.println(" " + conn.execute("SELECT * FROM products LIMIT 5")); 78 } // returns to pool automatically 79 80 System.out.println(); 81 82 System.out.println("=== Timed operation ==="); 83 try (TimedOperation timer = new TimedOperation("processPaymentBatch")) { 84 // Simulate processing work 85 Thread.sleep(5); 86 System.out.println(" Processing complete"); 87 } catch (InterruptedException e) { 88 Thread.currentThread().interrupt(); 89 } // timer.close() records duration 90 91 System.out.println(); 92 93 System.out.println("=== Transaction scope — committed ==="); 94 try (TransactionScope tx = new TransactionScope("TX-8821")) { 95 System.out.println(" Updating inventory..."); 96 tx.commit(); 97 } // close() sees committed=true — no rollback 98 99 System.out.println(); 100 101 System.out.println("=== Transaction scope — rolled back on exception ==="); 102 try (TransactionScope tx = new TransactionScope("TX-8822")) { 103 System.out.println(" Updating inventory..."); 104 throw new RuntimeException("Inventory DB unavailable"); 105 } catch (RuntimeException rte) { 106 System.out.println(" Caught: " + rte.getMessage()); 107 } // close() sees committed=false — rolls back 108 } 109}
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

Real-World Example — Flipkart Order Export Service

An order export service at Flipkart reads orders from a database, processes them, and writes the results to a file. Three resources are involved: a database connection, a prepared statement, and a file writer. They are declared in try-with-resources, closed in reverse order automatically, and the original exception is preserved even if closing one of them also fails.

1// File: ExportResult.java 2 3public record ExportResult(int recordsExported, String outputFile) { 4 @Override 5 public String toString() { 6 return String.format("ExportResult{exported=%d, file=%s}", 7 recordsExported, outputFile); 8 } 9}
1// File: OrderExportService.java 2 3import java.io.BufferedWriter; 4import java.io.IOException; 5import java.io.StringWriter; 6import java.sql.Connection; 7import java.sql.PreparedStatement; 8import java.sql.ResultSet; 9import java.sql.SQLException; 10import java.util.List; 11 12public class OrderExportService { 13 14 // Simulated JDBC types that implement AutoCloseable 15 static class SimulatedConnection implements AutoCloseable { 16 final String dsn; 17 SimulatedConnection(String dsn) { 18 System.out.println(" [DB] Connection opened to: " + dsn); 19 } 20 SimulatedPreparedStatement prepareStatement(String sql) { 21 return new SimulatedPreparedStatement(sql); 22 } 23 @Override 24 public void close() { 25 System.out.println(" [DB] Connection closed"); 26 } 27 } 28 29 static class SimulatedPreparedStatement implements AutoCloseable { 30 private final String sql; 31 SimulatedPreparedStatement(String sql) { 32 System.out.println(" [DB] Statement prepared: " + sql); 33 } 34 SimulatedResultSet executeQuery(String status, int limit) { 35 System.out.println(" [DB] Query executed (status=" + status + " limit=" + limit + ")"); 36 return new SimulatedResultSet(limit); 37 } 38 @Override 39 public void close() { 40 System.out.println(" [DB] Statement closed"); 41 } 42 } 43 44 static class SimulatedResultSet implements AutoCloseable { 45 private int remaining; 46 private int currentRow = 0; 47 48 SimulatedResultSet(int rowCount) { this.remaining = rowCount; } 49 50 boolean next() { return remaining-- > 0; } 51 String getString(String col) { 52 currentRow++; 53 return switch (col) { 54 case "order_id" -> "ORD-" + String.format("%04d", currentRow); 55 case "customer_id" -> "C-" + (1000 + currentRow); 56 case "amount" -> String.valueOf(500.0 * currentRow); 57 case "status" -> "DELIVERED"; 58 default -> "N/A"; 59 }; 60 } 61 62 @Override 63 public void close() { 64 System.out.println(" [DB] ResultSet closed"); 65 } 66 } 67 68 // Three resources: connection, statement, writer — all AutoCloseable 69 // Closed in REVERSE order: writer → statement → connection 70 public ExportResult exportDeliveredOrders( 71 String dsn, String outputPath, int limit) throws Exception { 72 73 StringWriter output = new StringWriter(); // simulates file writer 74 75 System.out.printf(" Exporting up to %d delivered orders to %s%n", 76 limit, outputPath); 77 78 // All three resources declared — closed in reverse order automatically 79 try (SimulatedConnection conn = new SimulatedConnection(dsn); 80 SimulatedPreparedStatement stmt = 81 conn.prepareStatement("SELECT order_id, customer_id, amount, status " + 82 "FROM orders WHERE status = ?"); 83 BufferedWriter writer = new BufferedWriter(output)) { 84 85 SimulatedResultSet rs = stmt.executeQuery("DELIVERED", limit); 86 87 writer.write("order_id,customer_id,amount,status\n"); 88 int count = 0; 89 90 while (rs.next()) { 91 writer.write(String.join(",", 92 rs.getString("order_id"), 93 rs.getString("customer_id"), 94 rs.getString("amount"), 95 rs.getString("status") 96 )); 97 writer.newLine(); 98 count++; 99 } 100 rs.close(); // ResultSet not in TWR here — closed explicitly 101 writer.flush(); 102 103 System.out.println(" Export data preview (first 2 lines):"); 104 output.toString().lines().limit(3).forEach( 105 line -> System.out.println(" " + line)); 106 107 return new ExportResult(count, outputPath); 108 } 109 // writer closed first, then stmt, then conn — REVERSE of declaration 110 } 111 112 public static void main(String[] args) { 113 114 OrderExportService service = new OrderExportService(); 115 116 System.out.println("=== Export 4 delivered orders ==="); 117 try { 118 ExportResult result = service.exportDeliveredOrders( 119 "jdbc:pg://orders-db.internal/orders", "/exports/delivered.csv", 4); 120 System.out.println("Export result: " + result); 121 } catch (Exception exception) { 122 System.out.println("Export failed: " + exception.getMessage()); 123 } 124 125 System.out.println(); 126 127 System.out.println("=== All three resources closed in reverse order ==="); 128 System.out.println(" Declared order : Connection → Statement → Writer"); 129 System.out.println(" Closed order : Writer → Statement → Connection"); 130 } 131}
Output:
=== Export 4 delivered orders ===
  Exporting up to 4 delivered orders to /exports/delivered.csv
  [DB]   Connection opened to: jdbc:pg://orders-db.internal/orders
  [DB]   Statement prepared: SELECT order_id, customer_id, amount, status FROM orders WHERE status = ?
  [DB]   Query executed (status=DELIVERED limit=4)
  [DB]   ResultSet closed
  Export data preview (first 2 lines):
    order_id,customer_id,amount,status
    ORD-0001,C-1001,500.0,DELIVERED
    ORD-0002,C-1002,1000.0,DELIVERED
  [DB]   Statement closed
  [DB]   Connection closed
Export result: ExportResult{exported=4, file=/exports/delivered.csv}

=== All three resources closed in reverse order ===
  Declared order : Connection → Statement → Writer
  Closed order   : Writer → Statement → Connection

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

1// WRONG — resource declared outside the try; no automatic close guarantee 2Connection conn = dataSource.getConnection(); // leaks if code before try throws 3try (conn) { // Java 9 syntax works here, but only if conn is effectively final 4 return conn.createStatement().executeQuery(sql); 5} catch (SQLException e) { 6 throw new DataAccessException(e); 7} 8 9// ALSO WRONG — Java 7/8 form with declaration outside: 10Connection conn = dataSource.getConnection(); 11try { 12 return conn.createStatement().executeQuery(sql); 13} finally { 14 conn.close(); // legacy — prefer declaration inside try() 15} 16 17// CORRECT — declare inside try-with-resources parentheses 18try (Connection conn = dataSource.getConnection()) { 19 return conn.createStatement().executeQuery(sql); 20} catch (SQLException e) { 21 throw new DataAccessException(e); 22}

Mistake 2 — Assuming Resources Are Closed in Declaration Order

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

Mistake 3 — Using null in try-with-resources

1// WRONG — passing null to try-with-resources causes NullPointerException during close 2Connection conn = possiblyNullConnection(); // might return null 3 4try (conn) { // if conn is null, close() throws NullPointerException on exit 5 conn.executeQuery(sql); 6} 7 8// CORRECT — null-check before entering try-with-resources 9Connection conn = possiblyNullConnection(); 10if (conn == null) { 11 throw new IllegalStateException("Connection not available"); 12} 13try (conn) { 14 conn.executeQuery(sql); 15} 16 17// OR: use a factory that never returns null 18try (Connection conn = getOrCreateConnection()) { // guaranteed non-null 19 conn.executeQuery(sql); 20}

Mistake 4 — Not Implementing AutoCloseable for Resource-Holding Classes

1// WRONG — connection-holding class does not implement AutoCloseable 2// Callers must manually call release() — easily forgotten 3class CacheClient { 4 private final RedisConnection redisConn; 5 6 CacheClient(String host) { 7 this.redisConn = new RedisConnection(host); 8 } 9 10 String get(String key) { return redisConn.get(key); } 11 12 void release() { redisConn.close(); } // manual — callers forget this 13} 14 15// CORRECT — implement AutoCloseable; callers use try-with-resources 16class CacheClient implements AutoCloseable { 17 private final RedisConnection redisConn; 18 19 CacheClient(String host) { 20 this.redisConn = new RedisConnection(host); 21 } 22 23 String get(String key) { return redisConn.get(key); } 24 25 @Override 26 public void close() { 27 redisConn.close(); // compiler reminds callers via try-with-resources 28 } 29} 30// Callers: 31// try (CacheClient client = new CacheClient("redis.internal")) { 32// return client.get("session:user-001"); 33// }

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