DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
  1. DZone
  2. Coding
  3. Java
  4. Exceptions in Lambdas

Exceptions in Lambdas

Java streams don't play well with checked exceptions. In this article, take a deep dive into how one can manage such problems.

Nicolas Fränkel user avatar by
Nicolas Fränkel
CORE ·
Oct. 20, 22 · Analysis
Like (12)
Save
Tweet
Share
11.70K Views

Join the DZone community and get the full member experience.

Join For Free

Java introduced the concept of checked exceptions. The idea of forcing developers to manage exceptions was revolutionary compared to the earlier approaches.

Nowadays, Java remains the only widespread language to offer checked exceptions. For example, every exception in Kotlin is unchecked.

Even in Java, new features are at odds with checked exceptions: the signature of Java's built-in functional interfaces doesn't use exceptions. It leads to cumbersome code when one integrates legacy code in lambdas. It's evident in Streams.

In this post, I'd like to dive deeper into how one can manage such problems.

The Problem in the Code

Here's a sample code to illustrate the issue:

Java
 
Stream.of("java.lang.String", "ch.frankel.blog.Dummy", "java.util.ArrayList")
      .map(it -> new ForNamer().apply(it))                                     // 1
      .forEach(System.out::println);


  1. Doesn't compile: need to catch the checked ClassNotFoundException

We must add a try/catch block to fix the compilation issue.

Java
 
Stream.of("java.lang.String", "ch.frankel.blog.Dummy", "java.util.ArrayList")
      .map(it -> {
          try {
              return Class.forName(it);
          } catch (ClassNotFoundException e) {
              throw new RuntimeException(e);
          }
      })
      .forEach(System.out::println);


Adding the block defeats the purpose of easy-to-read pipelines.

Encapsulate the Try/Catch Block Into a Class

To get the readability back, we need to refactor the code to introduce a new class. IntelliJ IDEA even suggests a record:

Java
 
var forNamer = new ForNamer();                                                // 1
Stream.of("java.lang.String", "ch.frankel.blog.Dummy", "java.util.ArrayList")
      .map(forNamer::apply)                                                   // 2
      .forEach(System.out::println);

record ForNamer() implements Function<String, Class<?>> {

    @Override
    public Class<?> apply(String string) {
        try {
            return Class.forName(string);
        } catch (ClassNotFoundException e) {
            return null;
        }
    }
}


  1. Create a single record object.
  2. Reuse it.

Trying With Lombok

Project Lombok is a compile-time annotation processor that generates additional bytecode. One uses the proper annotation and gets the result without having to write boilerplate code.

Project Lombok is a Java library that automatically plugs into your editor and build tools, spicing up your Java. Never write another getter or equals method again, with one annotation your class has a fully featured builder, Automate your logging variables, and much more.

- Project Lombok

Lombok offers the @SneakyThrow annotation: it allows one to throw checked exceptions without declaring them in one's method signature. Yet, it doesn't work for an existing API at the moment.

If you're a Lombok user, note that there's an opened GitHub issue with the status parked.

Commons Lang to the Rescue

Apache Commons Lang is an age-old project. It was widespread at the time, as it offered utilities that could have been part of the Java API but weren't. It was a much better alternative than reinventing your DateUtils and StringUtils in every project. While researching this post, I discovered it is still regularly maintained with great APIs. One of them is the Failable API.

The API consists of two parts:

  1. A wrapper around a Stream
  2. Pipeline methods whose signature accepts exceptions

Here's a small excerpt:

Excerpt: Apache Commons Lang

The code finally becomes what we expected from the beginning:

Java
 
Stream<String> stream = Stream.of("java.lang.String", "ch.frankel.blog.Dummy", "java.util.ArrayList");
Failable.stream(stream)
        .map(Class::forName)                                                  // 1
        .forEach(System.out::println);


Fixing Compile-Time Errors Is Not Enough

The previous code throws a ClassNotFoundException wrapped in an UndeclaredThrowableException at runtime. We satisfied the compiler, but we have no way to specify the expected behavior:

  • Throw at the first exception
  • Discard exceptions
  • Aggregate both classes and exceptions so we can act upon them at the final stage of the pipeline
  • Something else

To achieve this, we can leverage the power of Vavr. Vavr is a library that brings the power of Functional Programming to the Java language:

Vavr core is a functional library for Java. It helps to reduce the amount of code and to increase the robustness. A first step towards functional programming is to start thinking in immutable values. Vavr provides immutable collections and the necessary functions and control structures to operate on these values. The results are beautiful and just work.

- Vavr

Imagine that we want a pipeline that collects both exceptions and classes. Here's an excerpt of the API that describes several building blocks:

Excerpt of the API that describes several building blocks

It translates into the following code:

Java
 
Stream.of("java.lang.String", "ch.frankel.blog.Dummy", "java.util.ArrayList")
      .map(CheckedFunction1.liftTry(Class::forName))                          // 1
      .map(Try::toEither)                                                     // 2
      .forEach(e -> {
          if (e.isLeft()) {                                                   // 3
              System.out.println("not found:" + e.getLeft().getMessage());
          } else {
              System.out.println("class:" + e.get().getName());
          }
      });


  1. Wrap the call into a Vavr Try. 
  2. Transform the Try into an Either to keep the exception. If we had not been interested, we could have used an Optional instead.
  3. Act depending on whether the Either contains an exception, left, or the expected result, right.

So far, we have stayed in the world of Java Streams. It works as expected until the forEach, which doesn't look "nice."

Vavr does provide its own Stream class, which mimics the Java Stream API and adds additional features. Let's use it to rewrite the pipeline:

Java
 
var result = Stream.of("java.lang.String", "ch.frankel.blog.Dummy", "java.util.ArrayList")
        .map(CheckedFunction1.liftTry(Class::forName))
        .map(Try::toEither)
        .partition(Either::isLeft)                                              // 1
        .map1(left -> left.map(Either::getLeft))                                // 2
        .map2(right -> right.map(Either::get));                                 // 3

result._1().forEach(it -> System.out.println("not found: " + it.getMessage())); // 4
result._2().forEach(it -> System.out.println("class: " + it.getName()));        // 4


  1. Partition the Stream of Either in a tuple of two Stream. 
  2. Flatten the left stream from a Stream of Either to a Stream of Throwable. 
  3. Flatten the right stream from a Stream of Either to a Stream of Class. 
  4. Do whatever we want.

Conclusion

Java's initial design made plenty of use of checked exceptions. The evolution of programming languages proved that it was not a good idea.

Java streams don't play well with checked exceptions. The code necessary to integrate the latter into the former doesn't look good. To recover the readability we expect of streams, we can rely on Apache Commons Lang.

The compilation represents only a tiny fraction of the issue. We generally want to act upon the exceptions, not stop the pipeline or ignore exceptions. In this case, we can leverage the Vavr library, which offers an even more functional approach.

You can find the source code for this post on GitHub.

To Go Further

  • Exceptions in Java 8 Lambda Expressions
  • How to Handle Checked Exceptions With Lambda Expression
  • "Stackoverflow: Java 8 Lambda function that throws exception?"
  • Failable JavaDoc
  • Vavr
  • Exceptions in Lambda Expression Using Vavr
  • Java Streams vs Vavr Streams
API Boilerplate code Functional programming Stack overflow Virtual screening intellij Java (programming language) Pipeline (software) Stream (computing) Data Types

Published at DZone with permission of Nicolas Fränkel, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • 7 Awesome Libraries for Java Unit and Integration Testing
  • Kubernetes vs Docker: Differences Explained
  • Easy Smart Contract Debugging With Truffle’s Console.log
  • What Is Policy-as-Code? An Introduction to Open Policy Agent

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: