/jun 9, 2015

Exception Handling with Try with Resources Statement in Java 7

By Asankhaya Sharma

In a previous article, we saw how to avoid nested try-catch-finally blocks in Java. It was pointed out to me that Java 7 (and beyond) has a new try-with-resources construct. It can take multiple resources and ensure that each resource is closed at the end of the statement. I think this new construct is a good way to avoid some of the issues with try-catch-finally statement. In this article, we will have a look at how try-with-resources can avoid nested blocks and help correctly close multiple resources.

Try-with-resources Example

Consider the following snippet of code. We are trying to copy the contents of the input file into the output file using I/O streams.

public void copy() throws FileNotFoundException
{
  InputStream input = new FileInputStream("in.txt");
  OutputStream output = new FileOutputStream("out.txt");
  byte[] buffer = new byte[1024];
  try {
    try {
      while (-1 != input.read(buffer)) {
        output.write(buffer);
      }
    } catch (IOException ex) {
      Logger.getLogger(Easy.class.getName()).log(Level.SEVERE, null, ex);
    } finally {
      input.close();
      output.close();
    }
  } catch (IOException ex) {
    Logger.getLogger(Easy.class.getName()).log(Level.SEVERE, null, ex);
  }
}

In this example, there are two kinds of exceptions that are possible - FileNotFoundException if the file with the given name does not exist and IOException while reading from the file (and writing). For now, to keep things simple, we throw the FileNotFoundException and handle the IOException by catching and logging it. In the finally block we close the I/O streams. The close() method itself can throw an exception, that is handled subsequently. Now, the same example can also be written using try-with-resources as follows:

public void copy(String content) throws FileNotFoundException
{
  try(InputStream input = new FileInputStream("in.txt");
  OutputStream output = new FileOutputStream("out.txt");) {
    byte[] buffer = new byte[1024];
    while (-1 != input.read(buffer)) {
        output.write(buffer);
    }
  } catch (IOException ex) {
    Logger.getLogger(Easy.class.getName()).log(Level.SEVERE, null, ex);
  }
}

The try-with-resources version is much shorter and concise. There is no need call the close() method separately in the finally block. When the execution leaves the try block the resources are closed automatically. The resource must implement the AutoClosable interface to work with the try-with-resources construct. The interface for AutoClosable has only one method as shown below:

public interface AutoClosable {
    public void close() throws Exception;
}

Any class that implements this interface can use the try-with-resources construct to automatically close the resources. You may be wondering what happened to the exception that was thrown while calling the close() method. To answer that lets take a look at the bytecode that is generated by these two versions of copy method.

Try-with-resources Under the Hood

In order to explore the bytecode of the class files generated by the Java compiler we can use the javap utility. It is a command line tool that is bundled with the JDK and it helps in printing user readable bytecode information from class files.

javap -c example.class

After running it on the class file compiled with our first version of copy() method we get the following bytecode (to focus on the exceptions I have removed some lines, the full output is available here).

  public void copy() throws java.io.FileNotFoundException;
    Code:
      29: invokevirtual #33                 // Method java/io/InputStream.read:([B)I
      37: invokevirtual #34                 // Method java/io/OutputStream.write:([B)V
      40: goto          26
      43: aload_1
      44: invokevirtual #35                 // Method java/io/InputStream.close:()V
      47: aload_2
      48: invokevirtual #36                 // Method java/io/OutputStream.close:()V
      51: goto          97
      68: aload         4
      73: aload_1
      74: invokevirtual #35                 // Method java/io/InputStream.close:()V
      77: aload_2
      78: invokevirtual #36                 // Method java/io/OutputStream.close:()V
      81: goto          97
      84: astore        5
      86: aload_1
      87: invokevirtual #35                 // Method java/io/InputStream.close:()V
      90: aload_2
      91: invokevirtual #36                 // Method java/io/OutputStream.close:()V
      94: aload         5
     119: return
    Exception table:
       from    to  target type
          26    43    54   Class java/io/IOException
          26    43    84   any
          54    73    84   any
          84    86    84   any
          26    97   100   Class java/io/IOException

As we can see above, the nested try-catch-finally block in the copy() method generates code for three pairs (InputStream and OutputStream) of calls to close() method. The first two calls are due to the fact that we are closing the stream in the finally block. The close() method is to be executed regardless of the fact whether the try block throws an exception or not. The third call is from the nested try-catch. If the finally block itself has an exception then the control needs to pass to the outer catch rather than the return statement. Thus, the first version generates 3 calls to properly close() the resource. Before we consider the bytecode generated by the try-with-resources version, note that the total number of bytecode instructions generated in this case is 119 and the exception table has 5 entries in it.

For the try-with-resources version of the copy() method we have the following bytecode (full output is available here):

public void copy(java.lang.String) throws java.io.FileNotFoundException;
    Code:
      37: invokevirtual #33                 // Method java/io/InputStream.read:([B)I
      47: invokevirtual #34                 // Method java/io/OutputStream.write:([B)V
      60: ifnull        83
      63: aload         4
      65: invokevirtual #36                 // Method java/io/OutputStream.close:()V
      77: invokevirtual #44                 // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
      85: invokevirtual #36                 // Method java/io/OutputStream.close:()V
     109: ifnull        132
     112: aload         4
     114: invokevirtual #36                 // Method java/io/OutputStream.close:()V
     126: invokevirtual #44                 // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
     134: invokevirtual #36                 // Method java/io/OutputStream.close:()V
     145: ifnull        166
     148: aload_2
     149: invokevirtual #35                 // Method java/io/InputStream.close:()V
     160: invokevirtual #44                 // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
     167: invokevirtual #35                 // Method java/io/InputStream.close:()V
     188: ifnull        209
     191: aload_2
     192: invokevirtual #35                 // Method java/io/InputStream.close:()V
     203: invokevirtual #44                 // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
     210: invokevirtual #35                 // Method java/io/InputStream.close:()V
     236: return
    Exception table:
       from    to  target type
          63    68    71   Class java/lang/Throwable
          26    53    91   Class java/lang/Throwable
          26    53   100   any
         112   117   120   Class java/lang/Throwable
          91   102   100   any
         148   152   155   Class java/lang/Throwable
          12   140   173   Class java/lang/Throwable
          12   140   181   any
         191   195   198   Class java/lang/Throwable
         173   183   181   any
           0   216   219   Class java/io/IOException

From the output it is clear that the version with the new try-with-resources generates a class file with almost twice as much bytecode (236 v/s 119) and the exception table is also over twice the size (11 v/s 5). Now, if we look at the number of calls to the close() method that are generated we see that there are 4 pairs of calls. The bytecode also shows what happens to the exception generated by the close() method. It is suppressed (as shown on line 77, 126, 160 and 203 above). For more details on suppressed exceptions you can check out the Java docs here.

So, in fact using try-with-resources generates a more nested control flow. The reason is that, in the first case, we combined the close() method call for both input and output in the same finally block. The compiler cannot make this choice and handles the most general case, i.e. once for each resource declared in the try statement. On the other hand, the benefit for the developer is obvious since she doesn't need to worry about closing the I/O stream. In conclusion, even though the generated bytecode is much larger, the source code of the program is a lot more readable and concise. Try-with-resources is a good abstraction for developers to adopt and avoids some of the problems that can lead to nested blocks of try-catch statements.

What do you think about the exception handling mechanisms in Java and how often do you use try-with-resources? Let us know in comments or reach out to me on twitter.

Related Posts

By Asankhaya Sharma

Dr. Asankhaya Sharma is the Director of Software Engineering at Veracode. Asankhaya is a cyber security expert and technology leader with over a decade of experience in creating security products for industry, academia and open-source community. He is passionate about building high performing teams and taking innovative products to market. He is also an Adjunct Professor at the Singapore Institute of Technology.