DEV Community

loading...
Cover image for Exception Scenarios for Java's try-with-resources

Exception Scenarios for Java's try-with-resources

John Mercier
A software developer. I'm interested in learning new technologies and core language features. I love to dive into legacy code writing tests and refactoring as I go.
・6 min read

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 + "\"");
        }
    }
}

Discussion (1)

Collapse
jingxue profile image
Jing Xue

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.