Abrupt completion and exceptions in Java

2019-05-07

Here are some alternatives titles I could’ve used:

Today I’m going to briefly discuss something that tripped me up in my day job the other day. This is perhaps something that every Java developer is taught during the first day on the job or in class but which no one ever remembers until the day it bites them in the behind. Anyway, it’s something I didn’t know and is really scary. I point you first at the Java Language Specification (JLS), specifically the section 14.20.2. Execution of try-finally and try-catch-finally. I will caution that everything in this section of the document seems completely obvious at first glance. However, I would like to bring your attention to two specific sentences:

If the finally block completes abruptly for reason S, then the try statement completes abruptly for reason S (and reason R is discarded).

and

If the finally block completes abruptly for reason S, then the try statement completes abruptly for reason S (and the throw of value V is discarded and forgotten).

Note that these two sentences are very similar and have similar implications. In the version of the JLS I’m looking at, the second sentence is specifically rendered in bold. I would argue that the first sentence should be similarly rendered in bold for the same reason: these are both counterintuitive behaviours that can have real-world consequences.

Consider the following pseudo-Java:

This would seem to be a reasonable way to achieve the following:

However, the Java specification tells us that if the try block throws an exception that is assignment-compatible with Exception (in the declaration of the catch block), the catch block will run followed by the finally block and, most importantly for our purposes here, the throw at the end of the catch block will be discarded and replaced by the return true at the end of the finally block.

Let that sink in for a minute. Go back and re-read that. This may not ring alarm bells in your head immediately, but it freaked me out. You may encounter code like this yourself: code that assumes that the exception will be rethrown. This is what a percursory glance at the code would suggest. The return true is an example of what I’m going to call “action at a distance” and means that, in order to fully understand any given instance of the try-catch-finally construct in Java, you have to consider how the finally block interacts with both your try and catch blocks. In summary, neither try nor catch blocks are fully self-describing.

The key to interpreting the JLS description of finally blocks is to understand what it means for a statement to complete abruptly. This is described in detail in the section 14.1. Normal and Abrupt Completion of Statements in the JLS. This section enumerates all of the associated reasons for an abrupt completion in Java, three of which are relevant to our example:

  • A return with no value
  • A return with a given value
  • A throw with a given value, including exceptions thrown by the Java Virtual Machine

Thus, in terms of the terminology used in the JLS:

The overall result, therefore, is that the whole try statement completes abruptly with reason S, i.e. return true, with reason R, i.e. throw e, being ignored.

The “obvious” behaviour (i.e. the exception is caught and rethrown and the clean-up code in finally block is run) can be achieved by rewriting this code as follows:

Critically, the finally block in this example does not complete abruptly and, consequently, the try block completes with reason R (i.e. throw e) and the overall statement throws the exception.

This following program systematically demonstrates these behaviours:

This program will generate the following output:

So, beware.

Reference

Tags

Java
Exceptions

All content © 2019 Richard Cook. All rights reserved.