Java try-with-resources and AutoCloseable Explained

Introduction Files, network connections, database connections and many other objects hold resources outside the JVM, which must be released by calling close() when you are done - even if an exception occurs. Doing this correctly by hand takes surprisingly careful code. try-with-resources, added in Java 7, does it for you. This tutorial explains try-with-resources and AutoCloseable in Java. We will start with the problem of closing resources by hand with finally. We will then use try-with-resources, and write our own resource class to see exactly when close() is called - including with several resources. Next we will understand suppressed exceptions, which try-with-resources keeps instead of losing. Lastly, we will look at the Java 9 improvement and at best practices.

(Note - This tutorial builds on try, catch, finally and multi-catchClick to read tutorial: Java try, catch, finally and Multi-catch with Examples.) The problem - closing resources by hand Before Java 7, the correct way to use a resource was a try block for the work and a finally block for closing it - with a null check, because the resource may never have been opened, and another try, because close() itself can throw.
Closing a resource the old way
BufferedReader reader = null;
try {
  reader = Files.newBufferedReader(Path.of("data.txt"));
  System.out.println(reader.readLine());
} catch (IOException e) {
  System.out.println("Read failed: " + e.getMessage());
} finally {
  if (reader != null) {
    try {
      reader.close();
    } catch (IOException e) {
      System.out.println("Close failed: " + e.getMessage());
    }
  }
}
That is a lot of code for one line of real work, and it is easy to get wrong - forgetting the null check, or the finally altogether. With two or three resources it gets much worse. And it has a hidden flaw: if both the read and the close fail, one of the two exceptions is lost. try-with-resources Syntax of try-with-resources
Syntax of try-with-resources
try (ResourceType resource = new ResourceType(...)) {
  // use the resource
} catch (SomeException e) {
  // optional catch blocks
} finally {
  // optional finally block
}
Where,
  • resources are declared in parentheses after try. Each must implement java.lang.AutoCloseable (or its subinterface java.io.Closeable) - which almost every resource class in the JDK does.
  • when the try block ends - normally, through return, or because of an exception - Java calls each resource's close() method automatically, before any catch or finally block runs.
  • a resource which was never successfully created (its expression threw) is not closed, so no null check is needed.
  • the resource variable is implicitly final, and its scope is the try block.
Reading a file with try-with-resources
//ReadWithResources.java
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

public class ReadWithResources {
  static void printFile(String name) {
    try (BufferedReader reader = Files.newBufferedReader(Path.of(name))) {
      String line;
      while ((line = reader.readLine()) != null) {
        System.out.println("  " + line);
      }
    } catch (IOException e) {
      System.out.println("  Could not read " + name + ": " + e);
    }
  }

  public static void main(String[] args) throws IOException {
    Files.writeString(Path.of("greetings.txt"), "Hello\nfrom\ntry-with-resources\n");
    System.out.println("greetings.txt:");
    printFile("greetings.txt");
    System.out.println("missing.txt:");
    printFile("missing.txt");
    Files.delete(Path.of("greetings.txt"));
  }
}
 OUTPUT of the above code
greetings.txt:
  Hello
  from
  try-with-resources
missing.txt:
  Could not read missing.txt: java.nio.file.NoSuchFileException: missing.txt
The same job as the old code, in a fraction of the lines - and the reader is closed in every case. For a missing file, newBufferedReader() threw, no reader was created, and nothing needed closing.
Writing your own AutoCloseable resource Any class can be used with try-with-resources by implementing AutoCloseable, whose only method is void close() throws Exception. Overriding it, you can narrow or drop the throws clause. Let us write a resource which prints when it opens and closes, and use two of them together.
A custom AutoCloseable resource
//Connection.java
public class Connection implements AutoCloseable {
  private final String name;

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

  void send(String message) {
    if (message.isEmpty()) {
      throw new IllegalArgumentException("Empty message on " + name);
    }
    System.out.println("  " + name + " sent: " + message);
  }

  @Override
  public void close() {                   // no checked exception declared
    System.out.println("  close " + name);
  }
}
Closing order with several resources
//ResourceOrder.java
public class ResourceOrder {
  static void transfer(String message) {
    try (Connection db = new Connection("database");
         Connection queue = new Connection("queue")) {
      db.send(message);
      queue.send(message);
      System.out.println("  try block finished");
    } catch (IllegalArgumentException e) {
      System.out.println("  catch: " + e.getMessage());
    } finally {
      System.out.println("  finally");
    }
  }

  public static void main(String[] args) {
    System.out.println("Normal run:");
    transfer("order #317");
    System.out.println("Run with an exception:");
    transfer("");
  }
}
 OUTPUT of the above code
Normal run:
  open  database
  open  queue
  database sent: order #317
  queue sent: order #317
  try block finished
  close queue
  close database
  finally
Run with an exception:
  open  database
  open  queue
  close queue
  close database
  catch: Empty message on database
  finally
Explanation of the code
  • Several resources are separated by semicolons. They are opened in the order written, and closed in the reverse order - queue first, then database - so a resource is never closed while one that depends on it is still open.
  • In the second run, db.send("") threw. Both connections were closed before the catch block ran, and then finally ran last.
  • close() was overridden without throws Exception, so code using Connection does not have to handle a checked exception from closing.
Suppressed exceptions What if the try block throws, and then close() throws as well? With the old finally pattern, the exception from close() replaced the original one, which was simply lost - usually the more important of the two. try-with-resources keeps the original exception, and attaches the exception from close() to it as a suppressed exception, available from getSuppressed().
An exception from close() is suppressed, not lost
//SuppressedDemo.java
public class SuppressedDemo {
  static class FaultyResource implements AutoCloseable {
    @Override
    public void close() {
      throw new IllegalStateException("close() failed");
    }
  }

  public static void main(String[] args) {
    try (FaultyResource resource = new FaultyResource()) {
      throw new RuntimeException("work failed");
    } catch (RuntimeException e) {
      System.out.println("Caught     : " + e.getMessage());
      for (Throwable suppressed : e.getSuppressed()) {
        System.out.println("Suppressed : " + suppressed.getMessage());
      }
    }
  }
}
 OUTPUT of the above code
Caught     : work failed
Suppressed : close() failed
The real problem - "work failed" - is the exception that was caught, and the secondary failure from close() is still available instead of being thrown away. Stack traces printed by Java show suppressed exceptions too, under "Suppressed:".
Java 9 - using an existing variable In Java 7 and 8, every resource had to be declared inside the parentheses. Since Java 9, you can also list an existing variable which is final or effectively final (never reassigned) - handy when a resource is created earlier, or passed in as a parameter.
try-with-resources on an existing variable (Java 9+)
//Java9Resources.java
public class Java9Resources {
  static void sendAll(Connection connection, String... messages) {
    try (connection) {                    // an existing, effectively final variable
      for (String m : messages) {
        connection.send(m);
      }
    }
  }

  public static void main(String[] args) {
    Connection mail = new Connection("mail-server");
    sendAll(mail, "Welcome, Tom", "Your order has shipped");
  }
}
 OUTPUT of the above code
  open  mail-server
  mail-server sent: Welcome, Tom
  mail-server sent: Your order has shipped
  close mail-server
Java9Resources reuses the Connection class from earlier. Note that it closes a connection it was given - make sure that is what callers expect, as the method now owns the resource. Best practices
  • Use try-with-resources for every resource - files, streams, readers and writers, sockets, JDBC connections, statements and result sets, Scanners on files, and executor services (which are AutoCloseable since Java 19).
  • Declare one resource per object. Writing try (var r = new BufferedReader(new FileReader(f))) registers only the outer reader; if its constructor failed, the inner FileReader would not be closed. Declaring each object as its own resource avoids that.
  • Make your own resource classes AutoCloseable, make close() safe to call more than once, and declare the narrowest exception it can throw (or none).
  • Do not close System.in with try-with-resources around a Scanner - closing the scanner closes standard input for the rest of the program, as explained in the Scanner tutorialClick to read tutorial: Reading User Input in Java with Scanner.
Summary In this tutorial we first saw how much careful code it takes to close a resource by hand with finally, and that it can lose exceptions. We then used try-with-resources, which calls close() automatically on every AutoCloseable resource, whether or not an exception occurs. Next we wrote our own resource class, and saw that several resources are closed in reverse order, before any catch or finally block runs. We then saw that an exception from close() is kept as a suppressed exception instead of hiding the original. Lastly, we used an existing variable as a resource (Java 9+), and went through best practices. In the next tutorial, we will bring it all together with exception handling best practicesClick to read tutorial: Java Exception Handling Best Practices.