Java Exception and Lambda Part 1 : To go sneaky or not?

Java Exception and Lambda Part 1 : To go sneaky or not?

Saying that there is a raging debate over the Internet among java developers about how to handle exceptions with java 8 lamba expressions might be a bit strong, but there is definitely an ongoing discussion and various points of view on the subject, so in the spirit of adding a little fuel to the fire, here is my take on it:

First some background, lamba expression introduced since Java 8 are mapped to functional interfaces i.e. single method java interfaces, the JDK provides a set of default functional interfaces to define functions with different number of arguments (declared for the most part under the java.util.function package), for example :

The map method from the Stream interface and the Function interface being defined as:

As we can see here, the Function interface (and all other interfaces defined in java.util.function) doesn’t declare any throws clause in the signature of its single abstract method, which means that the following lambda expression (which uses a method that declares a checked exception) would not compile :

So one way to fix this is to catch and handle the SQLException within the lamba expression so that it doesn’t throw any exception and can be mapped to the Function interface definition:

But there is a lot of boilerplate code involved here and it kind of defeats the purpose of using lambda expressions to make the code more concise. There are some techniques available to alleviate this (not so) small irritant, including mapping a lambda expression to a functional interface that declares an exception and using a utility function that encapsulate the above logic and return a Function instance:

So our code above now becomes:

The lambda expression (method reference actually here) passed to the unchecked() utility method now maps to CheckedFunction.apply() which declares an exception, so the compiler is happy now. But what if we could actually throw the SQLException itself instead of having to wrap it around a RuntimeException before throwing it from our unchecked() method? This is where a technique that goes by the unofficial name of “sneaky throw” comes in. I invite you to read this excellent article by Grzegorz Piwowarek for a great explanation on how we can “trick” the java compiler to convert a checked exception into a unchecked exception, but basically we can take advantage of the fact that a generic type that extends Exception or Throwable is inferred as a RuntimeException, thus bypassing the compiler check, so we can write the following:

In our example above, an actual SQLException will now be directly thrown from the this::queryDatabaseForCustomer method reference instead of a runtime exception wrapper around the root SQLException exception.

And here is where the debate starts…

Some people would find this “feature” very convenient, others would argue that this is a very dangerous hack, (as for everything in life) a reasonable resolution of this dilemma probably lies between the 2 polarized positions and is assorted of a big “it depends” label. We need to be extremely careful when using the sneaky throw technique as very subtle bugs can be introduced, those hard to find issues during development and testing, but easy to find in production as the Murphy’s law dictate. Let’s take an hypothetical scenario of a legacy piece of code processing a list of all Orders from a list of Customer. A very simple and and naive implementation (just for the purpose of this blog post) could look like this:

Here we don’t want to execute handleAllOrdersOf() if an exception is thrown from queryDatabaseForCustomers(), so handleAllOrdersOf() is declared inside the try/catch block, but it doesn’t declare any checked SQLException so it is clearly not the intention of the developer to expect a SQLException being thrown from handleAllOrdersOf().

A few months later, we realize we have a N + 1 query problem, the list of Orders needs to be decoupled from the Customer entity to reduce the number of queries, so the new handleAllOrdersOf() now looks like:

The unchecked() utility method wraps our queryDatabaseForAllOrders function so Optional.map() can compile, now the project builds again without any compilation error, the code changes were all contained inside one method, the signature of the method wasn’t modified, we are good… or are we? What about our original code calling handleAllOrdersOf()?

Our error handler code was originally written to ONLY handle an SQLException thrown from queryDatabaseForCustomers(), but now our catch block will unexpectedly catch any SQLException thrown from handleAllOrdersOf() even if it doesn’t declare any throws clause in its method signature. Maybe we are lucky and the same error handler code is valid for the 2 methods invoked in our try block, but if the error handler code was really specific only to queryDatabaseForCustomers(), we just changed the original flow of the program, and depending on the granularity of the unit tests and/or integration tests, this one could easily pass through all our safeguards currently in place and bite us in production.

On the flip side, if handleAllOrdersOf() above is now defined as:

It is expected that an SQLException can be thrown by this method, but we will never actually throw it if unchecked() is not implemented to use the sneaky throw technique, which means that any catch (SQLException e) block declared by code calling this method will never be triggered and the runtime exception will bubble up higher in the call stack.

Takeaway

So as you can see, “it depends”! A very careful analysis of the code and the workflow of the program is necessary to determine if it semantically make sense to go the sneaky throw route or not.

Stay tuned for Part 2 of this series as we will try to see if there are ways to design our code in a way that gives a little more power back to the java compiler when it comes to checking exceptions with lamba expressions.

Leave a Reply

Your email address will not be published. Required fields are marked *

%d bloggers like this: