The following approaches can be used to handle exceptions in Java lambda

  • Add try-catch block to the lambda statement

  • Extract try-catch block to a wrapper method

  • Create a generic method to wrap the try-catch block

Apart from that, you can throw unchecked exceptions to the lambda outside but can't throw checked exceptions

Let's walk through this article to explore them in more details

Add try-catch to the lambda statement

Use try-catch to handle exceptions in lambda is the most straightforward way to do

Say you use Files.readAllLines to read all lines from a list of file, some of them may not exist which will throw IOException

List.of("test.txt", "test2.txt").forEach(item -> {  
    try {
        Files.readAllLines(Path.of(item));
    } catch (IOException e) {
        System.out.println(e);
        throw new RuntimeException(e);
    }
});

As IOException is a checked exception, you have to convert it to a RuntimeException if you'd like to propagate it to the outside

Extract try-catch block to a wrapper method

Add try-catch block into a lambda can harm the readability. Lambda code should be concise, as it usually is a part of a chaining pipeline

The above try-catch block can be extracted to a separate method

void readFile(String fileName) throws RuntimeException{  
    try {
        Files.readAllLines(Path.of(fileName));
    } catch (IOException e) {
        System.out.println(e);
        throw new RuntimeException(e);
    }
}

The lambda statement now is converted to expression form, concise and clean with a single line only

public void wrapperFunction() {  
    List.of("test.txt", "test2.txt")
        .forEach(item -> readFile(item));
}

Generalize the extracted method

The above approach isn't bad, but you have to extract try-catch block into a separate method each time

As forEach accepts a built-in Consumer functional interface which defined as the following

@FunctionalInterface
public interface Consumer<T> {  
    void accept(T t);
    ...
}

You can create a similar functional interface which throws Exception on the single abstract method and a static method which accepts the custom interface, catch the Exception and rethrows RuntimeException as an alternate

@FunctionalInterface
interface ThrowingConsumer<T, E extends Exception> {  
    void accept(T t) throws E;

    static <T> Consumer<T> wrapper(ThrowingConsumer<T, Exception> t) {
        return arg -> {
            try {
                t.accept(arg);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        };
    }
}

Then put the lambda inside wrapper(ThrowingConsumer)

public void generalWrapperFunction() {  
    List.of("test.txt", "test2.txt")
        .forEach(wrapper(item -> Files.readAllLines(Path.of(item))));
}

Conclusion

In this article, we learned to handle exceptions in Java lambda. You can find the full source code as below