A small tweak to checked exceptions

Almost everyone hates Java’s checked exceptions. Even those that still use them will admit that they lead to a lot of boilerplate. Everyone has code along the following lines:


void example() throws MyServiceException {

    try {

        return doInput();
    
    } catch (IOException e) {

        throw new MyServiceException(e);

    }

}

When some method you call throws a checked exception (like IOException), and you cannot deal with it locally, you are forced to either add it to your own declared exceptions (leaking implementation details) or else rethrow it as a different exception type – leading to boilerplate code as above.

Most people do this a few times and then throw their hands up in despair and start using unchecked exceptions instead. But there are some nice aspects to checked exceptions. It is nice when I upgrade a 3rd-party library and get a compile error telling me that it has a new error case to handle, rather than waiting for that to blow up at runtime. (Error handling is for life, not just for Christmas).

My suggested improvement for checked exceptions would therefore be to cut down on the boilerplate a little by adopting the following simple rule:

  • If a checked exception is not handled in the body of a method, but there exists an exception type in the throws clause that has a one-argument constructor compatible with that exception type then wrap the exception in that constructor and re-throw it.

For example, imagine our MyServiceException from earlier has a constructor for IOException or a super-type:


MyServiceException(IOException e) { ... }

Then we could remove the try-catch block from our example method and let the compiler insert equivalent code for us:


void example() throws MyServiceException {
    return doInput();

}

I’m not sure whether the wrapped exception should appear (in a stack trace) to be thrown from the line at which it originated (the doInput() line) or perhaps from the line with the throws declaration, but that is a detail.

This reduces the boilerplate significantly in the common case where you just want to propagate the exception and let more general error handling deal with it at a higher level. But by making your service layer exception constructors take specific cause exception types (e.g., IOException vs Throwable) you can retain the nice property of getting a compile error when a dependency starts throwing a new exception.

Author: Neil Madden

Security Director at ForgeRock. Experienced software engineer with a PhD in computer science. Interested in application security, applied cryptography, logic programming and intelligent agents.