Java Exception and Lambda Part 2 : Give power back to the compiler

Java Exception and Lambda Part 2 : Give power back to the compiler

In Part 1 of this series we talked about how lamba expressions introduced since Java 8 are mapping to functional interfaces and how the absence of any throws clause in the standard functional interfaces provided in the JDK somewhat opened the door to the idea of exploiting the sneaky throw technique. We also found how dangerous that compiler “feature” can be when misused and how easy it is to misuse it. We left with some hope that there might be a way to refactor our code in a way that would allow the java compiler to still be able to perform checked exception validation while still being able to leverage the sneaky throw technique for lamba expressions, so let’s think about this a little more…

First we will adapt a bit our legacy code example from Part 1:

So assuming our unchecked() method is now implemented to use the sneaky throw technique, from the above we can see that the obvious way to guarantee type safety and a consistent behavior for exception handling is to add throws SQLException to the method signature of handleAllOrdersOf() i.e. make sure our outer public method (our api) declares the same checked exception as the queryDatabaseForAllOrders() method used in our implementation as a method reference passed to our unchecked(). So we sneaky throw an SQLException from our implementation of queryDatabaseForAllOrders() but we also let the compiler know that handleAllOrdersOf() can throw an SQLException, so now we have type safety back… or do we?

It’s working for now but…

What if a few months later in another refactoring effort, someone change the method signature of queryDatabaseForCustomers() to now throw e.g. an IOException or another custom checked exception type? This is something we all did, change a method signature and let the compiler spit out compilation errors as a way to find all places where the method is used, then just replace the catch block to handle the new exception type. It is going to work… everywhere except for our code, the compiler will still not catch the exception type mismatch between our method implementation that sneaky throw SQLException (as a RuntimeException) and our method declaration that now throws IOException. So how can we statically link the relation between the checked exception type thrown by our outer method and the lambda expression we wrap in unchecked()?

First we need our functional interface CheckedFunction to declare a specific exception type instead of a generic Exception:

So before this change our method reference was mapped as:

i.e. the apply() method was resolving to:

So we were losing the exception type information for SQLException, but by changing the definition of our CheckedFunction as we did above, our method reference now maps to:

and the apply() now resolves to:

Consequently the definition of our unchecked() method now becomes:

The next step involves refactoring our logic for handleAllOrdersOf() by creating a more generic method that will take as a parameter our new CheckedFunction interface and will declare a throws clause of the same exception type as the CheckedFunction we are passing:

Notice the type E at line 2? The exception type of the lamba expression passed as a higher order function parameter is now statically linked to the throws clause of our public method declaration. We can now invoke our new method this way:

We killed two birds with one stone here, we explored a pattern to create reusable functions (e.g. processSubQuery) that can be used in other contexts (our specific example here is not much useful outside the context of this blog post, but the focus should be about the general idea of passing higher order function as parameter to avoid hardcoding logic in our method implementation), but more importantly we regain the type safety and exception checking at compile time that we lost, if we change the definition of the method queryDatabaseForAllOrders to throw e.g. an IOException, our code above will fail to compile with a “Unhandled exception: java.io.IOExeception” error.

Some caveats

One “limitation” of this design is that queryDatabaseForAllOrders() cannot throw more than one checked exception (SQLException in this example), the word “limitation” being put in quotes here because although this is still an ongoing debate in the java community regarding if checked exceptions are a mistake (vs. only using runtime exceptions, strategy taken by other languages e.g. Kotlin, Scala or C#), the community seems to lean a bit more toward the fact that throwing more than one checked exception from a public method can be seen as an anti-pattern and a code smell, although this is also not a unanimous consensus. So this could be another subject for a subsequent blog post, but let’s assume for now that we stick with the at most one checked exception per public method rule.

Another very important point to consider is, assuming you are in the pro checked exceptions camp and want the java compiler to perform exception type checks instead of going all in with runtime exceptions (as we would do in other languages like Kotlin or Scala, or as with some java libraries like Spring Data), when designing a solution like the above, make sure that the lamba expression wrapped with a function that hides the checked exception and use the sneaky throw technique (e.g. unchecked() in the example above) is actually invoked within the scope of our method using it (e.g. processSubQuery()). In our case this assumption is valid, but beware of lazy evaluation code, for example what if our lambda expression is used as part of e.g. constructing a Stream that is returned by our api and then consumed later outside the scope of our method that wrapped our lamba expression with unchecked()?

Here the execution of our convert() function escaped the scope of createStream() and will be triggered outside the try/catch block above, so an IOException will be sneaky thrown as a runtime exception i.e. we will still have the problem we tried to resolved with the blog post you are currently reading ;-).

Takeway

With a bit of refactoring we were able to give back some power to the compiler for exception checking even when using the sneaky throw technique to overcome the lack of support for checked exceptions with Java 8 lamba expressions, assuming the execution of the lamba expression doesn’t escape the scope of the code referencing it, which should be the case most of the time. But as you can see this is still not 100% bulletproof i.e. as a developer we still need to think about the workflow of our program to avoid the edge case described above.

Stay tuned for part 3 of this series where we look into how functional programming principles would help make the whole exception handling with lamba expressions much more fluent without breaking the flow of our program, and then try to answer the question as to if the sneaky throw technique would still be relevant.

Leave a Reply

Your email address will not be published.

%d bloggers like this: