Java 7 includes a couple new features that change exception handling. Specifically, these features are try-with-resources and multi-catch with more precise rethrow. These are the first changes to the exception facility since chained exceptions were added in 1.4. Exception chaining was an important improvement, but it didn’t really change anything fundamental about the way you had to write exception handling code. Indeed, exception handling code is pretty much unchanged since the beginning of Java.
Java 7 changes that.
Consider this common exception handing task. In a two-step operation, if an exception is thrown in the second step, you need to clean up anything left from the first step.
For example, suppose you want to create and return a Foo, which in turns needs a Bar to have been created first. If creating the Foo fails for some reason, you want to make sure that the Bar is cleaned up. But if creating the Foo succeeds, you don’t clean up the Bar. You could start off with some code that looks like this:
Foo createFoo() throws FooException { Bar bar = new Bar(); try { return new Foo(bar); } catch (FooException fe) { bar.cleanup(); throw fe; } }
(This assumes that the Foo constructor has been declared with throws FooException, a checked exception.)
The problem here is that if creation of the Foo (or something else within the try block) throws something other than FooException. The bar.cleanup() code gets skipped, and you’re screwed. In some sense what you want to do is to catch and rethrow a Throwable, but you can’t do this because you’d then be forced to change the declaration of createFoo to have throws Throwable which you really don’t want to do either.
A more effective approach is to use a finally clause for this, since it executes your code and then lets the exception propagate unchanged. Unfortunately, the finally clause also executes even if no exception is thrown, so you have to add some conditionals to make sure cleanup doesn’t occur in the successful case. This usually involves creating a local, initializing it to null, and then checking for null in the finally clause. Your code ends up looking something like this:
Foo createFoo() throws FooException { Foo foo = null; Bar bar = new Bar(); try { foo = new Foo(bar); } finally { if (foo == null) bar.cleanup(); } return foo; }
The reasoning is as follows. The foo local variable is initialized to null and is assigned at the very end of the try block. If it’s null at the time the finally block executes, an exception must have been thrown from somewhere in the try block, and so the cleanup code needs to get run. This is a bit ugly, but it works, and I see it often enough to recognize it as an idiom.
Turns out that in Java 7 we don’t need to write this anymore. Instead, we can do this:
Foo createFoo() throws FooException { Bar bar = new Bar(); try { return new Foo(bar); } catch (Throwable t) { try { bar.cleanup(); } catch (Throwable t2) { t.addSuppressed(t2); } throw t; } }
(Update 2017-11-22: added suppressed exception handling in outer catch-block. If bar.cleanup() throws an exception, especially a checked exception, this is caught and added to the original Throwable’s suppressed exception list.)
That is, we can catch and rethrow Throwable, just like we wanted to originally, but we don’t have to change the method declaration to throws Throwable. How can this work?
This is the “more precise rethrow” portion of the multi-catch with more precise rethrow feature, part of Java 7’s Project Coin. This feature is often overlooked, since it’s combined with multi-catch. Most treatments of this feature focus on multi-catch and barely talk about “more precise rethrow.” But as we can see from the code above, this feature can be used independently of multi-catch, and quite effectively as well.
To understand what’s going on here, we need to revisit some fundamentals of checked exceptions.
All Java code must conform to the “catch or specify” requirement. Any checked exceptions that can be thrown by a piece of code must either be caught by a try-catch statement that contains the code, or the checked exceptions must be specified in the throws clause of the method that contains the code. In Java 7, fundamentally this is still true, but the change is that the compiler now does additional analysis of what exceptions can be thrown by a throw statement inside a catch clause.
Prior to Java 7, the analysis was as follows. What exceptions can the “throw t” statement throw? The t variable is declared as type Throwable. Therefore, “throw t” can throw Throwable. This must be caught or specified, which in turn requires the method declaration to be changed to throws Throwable.
In Java 7, the analysis has been made deeper. Instead of just looking at the declared type of t, the compiler looks at where t came from. First, it makes sure that t points to the same object as it did at the beginning of the catch clause. (That is, t must be “effectively final”.) If so, the compiler then looks at the checked exceptions that can be thrown from the try block. In this case, we’ve called the Foo constructor, which can throw FooException. That’s the only checked exception that can arise from the try block, therefore the only checked exception type that t could have in the catch block is FooException. (It could potentially be an unchecked exception, but we don’t care about that, since unchecked exception types don’t participate in the catch-or-specify rule.) Now we get to the “throw t” statement; since we know t is either unchecked or is a FooException, we need to make sure that the enclosing method declares “throws FooException.” Indeed it does, and we’re done.
Now, if this chain of reasoning doesn’t hold up — for example, t is assigned to something else in the catch block — we revert to the old analysis, and t is treated as its declared type, just as before. Fortunately, most exception catching code doesn’t assign to the exception variable, so the new analysis will usually apply. Note that the exception itself can be modified, for example with the addSuppressed or initCause methods, and the new analysis will still apply. You just can’t assign to t and still expect this to work.
Anyway, where are we after all that?
It used to be that “catch Throwable” was a no-no. It usually meant you were going to so something stupid like swallow the exception. You couldn’t rethrow the throwable, since you’d be forced to redeclare your method with “throws Throwable” and nobody wants to do that. Now, in Java 7, it makes perfect sense to catch Throwable, do some cleanup, and then rethrow, without garbaging up the throws clause of your method declaration.
Will this make your exception handling code better? Is this a new Java exception handling idiom?
* * *
I’ll be talking about this more at my OSCON/Java talk next week in Portland.
Before Java 7 you could also catch throwable and then throw a new FooException with the original exception as its cause. Granted, that’s no ideal solution either, since you’re signalling upstream that FooException is what actually happened, whilst it is not (you need to look at the cause to find that out). So, I’d say this is a very nice improvement, typical of the things you’d expect from Project Coin.
[…] to the exception facility since chained exceptions were added in 1.4. Exception chaining was an… Read more… Categories: JDK Share | Related […]
I love this new possibility for exception handling but I think you should give the fully robust version with a nested try-catch-addSuppressedException in the finally block before the over-simplified version spreads too far – even if it is much better than we can easily do currently.
Hm, yes, good point… I’ve presented what you call the “fully robust version” at several conferences but I haven’t updated this blog post with it. I’ll consider doing so. Thanks for pointing this out.
Thanks for the overview – very nicely done.
There is a couple gotchas with that probably should be mentioned.
1. For some bad errors (OutOfMemoryError, VirtualMachineError etc) it is quite possible for bar.cleanup() to also end up failing catastrophically. That said, practically this probably doesn’t matter all that much -> hosed system won’t behave nicely.
2. If bar.cleanup() throws an Runtime or (declared) checked exception, then the upstack caught exception will no longer have the root cause.
Thanks. Good points too. In my presentations at a couple conferences this year I showed a variation where the equivalent of bar.cleanup() is wrapped in another catch of Throwable that adds any caught exceptions to the original exception’s suppressed exception list. This prevents information from being lost. I’ll update this article to mention this point.
Hey, a nice post but here are some of mine post on the same . The readers of might be interested in Exceptions in Java and Declare own exceptions