Try-Catch-Resource and the Exception.getSuppressed() Method

Java 7 is out there for such a long time now so this post is even not fashionably late but still there is one aspect of the try-with-resource construct which, in my opinion, is sometimes overlooked: In this post I would like to point out some behavioral change related to exception suppression which might affect code migrating from older Java versions.


Old Code

Let's assume the following pre-Java 7 code, for the example I assume a dummy OutputStream ('SampleStream') which throws IOWriteException when write() is invoked and IOCloseException when close() is invoked (both are faked exceptions derive from IOException). For the sake of the example both exceptions are always thrown - regardless of the state of the application/stream. Which exception would be thrown by the method below?

public void testTraditionalTryCatchWithExceptionOnFinally() throws IOException {
  OutputStream os = null;
  try {
    os = new SampleStream(); 
    os.write(0); // Throws IOWriteException 
  } finally {
    if (os != null) {
      os.close(); // Throws IOCloseException
  }
}

The invocation of os.write() throws IOWriteException which passed the control to the finally block. The os variable is not null so the os.close() method is invoked resulting in the IOCloseException. At that point the method execution ends throwing an instance of IOCloseException. A caller similar to the one below will print 'Failed to closed stream'.

try {
 testTraditionalTryCatchWithExceptionOnFinally();
} catch (IOWriteException e) {
 System.out.println("Failed to write to stream");
} catch (IOCloseException e) {
 System.out.println("Failed to close stream");
} catch (IOException e) {
 System.out.println("Error working with the stream");
}


Migrating to Try-catch-resource

The method above using a try with resource block would be written as

public void testJava7TryCatchWithExceptionOnFinally() throws IOException {
 try (OutputStream os = new SampleStream(true)) {
  os.write(0); // Throws IOWriteException
 }
}

Indeed more elegant and a very easy conversion of the code - however there is an important nuance from the caller point of view: the try-catch-resource version of the method throws IOWriteException rather than IOCloseException. Obviously the caller illustrated few paragraphs above will change its behavior when using the migrated version of the method. So what had seemed to be a naive internal method change becomes now a contract change.

What can be done: document and getSuppressed()

First thing is to be aware of that change - and to consider if this internal change in the method does worth it. If it is decided to do the change we should track down all usages of the modified method and to see if any changes need to be done. However in real life this is not always easy: in large projects or if the modified method is a part of a library provided to other projects (so there is no visibility  on who is using it) we should also include an explicit notification of that behavior change in the library documentation/release notes. Once the change is communicated with other developers they can use the getSuppressed() method to drill down into the different use cases. The try-catch-resource block does expose the suppressed exception using the new (since Java 1.7) getSuppressed() method. This method returns all of the suppressed exceptions by the try-catch-resource block (notice that it returns ALL of the suppressed exceptions if more than one occurred). A caller might use the following structure to reconcile with existing behavior

try {   
  testJava7TryCatchWithExceptionOnFinally(); // This is the method illustrated above
} catch (IOException e) {       
    Throwable[] suppressed = e.getSuppressed();
    for (Throwable t : suppressed) {
        // Check T's type and decide on action to be taken
    }
}

Not the most elegant solution I could think about - but still one of those things Java developers must be aware of.

And one last thing

Just another example on the importance of good unit test coverage which can promptly identify that kind of  'hidden changes'.

[some sample code for try-with-resource is at https://github.com/eyal-lupu/eyallupu-blog/tree/master/Java-SE/src/test/java/com/eyalllupu/blog/java7/trywithresource]

Comments

Unknown said…
an excellent article I actually got to your page googling around with a Java NIO question.

Popular posts from this blog

New in Spring MVC 3.1: CSRF Protection using RequestDataValueProcessor

Hibernate Exception - Simultaneously Fetch Multiple Bags

Hibernate Derived Properties - Performance and Portability