29

Let's say I have a method which throws a runtime exception. I'm using a Stream to call this method on items in a list.

class ABC {

    public void doStuff(MyObject myObj) {
        if (...) {
            throw new IllegalStateException("Fire! Fear! Foes! Awake!");
        }
        // do stuff...
    }

    public void doStuffOnList(List<MyObject> myObjs) {
        try {
            myObjs.stream().forEach(ABC:doStuff);
        } catch(AggregateRuntimeException??? are) {
            ...
        }             
    }
}

Now I want all items in the list to be processed, and any runtime exceptions on individual items to be collected into an "aggregate" runtime exception which will be thrown at the end.

In my real code, I am making 3rd party API calls which may throw runtime exceptions. I want to make sure that all items are processed and any errors reported at the end.

I can think of a few ways to hack this out, such as a map() function which catches and returns the exception (..shudder..). But is there a native way to do this? If not, is there another way to implement it cleanly?

metacubed
  • 7,031
  • 6
  • 36
  • 65
  • This feels like an abuse of the exception mechanism. What's your real use case here? Validating a set of operations and reporting what failed? – Kayaman May 08 '15 at 06:33
  • @Kayaman not quite. I am making 3rd party API calls which may throw runtime exceptions. All I want to do is make sure all items are processed and any errors reported at the end. – metacubed May 08 '15 at 06:35
  • 1
    Do you really need to use streams? This is trivial without it. – Kayaman May 08 '15 at 06:36
  • @Kayaman I know, right? But I was hoping to use `parallelStream` eventually. – metacubed May 08 '15 at 06:37
  • My stream-fu isn't too strong to give you a nice answer, but based on [this discussion on streams and exceptions](http://stackoverflow.com/questions/19757300/java-8-lambda-streams-filter-by-method-with-exception) a stream based solution would end up being at least a bit dodgy. – Kayaman May 08 '15 at 06:44

4 Answers4

30

In this simple case where the doStuff method is void and you only care about the exceptions, you can keep things simple:

myObjs.stream()
    .flatMap(o -> {
        try {
            ABC.doStuff(o);
            return null;
        } catch (RuntimeException ex) {
            return Stream.of(ex);
        }
    })
    // now a stream of thrown exceptions.
    // can collect them to list or reduce into one exception
    .reduce((ex1, ex2) -> {
        ex1.addSuppressed(ex2);
        return ex1;
    }).ifPresent(ex -> {
        throw ex;
    });

However, if your requirements are more complicated and you prefer to stick with the standard library, CompletableFuture can serve to represent "either success or failure" (albeit with some warts):

public static void doStuffOnList(List<MyObject> myObjs) {
    myObjs.stream()
            .flatMap(o -> completedFuture(o)
                    .thenAccept(ABC::doStuff)
                    .handle((x, ex) -> ex != null ? Stream.of(ex) : null)
                    .join()
            ).reduce((ex1, ex2) -> {
                ex1.addSuppressed(ex2);
                return ex1;
            }).ifPresent(ex -> {
                throw new RuntimeException(ex);
            });
}
Misha
  • 27,433
  • 6
  • 62
  • 78
5

There are already some implementations of Try monad for Java. I found better-java8-monads library, for example. Using it, you can write in the following style.

Suppose you want to map your values and track all the exceptions:

public String doStuff(String s) {
    if(s.startsWith("a")) {
        throw new IllegalArgumentException("Incorrect string: "+s);
    }
    return s.trim();
}

Let's have some input:

List<String> input = Arrays.asList("aaa", "b", "abc  ", "  qqq  ");

Now we can map them to successful tries and pass to your method, then collect successfully handled data and failures separately:

Map<Boolean, List<Try<String>>> result = input.stream()
        .map(Try::successful).map(t -> t.map(this::doStuff))
        .collect(Collectors.partitioningBy(Try::isSuccess));

After that you can process successful entries:

System.out.println(result.get(true).stream()
    .map(t -> t.orElse(null)).collect(Collectors.joining(",")));

And do something with all the exceptions:

result.get(false).stream().forEach(t -> t.onFailure(System.out::println));

The output is:

b,qqq
java.lang.IllegalArgumentException: Incorrect string: aaa
java.lang.IllegalArgumentException: Incorrect string: abc  

I personally don't like how this library is designed, but probably it will be suitable for you.

Here's a gist with complete example.

Tagir Valeev
  • 97,161
  • 19
  • 222
  • 334
2

Here's a variation on the theme of mapping-to-exceptions.

Start with your existing doStuff method. Note that this conforms to the functional interface Consumer<MyObject>.

public void doStuff(MyObject myObj) {
    if (...) {
        throw new IllegalStateException("Fire! Fear! Foes! Awake!");
    }
    // do stuff...
}

Now write a higher-order function that wraps this and turns this into a function that might or might not return an exception. We want to call this from flatMap, so the way "might or might not" is expressed is by returning a stream containing the exception or an empty stream. I'll use RuntimeException as the exception type here, but of course it could be anything. (In fact it might be useful to use this technique with checked exceptions.)

<T> Function<T,Stream<RuntimeException>> ex(Consumer<T> cons) {
    return t -> {
        try {
            cons.accept(t);
            return Stream.empty();
        } catch (RuntimeException re) {
            return Stream.of(re);
        }
    };
}

Now rewrite doStuffOnList to use this within a stream:

void doStuffOnList(List<MyObject> myObjs) {
    List<RuntimeException> exs =
        myObjs.stream()
              .flatMap(ex(this::doStuff))
              .collect(Collectors.toList());
    System.out.println("Exceptions: " + exs);
}
Stuart Marks
  • 127,867
  • 37
  • 205
  • 259
  • What would be the advantage of using this approach instead of just returning `RuntimeException` from the higher order function and using `map()` instead of `flatMap()`? – metacubed May 08 '15 at 07:12
  • @metacubed The mapper function always has to return something. If there's no exception, should it perhaps return null? Then nulls would end up in the result list, or they'd have to be filtered out. – Stuart Marks May 08 '15 at 07:49
1

The only possible way I can imagine is to map values in a list to a monad, that will represent the result of your processing execution (either success with value or failure with throwable). And then fold your stream into single result with aggregated list of values or one exception with list of suppressed ones from the previous steps.

public Result<?> doStuff(List<?> list) {
     return list.stream().map(this::process).reduce(RESULT_MERGER)
}

public Result<SomeType> process(Object listItem) {
    try {
         Object result = /* Do the processing */ listItem;
         return Result.success(result);
    } catch (Exception e) {
         return Result.failure(e);
    }
}

public static final BinaryOperator<Result<?>> RESULT_MERGER = (left, right) -> left.merge(right)

Result implementation may vary but I think you get the idea.

Alex
  • 7,460
  • 2
  • 40
  • 51
  • Yes exactly. I started down the route of rolling-my-own result mapper, but was hoping there was a better way. – metacubed May 08 '15 at 06:55
  • Is `someValue` a parameter of process? If so, could you fix it please? – EliuX Apr 09 '17 at 04:20
  • 1
    It could be that you can declare something like `SomeType result` and then a processing is done where `listItem` is converted into `result` and then you return `Result.success(result)`? – EliuX Apr 09 '17 at 04:43