Thursday, January 23, 2014

Java 7 try-with-resources Statement

Introduction

One of the most exciting features added in Java 7 is automatic resource management (a.k.a. try-with-resources or TWR). While basic TWR usage looks simple and straightforward there are certain subtleties which can puzzle even experienced developers. So it’s better to be prepared :)

Old School Resource Management

To fully appreciate TWR approach to resource management it is worth comparing it to the one being used with earlier versions of Java. Suppose you want to open a file, write certain data into it and then close the file. In addition you want to make sure that the file is closed regardless of whether write operation succeeded or failed. The most idiomatic way of doing this in Java 6 and earlier is via try-finally block:
public void writeDataToFile(String fileName, byte[] data)
        throws IOException {

    OutputStream out = null;

    try {
        out = new FileOutputStream(fileName);
        out.write(data);
    } finally {
        if (out != null) {
            out.close();
        }
    }
}
The finally block always executes when the try block exits. This ensures that the file referenced by out variable gets closed even when try block is terminated abruptly because of an exception. Cool, this is exactly what we wanted.

It is worth noting that in production code it would be better to wrap FileOutputStream object with BufferedOutputStream instance for efficiency or to leverage NIO.2 facilities, but for this post I decided to keep examples simple and focused.

Checking for null in the finally block is necessary. If construction of FileOutputStream object will throw an exception then out variable will keep its initial null value. Hence attempt to call out.close() will generate NullPointerException which in turn will suppress the original IO-related exception.

Pay attention to throws clause. It allows for propagating checked exceptions up to the caller thus making code in writeDataToFile() method more concise and readable. Without throws clause it would be a bit more cluttered:
public void writeDataToFile(String fileName, byte[] data) {
    OutputStream out = null;

    try {
        out = new FileOutputStream(fileName);
        out.write(data);
    } catch (IOException e) {
        // exception handling code
    } finally {
        if (out != null) {
            try {
                out.close();
            } catch (IOException e) {
                // exception handling code
            }
        }
    }
}
As you can see the code for managing even a single resource looks pretty verbose. What if there are a few? Suppose you want to read data from a URL and save it to a file. The following code can be used to do that:
public void saveUrlContentsToFile(URL url, String fileName) {
    InputStream in = null;

    try {
        in = url.openStream();

        OutputStream out = null;
                
        try {
            out = new FileOutputStream(fileName);                

            int len;
            byte[] buf = new byte[8192];
            
            while ((len = in.read(buf)) >= 0) {
                out.write(buf, 0, len);
            }
        } catch (IOException e) {
            // exception handling code
        } finally {
            try {
                if (out != null) {
                    out.close();
                }
            } catch (IOException e) {
                // exception handling code
            }
        }
    } catch (IOException e) {
        // exception handling code
    } finally {
        try {
            if (in != null) {
                in.close();
            }
        } catch (IOException e) {
            // exception handling code
        }
    }
}
It looks like a mess, isn't it? Fortunately Java 7 try-with-resources statement makes resource management much more pleasant experience.

Managing a Single Resource

Ok, without further ado let's take a look at how TWR can be leveraged to improve examples from the previous section. Method writeDataToFile() could be rewritten like this:
public void writeDataToFile(String fileName, byte[] data)
        throws IOException {

    try (OutputStream out = new FileOutputStream(fileName)) {
        out.write(data);
    }
}
As you can see the code looks more clear and concise. After try block exits (normally or abruptly) resource referenced by out variable is automatically closed. Also there's no need to worry about null value of out variable. It is handled by TWR. Execution of the following (contrived and useless but still working) code will result in printing TWR rocks! without generating any kind of exception:
public class MainClass {
    public static void main(String[] args) throws IOException {
        try (InputStream in = null) {
            System.out.println("TWR rocks!");
        }
    }
}
TWR also takes appropriate action to propagate exception being thrown during resource initialization to the caller. Assuming there is no file named "inexistent-file" in the program running directory the following code generates FileNotFoundException:
public class MainClass {
    public static void main(String[] args) throws IOException {
        try (InputStream in = new FileInputStream("inexistent-file")) {
            System.out.println("TWR rocks!");
        }
    }
}
This is different from analogous code without TWR and null check (described in the previous section) which suppresses FileNotFoundException and throws NullPointerException instead:
public class MainClass {
    public static void main(String[] args) throws IOException {
        InputStream in = null;

        try {
            in = new FileInputStream("inexistent-file");
        } finally {
            in.close();
        }
    }
}
Last note: variables referencing to resources managed by TWR are implicitly final. Hence an attempt to assign to it inside try-with-resources block will result in compile-time error:
try (InputStream in = new FileInputStream("inexistent-file")) {
    // error: “in” is implicitly final
    in = new FileInputStream("another-file");
    // ...
}

Managing Multiple Resources

Let’s rewrite saveUrlContentsToFile() method using TWR to see how a single try-with-resources statement can easily manage multiple resources:
public void saveUrlContentsToFile(URL url, String fileName)
        throws IOException {
    
    try (InputStream in = url.openStream();
         OutputStream out = new FileOutputStream(fileName)) {

        int len;
        byte[] buf = new byte[8192];

        while ((len = in.read(buf)) > 0) {
            out.write(buf, 0, len);
        }
    }
}
Compare it to the original version. As you can see, with increasing number of resources TWR usage gets more and more beneficial. After try block exits (normally or abruptly) resources referenced by in and out variables are automatically closed. An interesting thing to note is that resources in TWR block are initialized from left to right (top to bottom) and closed in reverse order.

What happens if initialization of the resource referenced by in succeeds but initialization of the resource referenced by out throws an exception of type E? Fortunately TWR is smart enough to handle this situation. In this case the resource referenced by in will be automatically closed and the whole try-with-resources block will be terminated abruptly propagating the exception of type E up the call stack.

In general, if you have n resources (n > 1) and initialization of one of them fails, then all resources which have already been successfully initialized will be automatically closed. In the following example all resources starting from r1 and up to ri (ri excluded) will be automatically closed:
try (R1 r1 = new R1();
     R2 r2 = new R2();
     // ...
     Ri ri = new Ri(); // this initialization fails
     // ...
     Rn rn = new Rn()) {
    // some action
}
Ok, so far so good, but wait a minute... Closing a resource can also generate an exception. How TWR behaves in this case? Fortunately, try-with-resources statement can handle this situation too. It will make an attempt to automatically close all managed resources regardless of whether try block itself or closing procedure of any managed resource exits normally or abruptly. All exceptions (if any) generated in the process will be collected and combined via Throwable#addSuppressed method (rules for combining suppressed exceptions are a bit complicated, all the gory details can be found in JLS-14.20.3.1). Let's take a look at a code snippet:
try (R1 r1 = new R1();
     R2 r2 = new R2(); // closing fails with exception of type E2
     R3 r3 = new R3(); // closing fails with exception of type E3
     R4 r4 = new R4();
     R5 r5 = new R5()) {
    // exits normally or abruptly with exception of type E
}
Assuming that initialization of all resources went fine the execution result of the previous example can be as follows:
  • If try block exits normally then the whole try-with-resources block terminates abruptly with exception of type E3 having exception of type E2 in its suppressed array (accessible via Throwable#getSuppressed()).
  • If try block exits abruptly then the whole try-with-resources block terminates abruptly with exception of type E having exceptions of types E3 and E2 in its suppressed array (accessible via Throwable#getSuppressed()).
Two previous code snippets and related discussion may seem a bit abstract and confusing. Let's play with more concrete examples to gain some hands-on experience. To get started let's create the following helper classes:
class VerboseResource implements AutoCloseable {
    private String name;

    public VerboseResource(String name) {
        this.name = name;
        System.err.println("initializing " + name);
    }

    @Override
    public void close() throws IOException {
        System.err.println("closing " + name);
    }

    protected String getName() {
        return name;
    }
}

class InitFailedResource extends VerboseResource {
    public InitFailedResource(String name) {
        super(name);
        throw new IllegalStateException(
                "unable to initialize " + name);
    }
}

class CloseFailedResource extends VerboseResource {
    public CloseFailedResource(String name) {
        super(name);
    }

    @Override
    public void close() throws IOException {
        super.close();
        throw new IllegalStateException(
                "unable to close " + getName());
    }
}
Interface AutoCloseable was added in Java 7. It should be implemented by any class which is intended to be managed by try-with-resources statement (see JLS-14.20.3). Interface Closeable was retrofitted to implement AutoCloseable, so that every class which implements Closeable can be used with TWR. As you can see, helper classes are pretty simple and straightforward:
  • VerboseResource outputs an appropriate message when constructor or close() method gets executed.
  • InitFailedResource inherits from VerboseResource and throws an exception during initialization of a resource.
  • CloseFailedResource inherits from VerboseResource and throws an exception during closing of a resource.
Now let’s create a simple program that uses our fresh helper classes to successfully initialize and close three resources:
public class MainClass {
    public static void main(String[] args) throws IOException {
        try (VerboseResource res1 = new VerboseResource("res1");
             VerboseResource res2 = new VerboseResource("res2");
             VerboseResource res3 = new VerboseResource("res3")) {

            System.err.println("inside try block");
        }
    }
}
The output produced by the program confirms that resources are acquired from top to bottom and released in reverse order:
initializing res1
initializing res2
initializing res3
inside try block
closing res3
closing res2
closing res1
Now let’s see what happens if initialization of a resource fails:
public class MainClass {
    public static void main(String[] args) throws IOException {
        try (VerboseResource    res1 = new VerboseResource("res1");
             VerboseResource    res2 = new VerboseResource("res2");
             InitFailedResource res3 = new InitFailedResource("res3");
             VerboseResource    res4 = new VerboseResource("res4")) {

            System.err.println("inside try block");
        }
    }
}
The output produced by the program confirms that all successfully initialized resources (res1 and res2 in this case) are automatically closed regardless of other resources' initialization result. As you might expect, the code in try block is not executed:
initializing res1
initializing res2
initializing res3
closing res2
closing res1
Exception in thread "main" java.lang.IllegalStateException: unable to initialize res3
 at com.apolunin.twr.InitFailedResource.(MainClass.java:26)
 at com.apolunin.twr.MainClass.main(MainClass.java:46)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
 at java.lang.reflect.Method.invoke(Method.java:601)
 at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)
Now let’s see what happens if closing of a resource fails:
public class MainClass {
    public static void main(String[] args) throws IOException {
        try (VerboseResource     res1 = new VerboseResource("res1");
             CloseFailedResource res2 = new CloseFailedResource("res2");
             VerboseResource     res3 = new VerboseResource("res3");
             VerboseResource     res4 = new VerboseResource("res4");
             CloseFailedResource res5 = new CloseFailedResource("res5")) {

            System.err.println("inside try block");
        }
    }
}
As you can see from the output, an attempt is made to release every managed resource. The first exception encountered (thrown by res5 closing procedure) is propagated up the call stack with the second one (thrown by res2 closing procedure) added to suppressed list:
initializing res1
initializing res2
initializing res3
initializing res4
initializing res5
inside try block
closing res5
closing res4
closing res3
closing res2
closing res1
Exception in thread "main" java.lang.IllegalStateException: unable to close res5
 at com.apolunin.twr.CloseFailedResource.close(MainClass.java:38)
 at com.apolunin.twr.MainClass.main(MainClass.java:51)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
 at java.lang.reflect.Method.invoke(Method.java:601)
 at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)
 Suppressed: java.lang.IllegalStateException: unable to close res2
There's a lot of possible execution paths of try-with-resources statement. Interested readers can find detailed specification in JLS-14.20.3. For deeper understanding of TWR I would recommend reading related JLS sections and then play around with aforementioned helper classes to clarify the most confusing cases.

The last but not the least. There's a common idiom in Java of using "Decorator" design pattern for resource chaining:
OutputStream out = new BufferedOutputStream(new FileOutputStream(“file.dat”));

When it comes to try-with-resources statement it should be used with caution. The previous code snippet doesn’t interact well with TWR. Let's add another helper class to illustrate the point:
class ChainedInitFailedResource extends InitFailedResource {
    private VerboseResource resource;

    public ChainedInitFailedResource(String name,
            VerboseResource resource) {
        
        super(name);
        this.resource = resource;
    }

    @Override
    public void close() throws IOException {
        resource.close();
        super.close();
    }
}
The whole purpose of class ChainedInitFailedResource is to make resource chaining possible while keeping our trace output at the same time. Let's take a look at the following example:
public class MainClass {
    public static void main(String[] args) throws IOException {
        try (VerboseResource res1 = new VerboseResource("res1");
             ChainedInitFailedResource res3 = new ChainedInitFailedResource("res3",
                     new VerboseResource("res2"))) {

            System.err.println("inside try block");
        }
    }
}
The output produced by this program looks like this:
initializing res1
initializing res2
initializing res3
closing res1
Exception in thread "main" java.lang.IllegalStateException: unable to initialize res3
 at com.apolunin.twr.InitFailedResource.(MainClass.java:26)
 at com.apolunin.twr.ChainedInitFailedResource.(MainClass.java:48)
 at com.apolunin.twr.MainClass.main(MainClass.java:62)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
 at java.lang.reflect.Method.invoke(Method.java:601)
 at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)
As you can see from the output, resource res2 (chained inside res3) was NOT closed. To avoid such unpleasant surprises each managed resource should be specified separately:
public class MainClass {
    public static void main(String[] args) throws IOException {
        try (VerboseResource res1 = new VerboseResource("res1");
             VerboseResource res2 = new VerboseResource("res2");
             ChainedInitFailedResource res3 = new ChainedInitFailedResource("res3", res2)) {

            System.err.println("inside try block");
        }
    }
}
Output produced by the program confirms that res2 is released this time:
initializing res1
initializing res2
initializing res3
closing res2
closing res1
Exception in thread "main" java.lang.IllegalStateException: unable to initialize res3
 at com.apolunin.twr.InitFailedResource.(MainClass.java:26)
 at com.apolunin.twr.ChainedInitFailedResource.(MainClass.java:48)
 at com.apolunin.twr.MainClass.main(MainClass.java:63)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
 at java.lang.reflect.Method.invoke(Method.java:601)
 at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)

Using catch and finally Blocks with TWR

It is possible to use catch and/or finally blocks with try-with-resources statement. There are two important points to remember:
  • catch clause of TWR statement can catch exceptions generated during automatic initialization or closing of any resource.
  • finally clause of TWR statement gets executed after all resources have been closed (or attempted to be closed).
Here is a program to illustrate the aforementioned points:
public class MainClass {
    public static void main(String[] args) throws IOException {
        try (VerboseResource     res1 = new VerboseResource("res1");
             VerboseResource     res2 = new VerboseResource("res2");
             CloseFailedResource res3 = new CloseFailedResource("res3")) {

            System.err.println("inside try block");
        } catch (IllegalStateException e) {
            System.err.println("inside catch block: " + e.getMessage());
        } finally {
            System.err.println("inside finally block");
        }
    }
}
The program produces the following output:
initializing res1
initializing res2
initializing res3
inside try block
closing res3
closing res2
closing res1
inside catch block: unable to close res3
inside finally block
As it confirms the order of execution is as follows:
  • resources are initialized from top to bottom;
  • code inside try block gets executed;
  • an attempt is made to automatically close all resources (in reverse order);
  • catch clause of TWR statement handles exception;
  • finally clause of TWR statement gets executed.

Conclusion

Ok, that's it. I hope now you're armed with thorough enough understanding of a powerful try-with-resources feature to be able to use it in day-to-day programming tasks. To deepen your knowledge I would recommend reading JLS-14.20.3 and play around with helper classes created in this article or something similar.

I hope you enjoyed reading :)
Andrew

3 comments:

  1. Nice! Thank you!
    Why not to tell about it on OdJUG?

    ReplyDelete
  2. Thank you for the awesome examples! Very clear.
    I was wondering about the decorator problem. Now I know how to avoid it.

    ReplyDelete
  3. We are a leading software development services in delhi ,which works as per the client requirements and give provide software.

    ReplyDelete