bhankas

Unborking Checked Exceptions in Java Lambda expressions

I'll leave describing lambdas and checked exceptions to better people.

For years, it has irked me the way lambdas (henceforth called closures) handle, or not really handle checked exceptions in Java.

In short, closures cannot throw checked exceptions, they must be converted to unchecked ones. For example,

1public class Foo {
2    public static String toString(Foo fooObj) {
3        return "foo!";
4    }
5
6    public static Foo parseFoo(String str) throws ParseException{
7        return fooMapper.parse(foo);
8    }
9}

If we call both these methods within closures, say those passed to Optional, it will look like this:

 1public static Foo incepFoon(Foo fooObj) {
 2    return Optional.ofNullable(fooObj)
 3        .map(Foo::toString)
 4        .map(str -> {
 5                try {
 6                    return Foo.parseFoo(str);
 7                } catch (ParseException e) {
 8                    throw new RuntimeException(e);
 9                }
10            })
11        .orElse(null);
12}

Notice how calling Foo::parse is nice and simple, but calling Foo::parseFoo involves making a temporary variable, wrapping with try-catch block and for coup de grâce, converting the actual exception to RuntimeException.

The last part, in particular is cause of my scorn. Oracle really screwed up here while designing lambdas. Checked exceptions are alternate API paths, and should be gracefully handled. When a checked exception is converted to a runtime exception, we lose out on important information. We are not able to gracefully bubble the exception up the stack, nor can we understand what exactly went wrong because not all information is preserved during casting. Even worse, we cannot rely on method signature alone to tell us what exceptions should be expected by the caller.

For many months, every time I had to write try-catch within closures, I hated looking at the code afterwards.

Last night, after nearly 3 hours of googling and struggling with Java's type system, I finally found this gem (thanks, stackoverflow!):

 1public final class LambdaExceptionUtil {
 2    @FunctionalInterface
 3    public interface Function_WithExceptions<T, R, E extends Exception> {
 4        R apply(T t) throws E;
 5    }
 6
 7    public static <T, R, E extends Exception> Function<T, R> rethrowFunction(Function_WithExceptions<T, R, E> function) throws E  {
 8        return t -> {
 9            try {
10                return function.apply(t);
11            } catch (Exception exception) {
12                throwActualException(exception);
13                return null;
14            }
15        };
16    }
17
18    @SuppressWarnings("unchecked")
19    private static <E extends Exception> void throwActualException(Exception exception) throws E {
20        throw (E) exception;
21    }
22
23}

This weird looking code is simple typechecking with generics. With this, above example becomes:

1public static Foo incepFoon(Foo fooObj) throws ParseException {
2    return Optional.ofNullable(fooObj)
3        .map(Foo::toString)
4        .map(rethrowFunction(Foo::parseFoo))
5        .orElse(null);
6}

Simple, elegant, useful! Notice how both method references look nearly identical, there's no unnecessary try-catch clutch, and finally, see that method signature now includes throws ParseException! If it is not present, the compiler complains1 until that particular type of exception is added to the signature!


1

Java's generics are pretty much entirely compile-time, and incredibly useful for (nearly) exhaustive static type-checking.