Write Java Streams with Checked Exception Safe Lambdas

August 12, 2019

Introduction

One of the downsides of working with lambdas is dealing with checked exceptions.

Let’s see in this article how we can smoothen the process.

Lombok

When it comes to getting rid of boilerplate code, or providing tooling for hacks, Lombok is the library to use.

Lombok has two interesting features :

  • Extensions will allow you to extend existing APIs with custom methods
  • SneakyThrows annotation handles checked exceptions as runtime exceptions

In this tutorial, we’ll extend the Stream API with a safe method handling checked exceptions smoothly.

Let’s first write the @FunctionalInterface

Functional Interface

The functional interface will have the same signature as Function<T, U> . The only difference is that it will throw a checked exception.

@FunctionalInterface
public interface UncheckedFunction<T, U> {
    U apply(T t) throws Exception;
}

Extended Function

We’ll need to extend the Stream API, as explained earlier.

Let’s focus on the Stream#map method for now and extend it to be unchecked.

public static <T, U> Stream<U> mapUnchecked(Stream<T> stream, UncheckedFunction<T, U> function) {
    return stream.map(t -> *safeApply*(t, function));
}

This newer version, named will accept a function throwing a checked exception. We can notice safeApply being used taking the UncheckedException and the stream element.

Here’s its implementation

@SneakyThrows
private static <T, U> U safeApply(T t, UncheckedFunction<T, U> function) {
    return function.apply(t);
}

Thanks to @SneakyThrows , nowhere in our code do we still have to write these unelegant try-catch clauses.

The compiled version, however, is not as beautiful.

private static <U, T> U safeApply(T t, UncheckedFunction<T, U> function) {
    try {
        return function.apply(t);
    } catch (Throwable var3) {
        throw var3;
    }
}

But that’s none of our business anymore…

Let’s Use It

Let’s write a function throwing a checked exception. Its content or behavior is not important, we just need it to prove our point :

private static Integer convert(String str) throws Exception {
    if (str.isEmpty()) {
        throw new Exception();
    }
    return Integer.*parseInt*(str);
}

If we were to use this method in a stream, we’d usually go for something in the likes of :

Stream.*of*("1", "2", "3")
        .map(str -> {
            try {
                return *convert*(str);
            } catch (Exception e) {
                return null;
            }
        })
        .forEach(System.*out*::println);

Not really elegant.

Let’s see how we can use the methods written earlier.

Stream.*of*("1", "2", "3")
        .mapUnchecked(SafeCheckedExceptionsApplication::*convert*)
        .forEach(System.*out*::println);

It is as simple as this. Very clear what it does and gives us the same exact runtime result.

Conclusion

Checked exceptions are more than often an extra burden to deal with while writing Java application. This way offers us the flexibility to deal with them in a transparent way.

If you’re not ready for experimental features of Lombok and don’t want to be bothered with “compilation” highlighting, another way of dealing with checked exceptions in lambdas is available on DZone.