Java 7’s Support for Suppressed Exceptions
Suppressed exceptions play a significant role in the execution of the new-to-Java 7 try-with-resources statement (also known as Automatic Resource Management [ARM]). Providing API support for this new resource management capability appears to have been the primary driver for the new constructor and methods on the Throwable class that provide access to suppressed exceptions, but the APIs support suppressed exceptions being used outside of the try-with-resources statement.
Perhaps the most common use case for encountering suppressed exceptions is when a try-with-resources statement (which is the one type of try that does not require a catch or finally clause according to Java Language Specification Java SE 7 Edition Section 14.20) encounters an exception within the try block and then encounters another exception in implicitly trying to close the related resource.
To illustrate this case, I make up my own “resource” that can be used in a try-with-resource statement because it implements the java.lang.AutoCloseable interface. This “naughty resource” intentionally throws an exception when the code using it attempts to use it and then continues its bad form by throwing another exception when the overridden close method (the only one prescribed by the AutoCloseable interface) is called. The code listing for NaughtyResource is shown next.
NaughtyResource.java
package dustin.examples; /** * Resource that throws exceptions both in its use and its closure and is only * intended for use in demonstrating Java 7's suppressed exceptions APIs. This * is not a well-behaved class. * * @author Dustin */ public class NaughtyResource implements AutoCloseable { /** * Method that intentionally throws an exception. * * @throws RuntimeException Thrown no matter how you call me. */ public void doNothingGood() { throw new RuntimeException("Nothing good can come of this."); } /** * The overridden closure method from AutoCloseable interface. * * @throws Exception Exception that might be thrown during closure of this * resource. */ @Override public void close() throws Exception { throw new UnsupportedOperationException("Not supported yet."); } }
With a naughty resource now available, it’s time to use the naughty resource and demonstrate the suppressed exceptions API. The next image depicts what happens if one tries to use this resource without catching the Exception thrown implicitly by the close method and without declaring the method as throwing it.
This is an error message provided by javac as shown in the next screen snapshot.
I have shown the two previous screen snapshots to emphasize the implicit call to close that is performed on the resource. The error message shown in the NetBeans editor and in the console clearly states this is the case.
The next code listing contains the first version of the SuppressedExceptions class that compiles.
SuppressedExceptions.java (Version 1)
package dustin.examples; /** * Demonstrate JDK 7's Suppressed Exceptions API support. * * @author Dustin */ public class SuppressedExceptions { /** * Executable function demonstrating suppressed exceptions. * * @param arguments The command line arguments; none expected. */ public static void main(String[] arguments) throws Exception { try (NaughtyResource naughty = new NaughtyResource()) { naughty.doNothingGood(); } } }
Although two exceptions are really encountered when the above code is executed (one from within the try block on the call to NaughtyResource.doNothingGood() and one on the implicitly called close method), only one percolates to the top and is shown when the application is run. This is demonstrated in the next screen snapshot.
As the last image proves, only the exception encountered in try try block of the try-with-resources statement is shown. This is where the ability to ask a Throwable (in this case an Exception) about its suppressed exceptions comes in handy. To demonstrate this, version 2 of the SuppressedExceptions class (which uses Throwable.getSuppressed()) is shown next.
SuppressedExceptions.java (Version 2)
package dustin.examples; import static java.lang.System.err; /** * Demonstrate JDK 7's Suppressed Exceptions API support. * * @author Dustin */ public class SuppressedExceptions { /** * Method that uses NaughtyResource with try-with-resource statement. * * @throws Exception Expected exception for try-with-resource used on the * NaughtyResource. */ public static void performTryWithResource() throws Exception { try (NaughtyResource naughty = new NaughtyResource()) { naughty.doNothingGood(); } } /** * Executable function demonstrating suppressed exceptions. * * @param arguments The command line arguments; none expected. */ public static void main(String[] arguments) { try { performTryWithResource(); } catch (Exception ex) { err.println("Exception encountered: " + ex.toString()); final Throwable[] suppressedExceptions = ex.getSuppressed(); final int numSuppressed = suppressedExceptions.length; if (numSuppressed > 0) { err.println("\tThere are " + numSuppressed + " suppressed exceptions:"); for (final Throwable exception : suppressedExceptions) { err.println("\t\t" + exception.toString()); } } } } }
The above application will print out any suppressed exceptions. In this case, the exception encountered upon attempted closure of NaughtyResource is now shown as one of the suppressed exceptions. This is demonstrated in the next screen snapshot.
As documented in the Java Tutorial, we see that the only thrown exception is the exception encountered within the try block of the try-with-resources statement and the second exception encountered during resource closure is “suppressed” (but still associated and available from the actually thrown exception).
One doesn’t need to use try-with-resources to work with suppressed exceptions. The next code listing adapts SuppressedExceptions to use a more traditional try-finally.
SuppressedExceptions.java (Version 3)
package dustin.examples; import static java.lang.System.err; /** * Demonstrate JDK 7's Suppressed Exceptions API support. * * @author Dustin */ public class SuppressedExceptions { /** * Method that uses NaughtyResource with JDK 7 try-with-resource statement. * * @throws Exception Expected exception for try-with-resource used on the * NaughtyResource. */ public static void performTryWithResource() throws Exception { try (NaughtyResource naughty = new NaughtyResource()) { naughty.doNothingGood(); } } /** * Method that uses NaughtyResource with traditional try-finally statement. * * @throws Exception Exception thrown during use of NaughtyResource. */ public static void performTryFinally() throws Exception { final NaughtyResource naughty = new NaughtyResource(); try { naughty.doNothingGood(); } finally { naughty.close(); } } /** * Executable function demonstrating suppressed exceptions. * * @param arguments The command line arguments; none expected. */ public static void main(String[] arguments) { try { // performTryWithResource(); performTryFinally(); } catch (Exception ex) { err.println("Exception encountered: " + ex.toString()); final Throwable[] suppressedExceptions = ex.getSuppressed(); final int numSuppressed = suppressedExceptions.length; if (numSuppressed > 0) { err.println("\tThere are " + numSuppressed + " suppressed exceptions:"); for (final Throwable exception : suppressedExceptions) { err.println("\t\t" + exception.toString()); } } else { err.println("\tNo Suppressed Exceptions."); } } } }
The above code calls a method that uses try-finally and the behavior is different than that for the try-with-resources example as shown in the next screen snapshot.
The try-finally shows the exception encountered on attempted resource closure rather than the exception encountered in use of the resource (opposite of try-with-resources shown above). An additional difference is that the other exception is not available even as a suppressed exception in the try-finally case. This is an example of the well-advertised “lost exception” issue of a potentially “trivial” exception (closing the resource) hiding a potentially more significant exception (actually using the resource).
If I want to see both exceptions thrown during use and closure of NaughtyResource in conjunction with try-finally, I can make use of Throwable.addSuppressed(Throwable) as shown in the next code listing. In that listing, the try and finally clauses are enhanced to capture the exception thrown during use of the NaughtyResource and to add that caught exception to the actually-thrown exception as a suppressed exception.
SuppressedExceptions.java (Version 4)
package dustin.examples; import static java.lang.System.err; /** * Demonstrate JDK 7's Suppressed Exceptions API support. * * @author Dustin */ public class SuppressedExceptions { /** * Method that uses NaughtyResource with JDK 7 try-with-resource statement. * * @throws Exception Expected exception for try-with-resource used on the * NaughtyResource. */ public static void performTryWithResource() throws Exception { try (NaughtyResource naughty = new NaughtyResource()) { naughty.doNothingGood(); } } /** * Method that uses NaughtyResource with traditional try-finally statement. * * @throws Exception Exception thrown during use of NaughtyResource. */ public static void performTryFinally() throws Exception { final NaughtyResource naughty = new NaughtyResource(); Throwable throwable = null; try { naughty.doNothingGood(); } catch (Exception usingEx) { throwable = usingEx; } finally { try { naughty.close(); } catch (Exception closingEx) { if (throwable != null) { closingEx.addSuppressed(throwable); throw closingEx; } } } } /** * Executable function demonstrating suppressed exceptions. * * @param arguments The command line arguments; none expected. */ public static void main(String[] arguments) { try { // performTryWithResource(); performTryFinally(); } catch (Exception ex) { err.println("Exception encountered: " + ex.toString()); final Throwable[] suppressedExceptions = ex.getSuppressed(); final int numSuppressed = suppressedExceptions.length; if (numSuppressed > 0) { err.println("\tThere are " + numSuppressed + " suppressed exceptions:"); for (final Throwable exception : suppressedExceptions) { err.println("\t\t" + exception.toString()); } } else { err.println("\tNo Suppressed Exceptions."); } } } }
The output of the modified class is shown next. Note that the exception resulting from using the resource itself is now associated with the thrown exception as a suppressed exception. Although not shown here, Throwable also now provides a constructor that allows specification via boolean argument of whether suppressed exceptions are allowed (enabled) or not allowed (disallowed) for the newly instantiated Throwable.
Another perspective from which to look at suppressed exceptions is that of full stack traces. The following three images are screen snapshots of full stack traces resulting from use of try-with-resources (explicitly shows suppresed exception), use of traditional try-finally statement without explicitly adding suppressed exceptions (and so no suppressed exceptions in full stack trace), and use of traditional try-finally with suppressed exceptions explicitly added (and thus do appear in full stack trace). I’ve added a red line to each image to separate show where the full stack trace ends and have circled the explicit reference to the suppressed exception where applicable.
Suppressed Exceptions Versus Chained Exceptions
Suppressed exceptions are not the same as chained exceptions. Chained exceptions were introduced with JDK 1.4 and were intended to make it possible to easily track causal relationships between exceptions. Typically, chained exceptions resulted from associating a newly thrown exception with the exception that was caught and caused the throwing of a new exception. For example, an unchecked exception might be thrown that “wraps” a checked exception that was caught and they could be chained together.
Suppressed exceptions were introduced with JDK 7 and are less about causal relationships and more about representing possibly related but not necessarily causal multiple exceptional conditions in a single thrown exception. In my naughty resource example above, the naughty resource’s exception upon its sole method being called was NOT the cause of it thrown an exception upon invocation of its close method. Because of this, it makes more sense for the two exceptions to be associated (via the suppressed exception mechanism) than to force one to appear to be the cause of the other in a chained relationship.
It may be easiest to quickly understand the difference between chained exceptions and suppressed exceptions by comparing Throwable’s methods for accessing each type. For chained exceptions, one invokes a particular Exception’s (Throwable’s) method. This method returns a single instance of the causing Throwable. That returned Throwable can be asked for its cause and the process is repeated up the chain of causing Throwables. The important observation is that each given Throwable has a single causing Throwable. This can be contrasted with the ability of a Throwable to provide multiple suppressed Throwables (in an array) when its getSuppressed() method is called.
NetBeans Recommends try-with-resource
It’s worth noting here that NetBeans warns about the use of try-finally and suggests replacing it with try-with-resources as discussed in my post Seven NetBeans Hints for Modernizing Java Code and in the screen snapshot shown next.
Conclusion
I believe it’s obvious why NetBeans recommends that the developer change a try-finally for resource handling to a try-with-resources statement. It is often preferable to know first what operation on the resource caused an exception, but it is nice to be able to access the exception on closure as well if desired. If I had to choose, I’d typically be more interested in the resource’s problem during execution because the closure problem might be a derivative of that. However, having both is even better. The traditional try-finally only lists the exception upon closure without additional effort to relay both. The try-with-resource statement is not only more concise; it’s also more useful thanks to its built-in support for inclusion of suppressed exceptions.
Reference: Java 7’s Support for Suppressed Exceptions from our JCG partner Dustin Marx at the Inspired by Actual Events blog.