Exception Handling in Java Streams
By: Arindam Roy
Tags: Software Development, Programming, Software Engineering, Java

Unchecked Exceptions

Let’s take an example use of streams: You are given a list of strings and you want to convert them all to integers. To achieve that, we can do something simple like this:

List<String> integers = Arrays.asList("44", "373", "145");

integers.forEach(str -> System.out.println(Integer.parseInt(str)));

The above snippet will work perfectly, but what happens if we modify the input to contain an illegal string, say "xyz" . The method parseInt() will throw a NumberFormatException , which is a type of unchecked exception.

A naive solution, one that is typically seen is to wrap the call in a try/catch block and handle it. That would look like this:

List<String> integers = Arrays.asList("44", "373", "xyz", "145"); integers.forEach(str -> {

try {

System.out.println(Integer.parseInt(str));

}catch (NumberFormatException ex) {

System.err.println("Can't format this string");

}

}

);

While this works, this defeats the purpose of writing small lambdas to make code readable and less verbose. The solution that comes to mind is to wrap the lambda around another lambda that does the exception handling for you, but that is basically just moving the exception handling code somewhere else:

static Consumer<String> exceptionHandledConsumer(Consumer<String> unhandledConsumer) {

return obj -> {

try {

unhandledConsumer.accept(obj);

} catch (NumberFormatException e) {

System.err.println(

"Can't format this string");

}

};

} public static void main(String[] args) {

List<String> integers = Arrays.asList("44", "xyz", "145"); integers.forEach(exceptionHandledConsumer(str -> System.out.println(Integer.parseInt(str))));

}

The above solution can be made much, much better by using generics. Let’s build a generic exception handled consumer that can handle all kinds of exceptions. We will, then, be able to use it for many different use cases within our application.

We can make use of the above code to build out our generic implementation. I will not go into the details of how generics work, but a good implementation would look like this:

static <Target, ExObj extends Exception> Consumer<Target> handledConsumer(Consumer<Target> targetConsumer, Class<ExObj> exceptionClazz) { return obj -> {

try {

targetConsumer.accept(obj);

} catch (Exception ex) {

try {

ExObj exCast = exceptionClazz.cast(ex);

System.err.println(

"Exception occured : " + exCast.getMessage());

} catch (ClassCastException ccEx) {

throw ex;

}

}

};

}

As you can see, this new consumer is not bound to any particular type of object it consumes and accepts the type of Exception your code might throw as a parameter. We can now, simply use the handledConsumer method to build our consumers. The code for parsing our list of Strings to Integers will now be this:

List<String> integers = Arrays.asList("44", "373", "xyz", "145"); integers.forEach(

handledConsumer(str -> System.out.println(Integer.parseInt(str)),

NumberFormatException.class));

If you have a different block of code that may throw a different exception, you can just reuse the above method. For example, the code below takes care of ArithmeticException due to a divide by zero.