Handle Stream Exceptions with an Attempt

Streams has become a very popular way to process a collection of elements. But a problem arises when some operation that you have to perform on the elements in the stream can throw an exception, which will interrupt your stream. In this article we will look at how we can handle it in an easy way by using an Attempt class.

Let’s say we have a collection of elements type R that we want to process with a stream. In the stream we want to map the elements to type T. But this operation can throw a runtime exception, or even worse a checked exception. If that happens our stream will be interrupted and we won’t process the remaining elements.

Introducing an Attempt

There are many libraries which provides ways to handle this, but it is unnecessary to pull in another dependency for such a simple task. We can write a generic class which can do this for us with about 25 lines of code.

public class Attempt {

  private T result;
  private Exception exception;

  private Attempt(T result, Exception e) {
    this.result = result;
    this.exception = e;
  }

  public static  Attempt of(final RuntimeExceptionWrappable supplier) {
    try {
      return new Attempt(supplier.execute(), null);
    } catch (Exception e) {
      return new Attempt(null, e);
    }
  }

  public Optional getResult() {
    return Optional.ofNullable(result);
  }

  public Optional getException() {
    return Optional.ofNullable(exception);
  }

  @FunctionalInterface
  interface RuntimeExceptionWrappable {
    T execute() throws Exception;
  }

}

That’s it! The Attempt class with the RuntimeExceptionWrappable functional interface allows us to handle even checked exceptions in a clean way. Storing the results and exceptions in our wrapper class allows us to continue processing the stream when something bad happens, so that we can deal with the exceptions later.

I like to utilise java.util.Optional to achieve null safety, but you could of course add add another method such as boolean executedSuccessfully() to do your check before retrieving the result or the exception.

Also make sure that you override equals and the hash code if you need it. I excluded it in the example as it is not necessary for it.

Here are a couple of tests which shows how easy and clean the code looks like using this utility class.

public class AttemptTest {

  @Test
  public void givenSupplierThatThrowsCheckedException() {
    final Attempt attempt = Attempt.of(() -> doSomething(-1));

    assertTrue(attempt.getException().isPresent());
    assertFalse(attempt.getResult().isPresent());
  }

  @Test
  public void givenSupplierThatExecutesSuccessfully() {
    final Attempt attempt = Attempt.of(() -> doSomething(0));

    assertFalse(attempt.getException().isPresent());
    assertTrue(attempt.getResult().isPresent());
  }

  @Test
  public void givenStreamExpectAllItemsProcessed() {
    int expectedFailures = 5;
    int expectedSuccess = 10;
    final List> attempts = IntStream.range(0 - expectedFailures, expectedSuccess)
        .mapToObj(i -> Attempt.of(() -> doSomething(i)))
        .collect(Collectors.toList());

    final Map>> results = attempts.stream()
        .collect(Collectors.groupingBy(attempt -> attempt.getResult().isPresent()));

    assertEquals(expectedSuccess + expectedFailures, attempts.size());
    assertEquals(expectedSuccess, results.get(Boolean.TRUE).size());
    assertEquals(expectedFailures, results.get(Boolean.FALSE).size());
  }

  private String doSomething(int i) throws Exception {
    if (i < 0) {
      throw new Exception();
    } else {
      return "TEST: " + i;
    }
  }

}

With an Attempt the code becomes quite neat and tidy, I would say!

Leave a Reply