Java Lambda Exceptions

Java Lambda Expressions are a powerful functional programming construct introduced from Java 8 through the use of functional interfaces. They offer a powerful mechanism to raise the level of abstraction and write more reusable, concise code. However, exception handling with functional interfaces can be difficult to work with. In this tutorial we will look at some ways to deal with exceptions when working with Lambda expressions.

Java Lambda Exceptions

In Java we normally handle exceptions using try catch blocks and this practice has not changed with the introduction of lambda expressions. Let’s take a simple example imagine we are creating a file processing application and we need to check if a file is empty and then take some action.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public class EmptyFileProcessor {
    public void processFiles(List<String> filesToProcess) {
        filesToProcess.stream()
                .map(file -> ResourceUtils.getFileFromResources(file))
                .forEach(file -> {
                    final String contents = IOUtils.readContentAsString(file);
                    if (contents == null || contents.trim().equals("")) {
                        System.out.println("File is empty");
                        //process the file in some way
                    }
                });
    }
}

The code above is pretty simple we used a Stream and the first map method converts the file path to a File object which is then passed to the foreach method which checks if the file is empty. The code within the map and foreach methods are lambda expressions. If we look closely at the code in the foreach we use a utility method IOUtils.readContentAsString() to read the contents of a file to String. However, this method throws an IOException, and if we try to compile this code as is, it won’t compile. In this case we must handle the exception within the lambda expression.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public void processFiles(List<String> filesToProcess) {
    filesToProcess.stream()
            .map(file -> ResourceUtils.getFileFromResources(file))
            .forEach(file -> {
                try {
                    final String contents = IOUtils.readContentAsString(file);
                    if (contents == null || contents.trim().equals("")) {
                        System.out.println("File is empty");
                        //process the file in some way
                    } 
                }
                catch(IOException ioe) {
                    ioe.printStackTrace();
                }
            });
}

Above we update the code to handle the exception. However, lambda code is supposed to be short, and concise and adding the exception handling to the lambda expression adds a lot of bloat.

Lets try improve our code to handle the exceptions in lambda expression a bit better.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
public void processFiles(List<String> filesToProcess) {
    filesToProcess.stream()
            .map(file -> ResourceUtils.getFileFromResources(file))
            .forEach(file -> {
                final String contents = readFileContentsAsString(file);
                if (contents == null || contents.trim().equals("")) {
                    System.out.println("File is empty");
                    //process the file in some way
                }
            });
}
private File readFileFromResource(String file) {
    return ResourceUtils.getFileFromResources(file);
}
private String readFileContentsAsString(File file) {
    try {
        return IOUtils.readContentAsString(file);
    } catch (IOException ioe) {
        throw new RuntimeException(ioe);
    }
}

In the above code we remove the exception handling code from the lambda expression and move it to helper methods that handle the exception. But there is no real benefit to doing this. In fact we are just masking the exception handling code, and we had to write more boilerplate code to keep our lambda expression concise.

What we need is a more concise reusable approach to handling exceptions in lambda expressions. If we look closely at the foreach function we can see it takes the functional interface Consumer . In this case we raise the level of abstraction by creating our own Functional interface to handle the exception.

1
2
3
4
@FunctionalInterface
public interface ThrowingConsumer<T, E extends Throwable> {
    void accept(T t) throws E;
}

Above we create a new ThrowingConsumer functional interface but it still does nothing useful. To this we need to create a utility method to handle the exception. In this case we have two choices we can create a set of helper methods and place them in a utility static class or we can take advantage of the fact that Java 8 allows us to place static methods in an interface. We will follow the second approach.

1
2
3
4
5
6
7
8
9
static <T, E extends Throwable> Consumer<T> unchecked(ThrowingConsumer<T, E> consumer) {
    return (t) -> {
        try {
            consumer.accept(t);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    };
}

Above we create a utility method unchecked which takes our new ThrowingConsumer functional interface and wraps it in a try catch block handling any exception thrown from the consumer and wrapping it in a RuntimeException. Now lets update our file processor with this new construct.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public void processFiles(List<String> filesToProcess) {
    filesToProcess.stream()
            .map(file -> ResourceUtils.getFileFromResources(file))
            .forEach(ThrowingConsumer.unchecked(file -> {
                final String contents = IOUtils.readContentAsString(file);
                if (contents == null || contents.trim().equals("")) {
                    System.out.println("File is empty");
                    //process the file in some way
                }
            }));
}

Now if we examine the above the code we have removed the boilerplate code and handled the checked exception within the lambda expression in a much cleaner way. In the above case we supported Java methods that take a Functional interface of type consumer, and we can follow the same approach for other functional interfaces.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package com.stephenenright.tutorials.corejavaexception.function;

import java.util.function.Function;

@FunctionalInterface
public interface ThrowingFunction<T, R, E extends Throwable> {
    static <T, R, E extends Throwable> Function<T, R> unchecked(ThrowingFunction<T, R, E> f) {
        return t -> {
            try {
                return f.apply(t);
            } catch (Throwable e) {
                throw new RuntimeException(e);
            }
        };
    }
    R apply(T t) throws E;
}

In the above code we create a new functional interface ThrowingFunction that handles the exception and returns a the functional interface Function that accepts one parameter of type T and a return value of type R.

Improving Java Lambda Checked Exception Handling

In the last section we looked at a reusable approach to handling checked exceptions in Java lambda expressions. The examples in the last section simply wrapped an exception and threw a RuntimeException which allowed us to simplify our lambda expressions. However, maybe there are times when we want to do more than simply wrap the exception.

1
2
3
4
@FunctionalInterface
public interface ExceptionHandler {
    boolean handle(Throwable t);
}

Above we create a simple functional interface ExceptionHandler for demonstration purposes this functional interface can be used to handle an exception in a particular way. This interface returns a boolean value to indicate if the exception has being handled. We now create a new functional interface to handle an exception thrown by a Consumer.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
@FunctionalInterface
public interface ThrowingConsumerHandler<T, E extends Throwable> {
    static <T, E extends Throwable> Consumer<T> unchecked(ThrowingConsumerHandler<T, E> consumer, ExceptionHandler handler) {
        return (t) -> {
            try {
                consumer.accept(t);
            } catch (Throwable e) {
                if(!handler.handle(e)) {
                    throw new RuntimeException(e);
                }
            }
        };
    }
    void accept(T t) throws E;
}

The ThrowingConsumerHandler delegates to the ExceptionHandler to do additional exception processing, if the ExceptionHandler returns false we throw a RuntimeException. Again, the ExceptionHandler shown in this example can be enhanced in your own projects to make the mechanism more powerful.

1
2
3
4
5
public void processFiles(List<String> filesToProcess) {
    filesToProcess.stream()
            .map(file -> //do processing
            , (e) -> {processorStats.addFailure(); return false;}));
}

Lambda Runtime Exceptions

In the last sections we looked at handling Java checked exceptions in lambda expressions. However, lambda expressions also have the possibility of raising Java runtime exceptions. Although, these exceptions don’t have to be handled explicitly in the same way as checked exceptions, we can still apply the same techniques to process runtime exceptions in a particular way.

In the last sections we created functional interfaces to handle the exceptions, however in this section we will create some static utility methods that follow the same patterns.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package com.stephenenright.tutorials.corejavaexception.lambda;

import com.stephenenright.tutorials.corejavaexception.function.ExceptionHandler;
import java.util.function.Consumer;

public abstract class LambdaExceptionWrappers {
    static <T, E extends Exception> Consumer<T> consumerWrap(Consumer<T> consumer, ExceptionHandler handler) {
        return i -> {
            try {
                consumer.accept(i);
            } catch (Exception e) {
                if(!handler.handle(e)) {
                    throw new RuntimeException(e);
                }
            }
        };
    }
}

In the above code we create a new utility function consumerWrap that wraps a Consumer and applies an ExceptionHandler in the event of an Exception.

Conclusion

We covered approaches on how to handle checked exceptions in lambda expressions. We also looked at an approach on how to handle unchecked / runtime exceptions if we need to process them in specific ways.