Handle Exceptions Thrown in Java Streams

Learn to handle the checked exceptions thrown from the methods used in Stream operations in Java 8 using standard techniques such as safe method extraction and Optional.

1. Introduction to the Problem

A Java program can stop the normal execution flow in the following case:

  • Checked exception
  • Unchecked exception
  • Error

Java compiler forces us to catch and handle the checked exceptions when we use a method that throws it. These checked exceptions are anticipated, for example, FileNotFoundException can be thrown when we try to read a file from the filesystem.

In a normal Java program, we can use try-catch-finally statements to catch such checked exceptions and handle appropriately. For example, Files.readString() is one method that throws IOException that must be handled.

try {
  Files.readString(path);
}
catch (IOException e) {
  //handle exception
}

When we use such a function in Java Streams, then also we are expected to handle such exceptions, or else the compiler will complain:

List<Path> pathList = List.of(Path.of("..."), Path.of("..."), Path.of("..."));

List<String> fileContents = pathList.stream()
    .map(Files::readString)
    .toList();

//Compiler Error: Unhandled exception: java.io.IOException

2. Using try-catch in Lambda Expression

The most straightforward and obvious solution is to wrap the function in try-catch block as a lambda expression and use it in the Stream operations.

List<String> fileContents = pathList.stream()
    .map(path -> {
      try {
        return Files.readString(path);
      } catch (IOException e) {
        return null;
      }
    })
    .filter(Objects::nonNull)
    .toList();

The above solution works, but it defeats the purpose of Streams, which is the streamlined processing of each element in the Stream in concise syntax. Using try-catch in lambda expressions is an anti-pattern because try-catch dictates what happens to Stream elements rather than the result of the previous operation in the Stream.

3. Using Safe Method Extraction

One possible better solution is to extract the try-catch block in a separate method that handles the exception and returns the default value when such an exception occurs.

public class FileUtils {

	public static String safeReadString(Path path) {
		try {
		  return Files.readString(path);
		} catch (IOException e) {
		  return null;
		}
	}
}

Now we can use this method in the stream operation without any ugliness in the code, and it will have the same effect.

List<String> fileContents = pathList.stream()
    .map(FileUtils::safeReadString)
    .filter(Objects::nonNull)
    .toList();

4. Not Throwing the Exception

This approach is useful if we have control over the sourcecode of the API. If not, we can use the safe extraction technique to wrap the exception-throwing API in a separate method call.

In this technique, as a best practice, we can return Optional instance when we encounter an exception. As a result, this will prevent NullPointerException further down in the call stack and make code robust.

public class FileUtils {

	static Optional<String> readString(Path path) {
    try {
      return Optional.of(Files.readString(path));
    } catch (IOException e) {
      return Optional.empty();
    }
  }
}

List<Optional<String>> fileContentsList = pathList.stream()
    .map(HandleCheckedExceptions::readString)
    .toList();

5. Using FailableStream from Apache Commons Lang

The FailableStream is a reduced, and simplified version of a Stream with failable method signatures. We can use it as follows:

List<String> fileContents = Streams.stream(pathList.stream())
    .map(Files::readString)
    .collect(Collectors.toList());

Do not forget to include the latest version of commons-lang3 library in the project classpath.

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.12.0</version>
</dependency>

6. Conclusion

In this Java Stream tutorial, we learned to handle the checked exceptions thrown from the methods used in intermediate operations in stream processing. We learned to use the inline try-catch block in lambda expressions and safe method extraction. As a bext practice, consider using Optional instead of throwing a null value from the extracted function.

Happy Learning !!

Sourcecode on Github

Comments

Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.