Have you ever ran into an exception thrown by a try-with-resources block and wondered how it works? This article observes the behavior of try-with-resources under different scenarios and explains what is going on.
Support for try-with-resources was added in java 7 and has been enhanced in java 9. A try-with-resources block is able to automatically close resources when execution of the block completes. There are many different scenarios where try-with-resources will throw exceptions. This article explores those scenarios.
If you are following along with the test examples the TestResource
class is attached at the bottom of this article.
TestResource
is a mock object used to simulate a resource. It can be configured to fail the work()
and close()
methods. The createFailure()
method simulates a failure to create a resource within a try block.
Creating Resources
Declare and initialize resource
Try-with-resources starts by declaring a resource inside a resource specification.
try(var r = new TestResource()) {
//work with resource here
}
Resource Specification - "A resource specification uses variables to denote resources for the try statement, either by declaring local variables with initializer expressions or by referring to suitable existing variables. An existing variable is referred to by either an expression name (§6.5.6) or a field access expression (§15.11)." --Java Language Specification
First r
is declared and initialized. After the try block executes r
is automatically closed and goes out of scope.
creating resource "resource"
closing resource "resource"
declare resource only
In java 7 a resource needed to be declared and initialized in the resource specification. Since java 9 initialized is not required as long as the variables is effectively final. Here is an example:
try(r) {
//work with resource here
}
multiple resources
Multiple resources can be used in a try-with-resources block.
try(var r1 = new TestResource("r1"); var r2 = new TestResource("r2")) {
//work with resource here
}
Resources are created in order and closed in reverse order.
creating resource "r1"
creating resource "r2"
closing resource "r2"
closing resource "r1"
when creation fails with one resource
Here is an example of what happens when creation fails.
try(var r1 = createFailure()) {
//work with resource here
}
An exception is thrown.
create failure
java.io.IOException: create failure
at com.github.moaxcp.trywithresources.TestResource.createFailure(TestResource.java:12)
at com.github.moaxcp.trywithresources.TestTryWithResources.creat_fails(TestTryWithResources.java:35)
The exception can be caught with a catch block.
try(var r1 = createFailure()) {
//work with resource here
} catch (IOException e) {
System.out.println("caught exception " + e);
}
An exception is thrown and caught.
create failure
caught exception java.io.IOException: create failure
when creation fails with multiple resources
Another scenario is what happens when creation fails with multiple resources.
try(var r1 = new TestResource("r1"); var r2 = createFailure()) {
//work with resource here
} catch (IOException e) {
System.out.println("caught exception " + e);
}
r1
is created, r2
fails to create, then r1
is closed, and then the catch block executes.
creating resource "r1"
create failure
closing resource "r1"
caught exception java.io.IOException: create failure
Notice that r1
is closed before the catch block is executed.
Working with resources
Now that we know the mechanics of creating a resoruce in a try-with-resources block lets examine working with resources inside the block.
Working with one resource
try(var r = new TestResource()) {
r.work();
}
In this case the resource is create, work is performed, and the resource is closed.
creating resource "resource"
performing work with resource "resource"
closing resource "resource"
when the work fails
What happens with the work()
method throws an exception? This can be done by setting the workFailure flag to true
on the TestResource
.
try(var r = new TestResource(true, false)) {
r.work();
}
When work fails the resource should be close and then the exception can be caught or thrown.
creating resource "resource"
performing work with resource "resource"
work failure with resource "resource"
closing resource "resource"
work failure with resource "resource"
java.io.IOException: work failure with resource "resource"
at com.github.moaxcp.trywithresources.TestResource.work(TestResource.java:47)
at com.github.moaxcp.trywithresources.TestTryWithResources.work_with_resource_fails(TestTryWithResources.java:68)
In this case if there was a catch block it would be executed after the resource closes.
when work fails with multiple resources
When work fails with multiple resources the resources are closed in reverse order and the exception is caught or thrown.
try(var r1 = new TestResource("r1", false, false); var r2 = new TestResource("r2", true, false)) {
r1.work();
r2.work();
}
Here r2
is setup to fail when the work()
method is called.
creating resource "r1"
creating resource "r2"
performing work with resource "r1"
performing work with resource "r2"
work failure with resource "r2"
closing resource "r2"
closing resource "r1"
work failure with resource "r2"
java.io.IOException: work failure with resource "r2"
at com.github.moaxcp.trywithresources.TestResource.work(TestResource.java:47)
at com.github.moaxcp.trywithresources.TestTryWithResources.work_with_multiple_resources_fails(TestTryWithResources.java:76)
As you can see, when r2.work()
fails the resources are closed in reverse order and the exception is thrown.
Closing resources
As show before, a try-with-resources block will automatically close the resource but what happens when the close fails?
when close fails with one resource
Failing close can be achieve by setting the closeFailure
flag to true
on the TestResource
.
try(var r = new TestResource(false, true)) {
r.work();
}
When close fails the exception is thrown.
creating resource "resource"
performing work with resource "resource"
closing resource "resource"
close failure with resource "resource"
close failure with resource "resource"
java.io.IOException: close failure with resource "resource"
at com.github.moaxcp.trywithresources.TestResource.close(TestResource.java:56)
at com.github.moaxcp.trywithresources.TestTryWithResources.close_with_resource_fails(TestTryWithResources.java:84)
When close fails with two resources
Here r1 and r2 will fail to close.
try(var r1 = new TestResource("r1", false, true); var r2 = new TestResource("r2", false, true)) {
r1.work();
r2.work();
}
Since resources are closed in reverse order the exception from r2
will suppress the exception from r1
.
creating resource "r1"
creating resource "r2"
performing work with resource "r1"
performing work with resource "r2"
closing resource "r2"
close failure with resource "r2"
closing resource "r1"
close failure with resource "r1"
close failure with resource "r2"
java.io.IOException: close failure with resource "r2"
at com.github.moaxcp.trywithresources.TestResource.close(TestResource.java:56)
at com.github.moaxcp.trywithresources.TestTryWithResources.close_with_two_resources_fails(TestTryWithResources.java:92)
Suppressed: java.io.IOException: close failure with resource "r1"
at com.github.moaxcp.trywithresources.TestResource.close(TestResource.java:56)
at com.github.moaxcp.trywithresources.TestTryWithResources.close_with_two_resources_fails(TestTryWithResources.java:89)
Notice - The exception from closing r1
is suppressed by the exception from r2
.
When work and close fails
Now we are getting to the interesting scenario.
try(var r1 = new TestResource("r1", true, true); var r2 = new TestResource("r2", true, true)) {
r1.work();
}
In this case work
will fail but so will closing the two resources.
creating resource "r1"
creating resource "r2"
performing work with resource "r1"
work failure with resource "r1"
closing resource "r2"
close failure with resource "r2"
closing resource "r1"
close failure with resource "r1"
work failure with resource "r1"
java.io.IOException: work failure with resource "r1"
at com.github.moaxcp.trywithresources.TestResource.work(TestResource.java:47)
at com.github.moaxcp.trywithresources.TestTryWithResources.work_and_close_with_two_resources_fails(TestTryWithResources.java:98)
...
Suppressed: java.io.IOException: close failure with resource "r2"
at com.github.moaxcp.trywithresources.TestResource.close(TestResource.java:56)
at com.github.moaxcp.trywithresources.TestTryWithResources.work_and_close_with_two_resources_fails(TestTryWithResources.java:97)
... 88 more
Suppressed: java.io.IOException: close failure with resource "r1"
at com.github.moaxcp.trywithresources.TestResource.close(TestResource.java:56)
at com.github.moaxcp.trywithresources.TestTryWithResources.work_and_close_with_two_resources_fails(TestTryWithResources.java:97)
... 88 more
Here the work failure is thrown and the two close failures are suppressed.
Summary
Try-with-resources is a very helpful tool when dealing with resources. It is especially helpful when dealing with multiple resources. It saves the developer from having to write correct closing code. It also makes suppressed exceptions available so developers know exactly what failures occurred. This article outlines different mechanics of try-with-resources in different scenarios. Can you think of any other scenarios for try-with-resources?
TestResource
Before I start showing examples there is some code for a TestResource
and a failing factory method that is needed.
class TestResource implements AutoCloseable {
private final String name;
private final boolean workFailure;
private final boolean closeFailure;
public static TestResource createFailure() throws IOException {
System.out.println("create failure");
throw new IOException("create failure");
}
public TestResource() {
name = "resource";
System.out.println("creating resource \"" + name + "\"");
workFailure = false;
closeFailure = false;
}
public TestResource(String name) {
System.out.println("creating resource \"" + name + "\"");
this.name = name;
workFailure = false;
closeFailure = false;
}
public TestResource(boolean workFailure, boolean closeFailure) throws IOException {
this.name = "resource";
System.out.println("creating resource \"" + name + "\"");
this.workFailure = workFailure;
this.closeFailure = closeFailure;
}
public TestResource(String name, boolean workFailure, boolean closeFailure) throws IOException {
System.out.println("creating resource \"" + name + "\"");
this.name = name;
this.workFailure = workFailure;
this.closeFailure = closeFailure;
}
public void work() throws IOException {
System.out.println("performing work with resource \"" + name + "\"");
if(workFailure) {
System.out.println("work failure with resource \"" + name + "\"");
throw new IOException("work failure with resource \"" + name + "\"");
}
}
@Override
public void close() throws IOException {
System.out.println("closing resource \"" + name + "\"");
if(closeFailure) {
System.out.println("close failure with resource \"" + name + "\"");
throw new IOException("close failure with resource \"" + name + "\"");
}
}
}
Top comments (1)
Interesting read. Thanks for trying out these scenarios. I like how it suppresses both close failures when there is a work failure, and let the work failure propagate up. That is what the caller would most likely care about.
I was a little surprised by the r2 create failure scenario, where it closes r1 before the catch block. I would have thought the closure would happen in an auto-generated finally block. However come to think of it, that actually makes sense because it gives the catch block a chance to handle close failures.