In-depth look: the Java try-with-resources statement
Tags: java
Published: 2022-11-04 12:12:40

Sometime ago I started writing a [JNI](https://docs.oracle.com/en/java/javase/18/docs/specs/jni/index.html) wrapper around [libgit2](https://libgit2.org/).

I quickly gave up on it. I was not confident on my very little C knowledge nor my very little JNI knowledge. So instead I started writing a [pure Java GIT (partial) implementation](https://github.com/objectos/incubator/tree/master/git).

A main takeaway from my brief JNI experience was my newfound appreciation of:

- Java's error signaling via exceptions. Checked exceptions included (as opposed to return codes); and
- Java's exception handling and resource releasing via `try` and `try-with-resources` statements (as opposed to a combination of `if` and `goto` statements).

## What problems did the `try-with-resources` solve?

As mentioned we handle exceptional conditions in Java via the `try` and the `try-with-resources` Java statements. The former exists since the first release of the language. The latter was introduced in Java 7.

At time of writing Java 7 is almost 11 years old. So let's remember why the `try-with-resources` statement was introduced.

I was not there with the language designers. Nor did I research the mailing lists. So this section is about my assumptions. I just want this to be clear.

Anyways, what problems did the `try-with-resources` solve? Wasn't the plain `try` statement enough?

Let's investigate.

### Our running example

For our investigations we will use a class that simulates a resource that needs to be released after it has been used.

For all purposes, think of it being like a `java.io.FileWriter`.

Here is the code:

```java
public class Resource implements Closeable {
  final String name;

  Resource(String name) {
    this.name = name;

    print0("created");
  }

  public static Resource create(String name) throws IOException {
    return new Resource(name);
  }

  @Override
  public void close() throws IOException {
    print0("closed");
  }

  public void write(String s) throws IOException {
    print0(s);
  }

  final void print0(String string) {
    System.out.println(name + ": " + string);
  }
}
```

To use it you first need to create a new instance by invoking the static `create` method. It requires a name. Think of it as a pathname.

The class can throw an `IOException` on creation, just like `new FileWriter` or `Files.newBufferedWriter` would. Examples of reasons why this might happen:

- the specified pathname resolves to a directory. You can't write to a directory as if it were a regular file; or
- the specified pathname could be of a location for which you do not have write permission.

Once the resource object is created it prints `created` to `System.out`.

You can write to it by invoking the `write` method. It does actual I/O as it prints to `System.out`.

After you have done writing you must close the resource by invoking the `close` method. It is specified by the `java.io.Closeable` interface which `Resource` implements. It prints `closed` signaling that closing was successful.

### Single resource

So let's use our class. We will use a single resource to print a string to the console.

First using a plain `try` statement:

```java
public class Try {
  public static void main(String[] args) throws IOException {
    var out = Resource.create("TRY");

    try {
      out.write("Try statement (iteration 1)");
    } finally {
      out.close();
    }
  }
}
```

I know, I am not actually doing any error handling here, but bear with me.

Executing this example results in the following output to the console:

```
TRY: created
TRY: Try statement (iteration 1)
TRY: closed
```

Now, the same example using the `try-with-resources` statement:

```java
public class TryWithResources {
  public static void main(String[] args) throws IOException {
    try (var out = Resource.create("TWR")) {
      out.write("Try-with-resources statement (iteration 1)");
    }
  }
}
```

Prints:

```
TWR: created
TWR: Try-with-resources statement (iteration 1)
TWR: closed
```

The `try-with-resources` is more concise, the closing happens automatically and the resource variable is scoped to the statement itself.

Still, one can say that, in this example, the `try` statement looks manageable, does it not?

I tend to agree, but I do not think this example illustrates the main reason for using the `try-with-resources` statement.

### Two resources

I think most problems arise when using more than one resource. This can be a common use-case, for instance:

- transferring data from an `InputStream` to an `OutputStream`;
- same thing but using Java NIO channels; or
- using plain JDBC and dealing with connection, statement and result set.

So let's write a version using two resources:

```java
public class Try {
  // Please avoid code like this in production
  public static void main(String[] args) throws IOException {
    var a = Resource.create("A");
    var b = Resource.create("B");

    try {
      a.write("Try statement (iteration 2)");
      b.write("Try statement (iteration 2)");
    } finally {
      a.close();
      b.close();
    }
  }
}
```

This example has a number of problems which we will discuss in this section. For now, let's run it as it is:

```
A: created
B: created
A: Try statement (iteration 2)
B: Try statement (iteration 2)
A: closed
B: closed
```

While using the `try-with-resources` statement:

```java
public class TryWithResources {
  public static void main(String[] args) throws IOException {
    try (var a = Resource.create("A"); var b = Resource.create("B")) {
      a.write("Try-with-resources statement (iteration 2)");
      b.write("Try-with-resources statement (iteration 2)");
    }
  }
}
```

Gives:

```
A: created
B: created
A: Try-with-resources statement (iteration 2)
B: Try-with-resources statement (iteration 2)
B: closed
A: closed
```

Apart from the closing happening in a different order (we will look into it later) both examples produce an equivalent output.

So now what?

### Two resources: error on creation

Let's modify our `Resource` class to simulate an error on creation. As mentioned earlier, thinking the class as a `FileWriter`, this could be caused by specifying a location for which we do not have writing permission:

```java
public static Resource throwOnCreate(String name) throws IOException {
  throw new IOException("Failed to create: " + name);
}
```

And we use it with the plain `try` statement:

```java
public class Try {
  // Please avoid code like this in production
  public static void main(String[] args) throws IOException {
    var a = Resource.create("A");
    var b = Resource.throwOnCreate("B");

    try {
      a.write("Try statement (iteration 3)");
      b.write("Try statement (iteration 3)");
    } finally {
      b.close();
      a.close();
    }
  }
}
```

Notice we reversed the order of the close statements.

Once again, this example still contain warning signs (in particular in the `finally` block) which will discuss as we go.

Running this example prints:

```
A: created
Exception in thread "main" java.io.IOException: Failed to create: B
        at shared.Resource.throwOnCreate(Resource.java:29)
        at iter3.Try.main(Try.java:14)
```

Resource `A` was created but it was never closed.

On the other hand, the same example using the `try-with-resources` statement:

```java
public class TryWithResources {
  public static void main(String[] args) throws IOException {
    try (var a = Resource.create("A"); var b = Resource.throwOnCreate("B")) {
      a.write("Try-with-resources statement (iteration 3)");
      b.write("Try-with-resources statement (iteration 3)");
    }
  }
}
```

Prints:

```
A: created
A: closed
Exception in thread "main" java.io.IOException: Failed to create: B
        at shared.Resource.throwOnCreate(Resource.java:29)
        at iter3.TryWithResources.main(TryWithResources.java:13)
```

Resource `A` was created _and_ was properly closed.

### Two resources: error on closing

Let's modify our `Resource` class once again to simulate an error when executing the `close` method.

This can happen when using a buffered writer. During the close operation, flushing the buffer to the filesystem will fail if the disk runs out of space.

So we create a subclass that throws in the `close` method:

```java
private static class ThrowOnClose extends Resource {
  ThrowOnClose(String name) {
    super(name);
  }

  @Override
  public void close() throws IOException {
    print0("close() called");

    throw new IOException("Failed to close: " + name);
  }
}
```

And our `create` method for this class is named `throwOnClose`:

```java
public static Resource throwOnClose(String name) throws IOException {
  return new ThrowOnClose(name);
}
```

Let's use it in the plain `try` case. We will have the `B` resource to throw on the close operation:

```java
public class Try {
  // Please avoid code like this in production
  public static void main(String[] args) throws IOException {
    var a = Resource.create("A");
    var b = Resource.throwOnClose("B");

    try {
      a.write("Try statement (iteration 4)");
      b.write("Try statement (iteration 4)");
    } finally {
      b.close();
      a.close();
    }
  }
}
```

Running this example results in:

```
A: created
B: created
A: Try statement (iteration 4)
B: Try statement (iteration 4)
B: close() called
Exception in thread "main" java.io.IOException: Failed to close: B
        at shared.Resource$ThrowOnClose.close(Resource.java:54)
        at iter4.Try.main(Try.java:20)
```

And the resource `A` was not closed.

Let's write the same example using the `try-with-resources` statement:

```java
public class TryWithResources {
  public static void main(String[] args) throws IOException {
    try (var a = Resource.create("A"); var b = Resource.throwOnClose("B")) {
      a.write("Try-with-resources statement (iteration 4)");
      b.write("Try-with-resources statement (iteration 4)");
    }
  }
}
```

Which gives:

```
A: created
B: created
A: Try-with-resources statement (iteration 4)
B: Try-with-resources statement (iteration 4)
B: close() called
A: closed
Exception in thread "main" java.io.IOException: Failed to close: B
        at shared.Resource$ThrowOnClose.close(Resource.java:54)
        at iter4.TryWithResources.main(TryWithResources.java:16)
```

Resource `A` was properly closed.

### Two resources: exception during the `try` block

We mentioned earlier that, so far, we were not actually doing any error handling. In other words, we were not declaring the `catch` part of our statement.

Granted, in our example, I am not sure we can do much. Except maybe:

- give a proper message to the user (as opposed to a stack trace); and
- stop the execution normally (as opposed to abruptly by an uncaught exception).

The `try` example was refactored to:

```java
public class Try {
  // Please avoid code like this in production
  public static void main(String[] args) throws IOException {
    Resource a = null;
    Resource b = null;

    try {
      a = Resource.create("A");
      a.throwOnWrite("Try statement (iteration 5)");

      b = Resource.create("B");
      b.write("Try statement (iteration 5)");
    } catch (IOException e) {
      System.err.println("The program failed: " + e.getMessage());

      return; // explicitly stop execution
    } finally {
      b.close();
      a.close();
    }

    System.out.println("More instructions...");
  }
}
```

Notice that, on the `A` resource, we invoked a `throwOnWrite` method.

It is a method to simulate an `IOException` during a `write` operation:

```java
public void throwOnWrite(String s) throws IOException {
  throw new IOException("Failed to write: " + name);
}
```

Running the example:

```
A: created
The program failed: Failed to write: A
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "shared.Resource.close()" because "b" is null
        at iter5.Try.main(Try.java:27)
```

The `A` resource does not get closed. To make matters worse, the program additionally exits abruptly with an uncaught exception: a `NullPointerException` occurred when trying to close the `B` resource.

The reason it happens is that the `B` resource is never created as the `A` resource throws before `B` can be created. And we deliberately did not include a null-check before closing the resource.

Let's write the same example using the `try-with-resources` statement:

```java
public class TryWithResources {
  public static void main(String[] args) throws IOException {
    try (var a = Resource.create("A"); var b = Resource.create("B")) {
      a.throwOnWrite("Try-with-resources statement (iteration 5)");
      b.write("Try-with-resources statement (iteration 5)");
    } catch (IOException e) {
      System.err.println("The program failed: " + e.getMessage());

      return; // explicitly stop execution
    }

    System.out.println("More instructions...");
  }
}
```

Results:

```
A: created
B: created
B: closed
A: closed
The program failed: Failed to write: A
```

Both resources are properly closed _and_ the program exits normally.

I am sure that, if we kept looking, we would find more issues. But this should do it for now.

Let's now see what's in a `try-with-resources` statement.

## What's in a `try-with-resources` statement?

So what is in a `try-with-resources` statement? Meaning can we rewrite it using the plain `try` statement?

The answer is yes. In fact that is what the Java compiler does. Others have written about it, so I will not here. For example:

- Heinz Kabutz [writes about it here](https://www.javaspecialists.eu/archive/Issue259-Try-With-Resource-in-Plain-Java.html)
- there is also this [StackOverflow answer](https://stackoverflow.com/a/7860144)

Here I will do something slightly different. Before we go into it, let's first look what the specification says.

### Java Language Specification (JLS) 18

At time of writing, Java 18 is the current release of the language. The `try-with-resources` statement is in [section 14.20.3](https://docs.oracle.com/javase/specs/jls/se18/html/jls-14.html#jls-14.20.3) of the Java language specification. In the first paragraph we read (emphasis mine):

> A try-with-resources statement is parameterized with variables (known as resources) that are initialized before execution of the try block and closed automatically, in the **reverse order** from which they were initialized

So this explains why we had to reverse the order of the close operations in the previous section.

In [section 14.20.3.1](https://docs.oracle.com/javase/specs/jls/se18/html/jls-14.html#jls-14.20.3.1) we find the specification of the code the compiler must emit:

```
{
    final {VariableModifierNoFinal} R Identifier = Expression;
    Throwable #primaryExc = null;

    try ResourceSpecification_tail
        Block
    catch (Throwable #t) {
        #primaryExc = #t;
        throw #t;
    } finally {
        if (Identifier != null) {
            if (#primaryExc != null) {
                try {
                    Identifier.close();
                } catch (Throwable #suppressedExc) {
                    #primaryExc.addSuppressed(#suppressedExc);
                }
            } else {
                Identifier.close();
            }
        }
    }
}
```

The specification is recursive on `ResourceSpecification_tail` when more than one resource is declared. In other words, when two resources are declared, there will be a second `try` nested in this `try`. When three resources are declared, there will be a third `try` nested in the second one. And so on.

We are not trying to write a Java compiler. We just want to understand what happens when a `try-with-resources` statement is executed.

So, can we write a version of this code in such ways that:

- produces the same effect, that is, "does the same job"; and
- is more readable by a Java developer (as opposed to being readable by a JVM)?

Let's give it a try.

### A closing utility

A good initial refactoring candidate is the code in the `finally` block. This block:

- closes the resource in a `null` safe way; and
- handles the exception that can be thrown during the `close` operation.

Let's extract it to a utility class and rewrite slightly:

```java
public final class Close {
  public static Throwable ifPossible(Throwable t, AutoCloseable c) {
    var res = t;

    if (c != null) {
      try {
        c.close();
      } catch (Throwable e) {
        if (res == null) {
         res = e;
        } else {
         res.addSuppressed(e);
        }
      }
    }

    return res;
  }
}
```

First there is a `null` check on the `AutoCloseable` instance. It is there so the `NullPointerException` we faced in the "Two resources: exception during the `try` block" example does not happen.

Next the resource is closed in a `try-catch` statement. The `try-catch` is necessary as the `close()` operation might throw:

- the `Exception` it declares it `throws`;
- a `RuntimeException`. For example, a `NullPointerException` from a programming error;
- an `Error`. For example, a `StackOverflowError` from a programming error.

We saw what happens if we don't catch this throwable in the "Two resources: error on closing" example.

In the `catch` block we check if there was a previous exception. This would typically be the exception caught in the main `try` block. In our examples this would be the one thrown while trying to write to the resource.

Now, if there were such exception it would be a distinct exception than the one thrown during a close operation. By this I mean that, even though _in this particular case_ they might have some relation (both were thrown by the same instance), they do not share a cause effect. In other words, one should not be declared as the cause of the other.

On the other hand, it would be unwise to simply ignore one exception or the other.

### Suppressed exception

So that the closing exception is not ignored, Java 7 introduced the concept of [_suppressed exceptions_](https://docs.oracle.com/en/java/javase/18/docs/api/java.base/java/lang/Throwable.html#addSuppressed(java.lang.Throwable)).

The exception thrown during the close operation is added as a _suppressed_ exception of the previous exception if the latter exists.

If there was no previous exception then the exception thrown during the close operation is simply returned by our utility and it becomes our primary exception.

### Rewriting our two resources `try` examples

Let's use our new utility class by rewriting the two resources examples.

A _happy path_ examples becomes:

```java
public class Try {
  public static void main(String[] args) {
    Throwable rethrow = null;

    Resource a = null;
    Resource b = null;

    try {
      a = Resource.create("A");
      b = Resource.create("B");

      a.write("Try statement (iteration 6)");
      b.write("Try statement (iteration 6)");
    } catch (Throwable e) {
      rethrow = e;
    } finally {
      rethrow = Close.ifPossible(rethrow, b);
      rethrow = Close.ifPossible(rethrow, a);
    }

    if (rethrow != null) {
      System.err.println("The program failed: " + rethrow.getMessage());

      return; // explicitly stop execution
    }

    System.out.println("More instructions...");
  }
}
```

The general _recipe_ is:

1. begin with a `null` throwable local variable named `rethrow`
1. declare all your resources initialized will `null`
1. at the start of the `try` block, create all your resources
1. do your work
1. declare a single catch-all `catch` block
1. simply assign the caught throwable to the `rethrow` variable
1. in the `finally` block, using our utility and in the reverse order of the resource creation close all of the resources
1. if any error occurred, either in the `try` block or in the `finally` block, the `rethrow` variable will be `rethrow != null`. Handle the error appropriately
1. if no errors occurred, continue the execution

Running this program results in:

```
A: created
B: created
A: Try statement (iteration 6)
B: Try statement (iteration 6)
B: closed
A: closed
More instructions...
```

Which is equivalent to the output of the following `try-with-resources` version:

```java
public class TryWithResources {
  public static void main(String[] args) {
    try (var a = Resource.create("A"); var b = Resource.create("B")) {
      a.write("Try-with-resources statement (iteration 6)");
      b.write("Try-with-resources statement (iteration 6)");
    } catch (Throwable e) {
      System.err.println("The program failed: " + e.getMessage());

      return; // explicitly stop execution
    }

    System.out.println("More instructions...");
  }
}
```

Both versions are equivalent in the sense that both "do the same job". They are not "bytecode equivalent" however.

The `try-with-resources` version:

- is more concise;
- the closing happens automatically; and
- the resource variable is scoped to the statement itself.

Additionally, the plain `try` statement version is error-prone:

- a good indicator is the fact that the recipe we wrote has nine steps in it.

The remaining examples can be found in the [GitHub repo](https://github.com/objectos/blog-examples/tree/master/p005-try-with-resources/src/main/java/iter6).

### Restricting the catch-all to catch `IOException` only

Did you notice that, in the previous `try-with-resources` example, we used a _catch-all_ `catch` clause?

Meaning that we declared it to catch `Throwable` and not `IOException` which is the only checked exception declared to be thrown in the statement:

- by the `create` operation;
- by the `write` operation; and
- by the `close` operation.

If using an IDE it would most likely suggest for the clause to catch an `IOException` only and not a `Throwable`:

```java
} catch (IOException e) {
  System.err.println("The program failed: " + e.getMessage());

  return; // explicitly stop execution
}
```

To make our `try` example equivalent our single `if` statement after the `try` statement would have to be rewritten to:

```java
if (rethrow instanceof RuntimeException re) {
  throw re;
}

if (rethrow instanceof Error err) {
  throw err;
}

if (rethrow instanceof IOException ioe) {
  System.err.println("The program failed: " + ioe.getMessage());

  return; // explicitly stop execution
}
```

Meaning that:

If either a `RuntimeException` or an `Error` is thrown in either the `try` block or during the closing of the resources then the program ends abruptly by an uncaught exception.

If an `IOException` is thrown in either the `try` block or during the closing of the resources then the program ends normally with the message "The program failed: {exception message}" printed to `System.err`.

### Suppressed exception example

As a final example, let's see the suppressed exceptions in action:

```java
public class TrySuppressed {
  public static void main(String[] args) throws IOException {
    Throwable rethrow = null;

    Resource a = null;
    Resource b = null;

    try {
      a = Resource.create("A");
      b = Resource.throwOnClose("B");

      a.throwOnWrite("Try statement (suppressed in action)");
      b.write("Try statement (suppressed in action)");
    } catch (Throwable e) {
      rethrow = e;
    } finally {
      rethrow = Close.ifPossible(rethrow, b);
      rethrow = Close.ifPossible(rethrow, a);
    }

    if (rethrow != null) {
      System.err.println("The program failed: " + rethrow.getMessage());

      for (Throwable s : rethrow.getSuppressed()) {
        System.err.println("\tsuppressed: " + s.getMessage());
      }

      return; // explicitly stop execution
    }

    System.out.println("More instructions...");
  }
}
```

Resource `B` is created such as it throws on closing. Resource `A` throws during writing.

Running this example gives:

```
A: created
B: created
B: close() called
A: closed
The program failed: Failed to write: A
        suppressed: Failed to close: B
```

The `close` operation was invoked in both resources.

And the close exception thrown by `B` was added as a suppressed exception to the exception thrown by `A`.

### `AutoCloseable`

Before we go I would like to write a few words on the `AutoCloseable` type.

As you can see in its [`Javadocs`](https://docs.oracle.com/en/java/javase/18/docs/api/java.base/java/lang/AutoCloseable.html) it was introduced in Java 7.

It marks types that are allowed to be used as resources in `try-with-resources` statements. It is defined in the [`try-with-resources` section](https://docs.oracle.com/javase/specs/jls/se18/html/jls-14.html#jls-14.20.3) of the Java language specification:

> The type of a local variable declared in a resource specification, or the type of an existing variable referred to in a resource specification, must be a subtype of AutoCloseable, or a compile-time error occurs.

Before its introduction there was no standard interface to indicate that a type had a method that had to be invoked in order for resources to be released. Yes, there was the [`Closeable`](https://docs.oracle.com/javase/6/docs/api/java/io/Closeable.html) interface introduced in Java 5.

By the way, `Closeable` was retrofitted to extend `AutoCloseable`.

But not all "things that can be closed" implemented it. For instance:

- **java.util.zip.ZipFile**. It had a `close()` method throwing `IOException` in Java 6. But it did not implement `Closeable`. Compare the javadocs: [Java 6](https://docs.oracle.com/javase/6/docs/api/java/util/zip/ZipFile.html) vs. [Java 7](https://docs.oracle.com/javase/7/docs/api/java/util/zip/ZipFile.html)
- **java.sql.Connection**, **java.sql.Statement** and **java.sql.ResultSet**. All had a `close()` method throwing `SQLException` in Java 6. But they did not implement a common interface. In Java 7 they all implemented `AutoCloseable`. Compare the javadocs for the `Statement` interface: [Java 6](https://docs.oracle.com/javase/6/docs/api/java/sql/Statement.html) vs. [Java 7](https://docs.oracle.com/javase/7/docs/api/java/sql/Statement.html)

## Conclusion

In this blog post we took a in-depth look in the `try-with-resources` Java statement.

We saw problems that can arise from the plain `try` statement when dealing with resources that must be closed programmatically. With this we wanted to understand what issues the `try-with-resources` was meant to address.

We then presented its formal definition in the Java language specification. We tried to understand it by rewriting it using the plain `try` statement and a small closing utility. In the process we saw two API changes introduced in Java 7 to support it:

- suppressed exceptions
- the `AutoCloseable` interface

Finally we saw some examples of JDK classes that were modified so they could be used with the `try-with-resources` statement.

The full source code of the examples listed in this post can be found in this [GitHub repository](https://github.com/objectos/blog-examples/tree/master/p005-try-with-resources/src/main/java).