
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 :
1 2 3 |
List<Integer> numbers = Stream.of("1", "2") .map(Integer::parseInt) .collect(toList()); |
The map
method from the Stream
interface and the Function
interface being defined as:
1 2 3 4 5 6 |
<R> Stream<R> map(Function<? super T, ? extends R> mapper); @FunctionalInterface public interface Function<T, R> { R apply(T t); } |
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 :
1 2 3 4 5 6 7 |
private Customer queryDatabaseForCustomer(Long id) throws SQLException { // issue a database call and return the corresponding Customer } List<Customer> entities = Stream.of(1L, 2L) .map(this::queryDatabaseForCustomer) // Doesn't compile .collect(toList()); |
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:
1 2 3 4 5 6 7 8 9 |
List<Customer> customers = Stream.of(1L, 2L) .map(id -> { try { return queryDatabaseForCustomer(id); } catch (SQLException e) { throw new RuntimeException(e); // or custom subclass of RuntimeException } }) .collect(toList()); |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
@FunctionalInterface public interface CheckedFunction<T, R> { R apply(T t) throws Exception; } public static <T, R> Function<T, R> unchecked(CheckedFunction<T, R> checkedFunction) { return t -> { try { return checkedFunction.apply(t); } catch (Exception e) { throw new RuntimeException(e); // or custom subclass of RuntimeException } }; } |
So our code above now becomes:
1 2 3 |
List<Customer> customer = Stream.of(1L, 2L) .map(unchecked(this::queryDatabaseForCustomer)) .collect(toList()); |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
@SuppressWarnings("unchecked") public static <T, E extends Exception> T sneakyThrow(Exception e) throws E { throw (E) e; } public static <T, R> Function<T, R> unchecked(CheckedFunction<T, R> checkedFunction) { return t -> { try { return checkedFunction.apply(t); } catch (Exception e) { return sneakyThrow(e); } }; } |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public interface Customer { ... List<Order> getOrders(); } try { List<Customer> customers = queryDatabaseForCustomers(); // Expected to throw SQLException List<Order> orders = handleAllOrdersOf(customers); // Not expected to throw SQLException // process orders } catch (SQLException e) { // Handle the exception from queryDatabaseForCustomers() } private List<Order> handleAllOrdersOf(List<Customer> customers) { // No throws clause here return Optional.ofNullable(customers) .orElseGet(Collections::emptyList).stream() .map(Customer::getOrders) // List of orders already populated .flatMap(Collection::stream) .collect(toList()); } |
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:
1 2 3 4 5 6 7 8 9 |
private List<Order> handleAllOrdersOf(List<Customer> customers) { return Optional.ofNullable(customers) .map(unchecked(this::queryDatabaseForAllOrders)) // Sneaky throw, SQLException .orElseGet(Collections::emptyList); } public List<Order> queryDatabaseForAllOrders(List<Customer> customers) throws SQLException { // Query database, retrieve all orders for all customers } |
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()
?
1 2 3 4 5 6 7 |
try { List<Customer> customers = queryDatabaseForCustomers(); // Expected to throw SQLException List<Order> orders= handleAllOrdersOf(customers); // Not expected to throw SQLException // process orders } catch (SQLException e) { // Handle the exception from queryDatabaseForCustomers() } |
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:
1 2 3 4 5 |
private List<Order> handleAllOrdersOf(List<Customer> customers) throws SQLException { return Optional.ofNullable(customers) .map(unchecked(this::queryDatabaseForAllOrders)) // No Sneaky throw, RuntimeException .orElseGet(Collections::emptyList); } |
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.