Checked exceptions in Java 8 streams

When you code some pipeline processing of a stream in Java 8, it might happen that a method you want to use throws a checked exception. What happens then ? The compiler complains because checked exceptions don’t bubble up like you’d naively expect; in other words you should deal with the exception when it happens, there is no default mechanism for interrupting the stream and rethrowing that exception automatically.

In practice this makes rewriting legacy code to Java 8 somewhat clumsy, because it requires adding an ugly try/catch inside the stream processing functions. You can then choose to re-throw an unchecked (runtime) exception that wraps the original exception, and either let it bubble up or catch it and re-throw the wrapped (checked) exception. Ugly.

Consider a Stream<String> containing class names, that you want to map to a Stream<Class>. What you’d like to write is probably

  stream.map(Class::forName)

and handle the ClassNotFoundException at the outer level. But the compiler will not let you: “incompatible thrown types java.lang.ClassNotFoundException in method reference”. Catching the exception inside the lambda gives something like this:

stream.map(s -> {
  try {
    return Class.forName(s);
  } catch (ClassNotFoundException e) {
    throw new RuntimeException(e);
  }
});

If you want to re-throw the original exception it gets worse:

try {
  stream.map(s -> {
    try {
      return Class.forName(s);
    } catch (ClassNotFoundException e) {
      throw new RuntimeException(e);
    }
  });
} catch (RuntimeException e) {
  if (e.getCause() != null) {
    throw e.getCause();
  }
}

Is there a way to avoid having to do so and still keep the elegance and concision of Java 8 streams ? Of course there is.  One way to do it is “sneaky throws“: a sneaky throw is a checked exception that is thrown by a method at runtime, while the method declaration doesn’t declare it. It is achieved by fooling the compiler; at runtime there is no constraint on what kind of exceptions can be thrown inside the JVM.

Some libraries (like org.jooq.lambda.Uncheck) will gladly help you to use functions that throw checked exceptions inside lambdas by wrapping them into sneaky-throwing functions. But if you do so, the compiler will not force you to deal with the exception, which is the whole point of checked exceptions… This is why I found MarcG‘sLambdaExceptionUtilfrom this Stackoverflow page  elegant and clean, as it maintains the checked nature of the thrown exception (so you have to catch it or declare it in the containing method), and still allows you to write concise code for your stream processing, like this:

  stream.map(rethrowFunction(Class::forName))

 


Leave a Reply

Your email address will not be published. Required fields are marked *