DEV Community

Cover image for Exception Handling in Java Streams

Exception Handling in Java Streams

The Stream API and lambdaโ€™s where a big improvement in Java since version 8. From that point on, we could work with a more functional syntax style. Now, after a few years of working with these code constructions, one of the bigger issues that remain is how to deal with checked exceptions inside a lambda.

As you all probably know, it is not possible to call a method that throws a checked exception from a lambda directly. In some way, we need to catch the exception to make the code compile. Naturally, we can do a simple try-catch inside the lambda and wrap the exception into a RuntimeException, as shown in the first example, but I think we can all agree that this is not the best way to go.

myList.stream()
  .map(item -> {
    try {
      return doSomething(item);
    } catch (MyException e) {
      throw new RuntimeException(e);
    }
  })
  .forEach(System.out::println);
Enter fullscreen mode Exit fullscreen mode

Most of us are aware that block lambdas are clunky and less readable. They should be avoided as much as possible, in my opinion. If we need to do more than a single line, we can extract the function body into a separate method and simply call the new method. A better and more readable way to solve this problem is to wrap the call in a plain old method that does the try-catch and call that method from within your lambda.

myList.stream()
  .map(this::trySomething)
  .forEach(System.out::println);
private Item trySomething(Item item) {
  try {
    return doSomething(item);
  } catch (MyException e) {
    throw new RuntimeException(e);
  }
}
Enter fullscreen mode Exit fullscreen mode

This solution is at least a bit more readable and we do separate our concerns. If you really want to catch the exception and do something specific and not simply wrap the exception into a RuntimeException, this can be a possible and readable solution for you.

RuntimeException

In many cases, you will see that people use these kinds of solutions to repack the exception into a RuntimeException or a more specific implementation of an unchecked Exception. By doing so, the method can be called inside a lambda and be used in higher-order functions.

I can relate a bit to this practice because I personally do not see much value in checked exceptions in general, but that is a whole other discussion that I am not going to start here. If you want to wrap every call in a lambda that has a checked into a RuntimeException, you will see that you repeat the same pattern. To avoid rewriting the same code over and over again, why not abstract it into a utility function? This way, you only have to write it once and call it every time you need it.

To do so, you first need to write your own version of the functional interface for a function. Only this time, you need to define that the function may throw an exception.

@FunctionalInterface
public interface CheckedFunction<T,R> {
    R apply(T t) throws Exception;
}
Enter fullscreen mode Exit fullscreen mode

Now, you are ready to write your own general utility function that accepts a CheckedFunction as you just described in the interface. You can handle the try-catch in this utility function and wrap the original exception into a RuntimeException (or some other unchecked variant). I know that we now end up with an ugly block lambda here and you could abstract the body from this. Choose for yourself if that is worth the effort for this single utility.

public static <T,R> Function<T,R> wrap(CheckedFunction<T,R> checkedFunction) {
  return t -> {
    try {
      return checkedFunction.apply(t);
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  };
}
Enter fullscreen mode Exit fullscreen mode

With a simple static import, you can now wrap the lambda that may throw an exception with your brand new utility function. From this point on, everything will work again.

myList.stream()
       .map(wrap(item -> doSomething(item)))
       .forEach(System.out::println);
Enter fullscreen mode Exit fullscreen mode

The only problem left is that when an exception occurs, the processing of your stream stops immediately. If that is no problem for you, then go for it. I can imagine, however, that direct termination is not ideal in many situations.

Either

When working with streams, we probably don't want to stop processing the stream if an exception occurs. If your stream contains a very large amount of items that need to be processed, do you want that stream to terminate when for instance the second item throws an exception? Probably not.

Let's turn our way of thinking around. Why not consider "the exceptional situation" just as much as a possible result as we would for a "successful" result. Let's consider it both as data, continuing to process the stream, and decide afterward what to do with it. We can do that, but to make it possible, we need to introduce a new type โ€” the Either type.

The Either type is a common type in functional languages and not (yet) part of Java. Similar to the Optional type in Java, an Either is a generic wrapper with two possibilities. It can either be a Left or a Right but never both. Both left and right can be of any types. For instance, if we have an Either value, this value can either hold something of type String or of type Integer, Either<String,Integer>.

If we use this principle for exception handling, we can say that our Either type holds either an Exception or a value. By convenience, normally, the left is the Exception and the right is the successful value. You can remember this by thinking of the right as not only the right-hand side but also as a synonym for โ€œgood,โ€ โ€œok,โ€ etc.

Below, you will see a basic implementation of the Either type. In this case, I used the Optional type when we try to get the left or the right because we:

public class Either<L, R> {
    private final L left;
    private final R right;
    private Either(L left, R right) {
        this.left = left;
        this.right = right;
    }
    public static <L,R> Either<L,R> Left( L value) {
        return new Either(value, null);
    }
    public static <L,R> Either<L,R> Right( R value) {
        return new Either(null, value);
    }
    public Optional<L> getLeft() {
        return Optional.ofNullable(left);
    }
    public Optional<R> getRight() {
        return Optional.ofNullable(right);
    }
    public boolean isLeft() {
        return left != null;
    }
    public boolean isRight() {
        return right != null;
    }
    public <T> Optional<T> mapLeft(Function<? super L, T> mapper) {
        if (isLeft()) {
            return Optional.of(mapper.apply(left));
        }
        return Optional.empty();
    }
    public <T> Optional<T> mapRight(Function<? super R, T> mapper) {
        if (isRight()) {
            return Optional.of(mapper.apply(right));
        }
        return Optional.empty();
    }
    public String toString() {
        if (isLeft()) {
            return "Left(" + left +")";
        }
        return "Right(" + right +")";
    }
}
Enter fullscreen mode Exit fullscreen mode

You can now make your own functions return an Either instead of throwing an Exception. But that doesn't help you if you want to use existing methods that throw a checked Exception inside a lambda right? Therefore, we have to add a tiny utility function to the Either type I described above.

public static <T,R> Function<T, Either> lift(CheckedFunction<T,R> function) {
  return t -> {
    try {
      return Either.Right(function.apply(t));
    } catch (Exception ex) {
      return Either.Left(ex);
    }
  };
}
Enter fullscreen mode Exit fullscreen mode

By adding this static lift method to the Either, we can now simply "lift" a function that throws a checked exception and let it return an Either. If we take the original problem, we now end up with a Stream of Eithers instead of a possible RuntimeException that may blow up my entire Stream.

myList.stream()
       .map(Either.lift(item -> doSomething(item)))
       .forEach(System.out::println);
Enter fullscreen mode Exit fullscreen mode

This simply means that we have taken back control. By using the filter function in the Stream API, we can simply filter out the left instances and, for example, log them. You can also filter the right instances and simply ignore the exceptional cases. Either way, you are back in control again and your stream will not terminate instantly when a possible RuntimeException occurs.

Because Either is a generic wrapper, it can be used for any type, not just for exception handling. This gives us the opportunity to do more than just wrapping the Exception into the left part of an Either. The issue we now might have is that if the Either only holds the wrapped exception, and we cannot do a retry because we lost the original value. By using the ability of the Either to hold anything, we can store both the exception and the value inside a left. To do so, we simply make a second static lift function like this.

public static <T,R> Function<T, Either> liftWithValue(CheckedFunction<T,R> function) {
  return t -> {
    try {
      return Either.Right(function.apply(t));
    } catch (Exception ex) {
      return Either.Left(Pair.of(ex,t));
    }
  };
}
Enter fullscreen mode Exit fullscreen mode

You see that in this liftWithValue function within the Pair type is used to pair both the exception and the original value into the left of an Either. Now, we have all the information we possibly need if something goes wrong, instead of only having the Exception.

The Pair type used here is another generic type that can be found in the Eclipse Collections library, or you can simply implement your own. Anyway, it is just a type that can hold two values.

public class Pair<F,S> {
    public final F fst;
    public final S snd;
    private Pair(F fst, S snd) {
        this.fst = fst;
        this.snd = snd;
    }
    public static <F,S> Pair<F,S> of(F fst, S snd) {
        return new Pair<>(fst,snd);
    }
}
Enter fullscreen mode Exit fullscreen mode

With the use of the liftWithValue, you now have all the flexibility and control to use methods that may throw an Exception inside a lambda. When the Either is a right, we know that the function was applied correctly and we can extract the result. If, on the other hand, the Either is a left, we know something went wrong and we can extract both the Exception and the original value, so we can proceed as we like. By using the Either type instead of wrapping the checked Exception into a RuntimeException, we prevent the Stream from terminating halfway.

Try

People that may have worked with for instance Scala may use the Try instead of the Either for exception handling. The Try type is something that is very similar to the Either type. It has, again, two cases: โ€œsuccessโ€ or โ€œfailure.โ€ The failure can only hold the type Exception, while the success can hold anything type you want. So, the Try is nothing more than a specific implementation of the Either where the left type (the failure) is fixed to type Exception.

public class Try<Exception, R> {
    private final Exception failure;
    private final R succes;
    public Try(Exception failure, R succes) {
        this.failure = failure;
        this.succes = succes;
    }
}
Enter fullscreen mode Exit fullscreen mode

Some people are convinced that it is easier to use, but I think that because we can only hold the Exceptionitself in the failure part, we have the same problem as explained in the first part of the Either section. I personally like the flexibility of the Either type more. Anyway, in both cases, if you use the Try or the Either, you solve the initial problem of exception handling and do not let your stream terminate because of a RuntimeException.

Libraries

Both the Either and the Try are very easy to implement yourself. On the other hand, you can also take a look at functional libraries that are available. For instance, VAVR (formerly known as Javaslang) does have implementations for both types and helper functions available. I do advise you to take a look at it because it holds a lot more than only these two types. However, you have ask yourself the question of whether you want this large library as a dependency just for exception handling when you can implement it yourself with just a few lines of code.

Conclusion

When you want to use a method that throws a checkedException, you have to do something extra if you want to call it in a lambda. Wrapping it into a RuntimeException can be a solution to make it work. If you prefer to use this method, I urge you to create a simple wrapper tool and reuse it, so you are not bothered by the try/catch every time.

If you want to have more control, you can use the Either or Try types to wrap the outcome of the function, so you can handle it as a piece of data. The stream will not terminate when a RuntimeException is thrown and you have the liberty to handle the data inside your stream as you please.

Top comments (2)

Collapse
 
nfrankel profile image
Nicolas Frankel

I'm wondering if there's a Java compiler plugin that wraps checked exceptions in lambdas in unchecked exceptions. If not, how hard would it be to design one?

Collapse
 
gklijs profile image
Gerard Klijs

Nice, looks a lot like Haskell like that.