Overcoming Checked Exceptions in Java Lambdas

Post image

In Java 8, the long awaited Lambda came to live, making it easy(-er) to do FP in Java. One problem I came across is, that most Java code throws checked exceptions which leads to IMHO ugly try/catch blocks in lambdas:

Function<A, B> fun = (a: A) -> {
    try {
        // some function call that trows checked exception$
        return callFn(a);
    } catch (Exception e) {
        // return failure result
    }
};

The Good, the Bad and the Ugly

A really simple, but also not really nice option is to wrap thrown exceptions into an unchecked one:

@FunctionalInterface
public interface CheckedFunction<T, R> extends java.util.function.Function<T, R> {
    
    @Override
    default R apply(T t) {
        try{
            return applyChecked(t);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
    R applyChecked(T t) throws Exception;
}

Now we have an ugly Function<T, R> which still has a side effect, but a hidden one, because the RuntimeException will bubble without being checked. This works, but is really ugly!

Preventing Observable Side Effects

To prevent the observable side effects (and yes, every thrown exception is one), one could create a return type containing the functions result or an exception instance:

import java.util.function.Function;

@FunctionalInterface
public interface ResultFunction<T, R> extends Function<T, ResultFunction.Result<R>> {

    @Override
    default Result<R> apply(T t) {
        try{
            return Result.success(applyChecked(t));
        } catch (Exception e) {
            return Result.error(e);
        }
    }

    R applyChecked(T t) throws Exception;

    final class Result<T> {
        private final T result;
        private final Exception error;

        private Result(T result, Exception error) {
            this.result = result;
            this.error = error;
        }

        static <A> Result<A> success(A result) {
            return new Result<>(result, null);
        }

        static <A> Result<A> error(Exception error) {
            return new Result<>(null, error);
        }

        public boolean isSuccess() {
            return error == null;
        }

        public <A> Result<A> map(Function<T, A> f) {
            return isSuccess() ? Result.success(f.apply(result)) : Result.error(error);
        }

        public <A> A fold(Function<Exception ,A> errorFn, Function<T, A> successFn) {
            return isSuccess() ? successFn.apply(result) : errorFn.apply(error);
        }

        public T getOrElse(Function<Exception, T> f) {
            return isSuccess() ? result : f.apply(error);
        }
    }

}

The Result<T> class is a generic disjoint union (some would call it Either<A,B>) which can have a result or an error. The fold method is convenient to forther process the result, because it just takes two functions (one for the error-case and one to process the result) which must return the same type. The ResultFunction<T, R> can be used (and defined) like a normal Function<T, R> but will return a Result<T> instead of throwing exceptions.

This implementation can be tested using the following simple main-method:

public class Main {

    private static ResultFunction<String, Integer> strPlusOne = in -> Integer.valueOf(in) + 1;

    public static void main(String... args) {
        System.out.println(res("42")); // Success: 43
        System.out.println(res("foo")); // Error: input was not a number!

        System.out.println(strPlusOne.apply("bar").
            map(Object::toString).
            getOrElse(e -> "NaN"));
    }

    private static String res(String in) {
        return strPlusOne.apply(in).fold(
                e -> "Error: input was not a number!",
                r -> "Success: " + r
        );
    }
}

As you can see in the res helper method, fold can be used to return either a success or an error string depending on the Result state. Or, linke in the second example, map and getOrElse can be used to achieve the same. This entirely depends on how you are used to code…

Now, a lambda throwing exceptions (NumberFormatException in that case), can be used without any side effect. Of course similar implementations do also work for other functional interfaces like BiFUnction, Supplier, etc.

Edit: Use FunctionalJava if Possible

An even better approach to handle exceptions in lambdas is, if you’re able to, use the Try-function of the FunctionalJava library. It does pretty much the same, but is more sophisticated and you don’t have to write it on your own. Give it a try and use other functional sugar like partial functions, products, unions, immutable collections, and so on…

You May Also Like

Functional Java 1 - Options

Functional Java 1 - Options

This is the first post of my series about functional programming in Java. There’s a lot of functional stuff one can do. Everyone knows the Java 8 Lambda expression, but with a little library support, there is way more… In this series, I’ll coder som libraries which provide functional paradigms and constructs for Java: