Friday, 25 February 2011

Do not return in a finally block: return will swallow your exceptions

This is a blog post that should not be. Unfortunately, this is a recurring mistake I'm seeing out there and hope this can help people prevent it.

Yesterday I've been busy troubleshooting a problem we were experiencing in a cluster of application servers. We narrowed down the problem to a module implementing the JAAS login protocol and, according to the failing module documentation, we should have received a LoginException generating from an UnsupportedOperationException launched down the call stack. Instead, the module was silently failing and returning a reference to a partially constructed JAAS Subject.

We checked out the module source code and it took us few minutes to detect the problem. The module was correctly implementing its documented protocol and was generating the UnsupportedOperationException as expected. The author, unfortunately, forgot one of the rules of the Java Language and was returning from a finally block, effectively discarding the exception that was launched just a couple of lines before.

That's amazing how many times I see people returning from a finally block without understanding what the code is intended to do.

Still not convinced?

Run this:

@Test
public void hello() {
    try {
        throw new UnsupportedOperationException();
    } finally {
        return;
    }
}

No exception is thrown. Surprised? You should not. There are plenty of variations of this theme out there. Nevertheless, if you're still surprised that the hello() method completes without throwing an UnsupportedOperationException, please read on.


try/catch/finally blocks

According to the Java Language Specification, this is a simplified vision of what happens during the execution of a finally block (you can read the normative documentation here):
  • If the finally block completes normally, then the try statement completes abruptly for reason R.
  • If the finally block complete abruptly for reason S, then the try statement completes abruptly for reason S.
As you may notice in the JLS, then, the "reason" a try statement completes with will always be affected by an abrupt completion of a finally block. Said in other words, the finally block will "decide" what happens later.

You probably though many times about what happens if an exception is thrown in a finally block, don't you? Perhaps you even tested that case: the exception that will be propagated up the stack will be the exception launched in the finally block. That's not surprising, after all, and that's why you should carefully check that the code in finally blocks don't fail without control.

So far, so good. But what happens, then, when a return statement is executed inside a finally block?


The return statement

What Java programmers often misunderstand is the very nature of a return statement. The Java Language Specification is clear about that:

The return statement always completes abruptly.

That's why exceptions launched in try or catch blocks (the "reason" of their abrupt completion) are simply ignored and not relaunched when a finally block completes abruptly with a return statement, being that the "resulting" reason of the abrupt completion of the try statement, as seen in the previous section.


Lesson learned

I personally don't see any good reason, unless you want any exception to be swallowed, to return in a finally block: it defies the very reason for try/catch blocks to exist.

Do think twice (or more...) before using this construct: chances are you're doing a favor to others and yourself if you avoid it.

No comments:

Post a Comment