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
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.
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
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");
}
}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:
- 1try (var a = new Res("A");
- 2 var b = new Res("B");
- 3 var c = new Res("C")) {
- 4 body();
- 5} catch (IllegalStateException e) {
- 6 log(e);
- 7} finally {
- 8 IO.println("finally");
- 9}
- open A
A is created and assigned. from now on, it will be closed no matter what.
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.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:
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):
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
nullresource is skipped,close()is never called on it, no NPE. - resources are final
- you can't reassign
rinside the block. since java 9 you can also list an existing, effectively final variable:try (reader) { ... }. - AutoCloseable vs Closeable
AutoCloseable.close()throwsException(java 7).Closeable.close()throwsIOExceptionand 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.
- anything
AutoCloseablegoes 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.