9
stream.map(obj -> doMap(obj)).collect(Collectors.toList());

private String doMap(Object obj) {
    if (objectIsInvalid) {
    throw new ParseException("Object could not be parsed");
    }
}

Problem: how can I throw the exception and make the stream iteration know that it should not break the whole iteration, but continue with the next element (and eventually log failed objects)?

membersound
  • 81,582
  • 193
  • 585
  • 1,120
  • 3
    If you don't want to return anything when an exception is thrown, perhaps you should use `flatmap` instead of `map`. – Balduz May 22 '15 at 13:02
  • 1
    Maybe you can just return null instead of a String and filter on non-null objects? – Davio May 22 '15 at 13:04
  • @membersound, is `ParseException` something you created yourself, or `java.text.ParseException`? – aioobe May 22 '15 at 13:11
  • 2
    I create the exception myself. But it does not matter, it may likewise be throws of any other framework class I use in the map method. – membersound May 22 '15 at 13:24
  • 1
    @Davio yes for continuing the loop that's a great idea to `return null` instead of throwing an exception and filter accordingly after the mapping. With the current limitations of streams probably the best solution, you might want to add this as an answer. – membersound May 22 '15 at 13:32
  • 1
    Reopened; not a duplicate of http://stackoverflow.com/questions/27644361/how-can-i-throw-checked-exceptions-from-inside-java-8-streams . That question has to do with the inability to throw checked exceptions from lambdas matching predefined functional interfaces. This question is about how to deal with exceptions (checked or unchecked) without stopping stream processing. – Stuart Marks May 25 '15 at 00:33

5 Answers5

6

Here's one weird trick you can use to improve your exception handling.

Let's say your mapper function is like this:

String doMap(Object obj) {
    if (isInvalid(obj)) {
        throw new IllegalArgumentException("info about obj");
    } else {
        return obj.toString();
    }
}

This returns a result if the object is valid, but it throws an exception if the object is invalid. Unfortunately if you stick this directly into a pipeline, any error will stop the pipeline execution. What you want is something like an "either" type that can hold either a value or an error indicator (which would be a exception in Java).

Turns out that CompletableFuture can hold either a value or an exception. Although it's intended for asynchronous processing -- which isn't occurring here -- we only have to contort it a little bit to use for our purposes.

First, given a stream of objects to process, we call the mapping function wrapped in a call to supplyAsync:

 CompletableFuture<String>[] cfArray = 
        stream.map(obj -> CompletableFuture.supplyAsync(() -> doMap(obj), Runnable::run))
              .toArray(n -> (CompletableFuture<String>[])new CompletableFuture<?>[n]);

(Unfortunately, the generic array creation gives an unchecked warning, which will have to be suppressed.)

The odd construct

 CompletableFuture.supplyAsync(supplier, Runnable::run)

runs the supplier "asynchronously" on the provided Executor Runnable::run, which simply runs the task immediately in this thread. In other words, it runs the supplier synchronously.

The trick is that the CompletableFuture instance returned from this call contains either the value from the supplier, if it returned normally, or it contains an exception, if the supplier threw one. (I'm disregarding cancellation here.) We then gather the CompletableFuture instances into an array. Why an array? It's setup for the next part:

CompletableFuture.allOf(cfArray).join();

This normally waits for the array of CFs to complete. Since they've been run synchronously, they should already all be complete. What's important for this case is that join() will throw a CompletionException if any of the CFs in the array has completed exceptionally. If the join completes normally, we can simply gather up the return values. If the join throws an exception, we can either propagate it, or we can catch it and process the exceptions stored in the CFs in the array. For example,

try {
    CompletableFuture.allOf(cfArray).join();
    // no errors
    return Arrays.stream(cfArray)
                 .map(CompletableFuture::join)
                 .collect(toList());
} catch (CompletionException ce) {
    long errcount =
        Arrays.stream(cfArray)
              .filter(CompletableFuture::isCompletedExceptionally)
              .count();
    System.out.println("errcount = " + errcount);
    return Collections.emptyList();
}

If all are successful, this returns a list of the values. If there are any exceptions, this counts the number of exceptions and returns an empty list. Of course, you could easily do something else, like log the details of the exceptions, filter out the exceptions and return a list of valid values, etc.

Andrii Abramov
  • 10,019
  • 9
  • 74
  • 96
Stuart Marks
  • 127,867
  • 37
  • 205
  • 259
4

Without an exception you can work with Optionals:

stream.map(obj -> doMap(obj))
      .filter(obj -> obj.isPresent())
      .collect(Collectors.toList());

private Optional<String> doMap(Object obj) {
   if (objectIsInvalid) {
    return Optional.empty();
   }
}
Andrii Abramov
  • 10,019
  • 9
  • 74
  • 96
Davio
  • 4,609
  • 2
  • 31
  • 58
  • 2
    I guess it depends on what you're doing, but silently suppressing exceptions is usually a very bad idea. If someone passed in invalid data, or if the user specified invalid input, you should tell them about it, not behave as if everything is fine. – VGR May 22 '15 at 14:33
  • 3
    I don’t know why you want to “work with nulls *and* Optionals”. Normally you should decide for *either*, using `null` or using `Optional`. Your code returns `null` in `doMap` and tries to invoke `.isPresent()` on it within `filter(…)`… – Holger May 22 '15 at 15:30
  • 3
    Please don't do this. If something is invalid, you probably want to return an *empty* `Optional` instead of `null`. As it stands, you will get NPE when `isPresent` is called on `null`. – Stuart Marks May 22 '15 at 16:37
  • Sometimes this solution is not possible, when you use external service which simply throws exception. – Łukasz Rzeszotarski May 30 '16 at 15:48
4

If you want a means to quietly ignore those elements that throw an exception when mapped, you can define a helper method that either returns a success or nothing, as a stream of 0 or 1 elements, and then flatMap your stream with that method.

import static java.util.stream.Collectors.toList;

import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Stream;

public class Fish {
    private int weight;
    public Fish(int weight) { this.weight = weight; }
    public String size() {
        if (weight < 10) {
            return "small";
        } else if (weight < 50) {
            return "medium";
        } else if (weight < 100) {
            return "large";
        } else {
            throw new RuntimeException("I don't believe you");
        }   
    }

    public static <T> Stream<T> successOrNothing(Supplier<T> supplier) {
      try {
        return Stream.of(supplier.get());
      } catch (Exception e) {
        return Stream.empty();
      }
    }

    public static void main(String[] args) {
        Stream<Fish> fishes = Stream.of(new Fish(1000), new Fish(2));
        List<String> sizes = fishes
          .flatMap(fish -> successOrNothing(fish::size))
          .collect(toList());
        System.out.println(sizes);
    }
}

Each element in the stream gets mapped to 1 element (if the call returned successfully) or 0 (if it did not). This might be easier or more straightforward then the "change the method to return null or Optional.empty() instead of throwing" pattern, especially when dealing with a method whose contract already is defined and you don't want to change.

Caveat: this solution is still silently ignoring exceptions; that is inappropriate in many situations.

0

I would do following...

stream.map(doMapLenient())
      .filter(Objects::nonNull)
      .collect(Collectors.toList());

private String doMap(Object obj) {
    if (objectIsInvalid(obj)) {
    throw new ParseException("Object could not be parsed");
    }
    return "Streams are cool";
}

private Function<Object, String> doMapLenient() {
  return obj -> {
     try {
       return doMap(obj);   
   } catch (ParseExcepion ex) {
       return null; 
   }
}

here you can add nicely also some logging in catch part

Andrii Abramov
  • 10,019
  • 9
  • 74
  • 96
Łukasz Rzeszotarski
  • 5,791
  • 6
  • 37
  • 68
-2

You can just explicitly iterate over the stream elements using an iterator:

Stream<Object> stream = ... // throws unchecked exceptions;
Iterator<Object> iterator = stream.iterator();
while (iterator.hasNext()) {
    try {
        Object obj = it.next();
        // process
    } catch (ParseException ex) {
        // handle
    }
}
b10y
  • 839
  • 9
  • 19