javafunctional-programmingvavr

Exception handling in streams with Either


Background

I have been fascinated with Scott WLaschin's Railway Oriented Programming model of handling exceptions: have a side channel, where all the bad stuff will be processed, and keep the good stuff on the main track. Picture below:

enter image description here

Problem

A common pattern that comes up in daily code is like :

Question

How to do this in a way that resembles the railway-oriented model discussed above.


Solution

  • Somjit's answer shows correct approach however it uses an outer variable (errors) to accumulate the errors. This is, in general, discouraged since we should avoid the global/external state.

    You can use vavr's partition method to split the stream into two: one with errors and one with validated ints. They are both put into a tuple:

    public void composeExceptions() {
       final Tuple2<Stream<Either<IllegalArgumentException, Integer>>, Stream<Either<IllegalArgumentException, Integer>>> both = Stream.range(1, 11)
                .map(this::validate)
                .partition(Either::isLeft);
    
       both._1.map(Either::getLeft).forEach(e -> System.out.println("Got error: " + e.getMessage()));
       both._2.map(Either::get).forEach(i -> System.out.println("Validated correctly: " + i));
    }
    

    EDIT Actually there also other options like:

    Stream
       .range(1, 11)
       .map(this::validate)
       .toJavaStream()
       .collect(Collectors.teeing(
          Collectors.filtering(Either::isLeft, toList()),
          Collectors.filtering(Either::isRight, toList()),
          (errors, ints) -> new Tuple2<>(errors.stream().map(Either::getLeft), ints.stream().map(Either::get))));
    

    which uses teeing which is quite interesting collector from java API. Unfortunately it mixes vavr and java API which is not great, not terrible.

    And:

    Stream
       .range(1, 11)
       .map(this::validate)
       .collect(
          () -> new Tuple2<>(List.<RuntimeException>empty().asJavaMutable(), List.<Integer>empty().asJavaMutable()),
          (tuple, either) -> {
             either.peekLeft(tuple._1::add);
             either.peek(tuple._2::add);
          },
          (t1, t2) -> {
             t1._1.addAll(t2._1);
             t1._2.addAll(t2._2);
          }
        )
           .map((exceptions, integers) -> new Tuple2<>(List.ofAll(exceptions), List.ofAll(integers)));
    

    which uses vavr API only but underneath uses java List since a mutable structure is required here.