skip to content
everything.coreApi.tryWithResources

try-with-resources

automatic closing, in reverse order, with suppressed exceptions.

chapter
chapter 04.06
reading time
6 min read

try (var r = open()) { ... } calls r.close() for you when the block ends, however it ends. with several resources they close in reverse order of opening, and only the ones that actually got opened.

if the body throws and close() throws too, the close exception doesn't replace the real one: it's attached to it as a suppressed exception.

the picture

getting dressed: socks, then shoes. getting undressed: shoes, then socks. you undo things in the reverse order you did them, because the later thing usually depends on the earlier one. a buffered reader sits on a file, a statement sits on a connection. close the connection first and the statement's close() has nothing to talk to.

try-with-resources is that rule, written once in the compiler instead of a hundred times in your code. anything that implements AutoCloseable (one method, close()) can go in the parentheses.

before java 7: the finally dance

don'ttry / finally by hand
java
Connection conn = open();
try {
    Statement st = conn.createStatement();
    try {
        st.execute(sql);
    } finally {
        st.close();
    }
} finally {
    conn.close();
}

one level of nesting per resource. and if execute throws and then close() throws, the close exception replaces the real one: your log says "close failed" and the actual SQL error is gone.

dotry-with-resources
java
try (Connection conn = open();
     Statement st = conn.createStatement()) {
    st.execute(sql);
}

same closing, in the same order, written once. and when both throw, the SQL error is the one you see, with the close failure attached to it.

the order things happen in

Main.java
record Res(String name) implements AutoCloseable {
    Res {
        IO.println("open " + name);
    }
 
    public void close() {
        IO.println("close " + name);
    }
}
 
void main() {
    try (var a = new Res("A"); var b = new Res("B"); var c = new Res("C")) {
        IO.println("body");
    } finally {
        IO.println("finally");
    }
}
output
open A
open B
open C
body
close C
close B
close A
finally

opened left to right, closed right to left. and notice where finally lands: after the resources are closed. same for a catch on a try-with-resources, it only runs once everything is already closed.

now make things fail. every scenario below is the real output of that program with one thing changed:

Main.java
  1. 1try (var a = new Res("A");
  2. 2 var b = new Res("B");
  3. 3 var c = new Res("C")) {
  4. 4 body();
  5. 5} catch (IllegalStateException e) {
  6. 6 log(e);
  7. 7} finally {
  8. 8 IO.println("finally");
  9. 9}
Res Aopen
Res Bnot opened
Res Cnot opened
  1. open A

A is created and assigned. from now on, it will be closed no matter what.

step 1/10
pick a failure and step through. resources stack up as they open and close newest first. when the body and a close() both throw, the body's exception is the one you catch, and the close failure rides along inside getSuppressed(). if a constructor throws, only what was already opened gets closed.
predict the output
java
try (var a = new Res("A"); var b = new Res("B")) {
    throw new IllegalStateException("x");
} catch (IllegalStateException e) {
    IO.println("done");
}

suppressed exceptions

when the body throws and then a close() throws, java has two exceptions and can only propagate one. it keeps the body's (the cause of the trouble) and attaches the other with addSuppressed. nothing is lost, and the stack trace prints both:

text
Exception in thread "main" java.lang.RuntimeException: query failed
	at Sup.main(Sup.java:7)
	Suppressed: java.lang.IllegalStateException: close failed
		at Sup$Conn.close(Sup.java:3)
		at Sup.main(Sup.java:6)

in code, e.getSuppressed() gives you the array. it's on every Throwable since java 7, so you can use addSuppressed yourself whenever you're cleaning up after a failure and the cleanup fails too.

what javac writes for you

try-with-resources is pure compiler sugar. for one resource, the generated code is shaped like this (a sketch of JLS 14.20.3.1):

java
final Res r = new Res("A");
Throwable primary = null;
try {
    body();
} catch (Throwable t) {
    primary = t;
    throw t;
} finally {
    if (r != null) {
        if (primary != null) {
            try {
                r.close();
            } catch (Throwable closeFailure) {
                primary.addSuppressed(closeFailure);   // keep the first, attach the second
            }
        } else {
            r.close();                                 // nothing failed: a close failure is the failure
        }
    }
}

several resources nest: each later one is a try-with-resources inside the earlier one's body. that's the whole reason the order is reversed.

a few rules fall straight out of that:

null is fine
a null resource is skipped, close() is never called on it, no NPE.
resources are final
you can't reassign r inside the block. since java 9 you can also list an existing, effectively final variable: try (reader) { ... }.
AutoCloseable vs Closeable
AutoCloseable.close() throws Exception (java 7). Closeable.close() throws IOException and must be safe to call twice (java 5, every I/O stream). implement the narrowest one you can.
catch and finally come last
both run after every resource is closed.
keylines
  • anything AutoCloseable goes in the parentheses. it's closed however the block ends.
  • several resources close in reverse order, and only the ones that were actually opened.
  • body and close both throw? you get the body's, with the close failure in getSuppressed().
  • catch and finally run after the resources are closed.
  • don't return a resource from inside the block that closes it.