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.