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
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

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

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Related

  • Thread-Safety Pitfalls in XML Processing
  • Java Stream API: 3 Things Every Developer Should Know About
  • Understanding Lazy Evaluation in Java Streams
  • Exploring TakeWhile and DropWhile Functions in Java

Trending

  • How to Ensure Cross-Time Zone Data Integrity and Consistency in Global Data Pipelines
  • Understanding IEEE 802.11(Wi-Fi) Encryption and Authentication: Write Your Own Custom Packet Sniffer
  • Unit Testing Large Codebases: Principles, Practices, and C++ Examples
  • How Kubernetes Cluster Sizing Affects Performance and Cost Efficiency in Cloud Deployments
  1. DZone
  2. Coding
  3. Java
  4. Exception Handling in Java Streams

Exception Handling in Java Streams

When you want to use a method that throws a checkedException, you have to do something extra if you want to call it in a lambda.

By 
Brian Vermeer user avatar
Brian Vermeer
·
Dec. 21, 18 · Tutorial
Likes (118)
Comment
Save
Tweet
Share
316.5K Views

Join the DZone community and get the full member experience.

Join For Free

The Stream API and lambda’s where a big improvement in Java since version 8. From that point on, we could work with a more functional syntax style. Now, after a few years of working with these code constructions, one of the bigger issues that remain is how to deal with checked exceptions inside a lambda.

As you all probably know, it is not possible to call a method that throws a checked exception from a lambda directly. In some way, we need to catch the exception to make the code compile. Naturally, we can do a simple try-catch inside the lambda and wrap the exception into a RuntimeException, as shown in the first example, but I think we can all agree that this is not the best way to go.

myList.stream()
  .map(item -> {
    try {
      return doSomething(item);
    } catch (MyException e) {
      throw new RuntimeException(e);
    }
  })
  .forEach(System.out::println);


Most of us are aware that block lambdas are clunky and less readable. They should be avoided as much as possible, in my opinion. If we need to do more than a single line, we can extract the function body into a separate method and simply call the new method. A better and more readable way to solve this problem is to wrap the call in a plain old method that does the try-catch and call that method from within your lambda. 

myList.stream()
  .map(this::trySomething)
  .forEach(System.out::println);


private Item trySomething(Item item) {
  try {
    return doSomething(item);
  } catch (MyException e) {
    throw new RuntimeException(e);
  }
}


This solution is at least a bit more readable and we do separate our concerns. If you really want to catch the exception and do something specific and not simply wrap the exception into a RuntimeException, this can be a possible and readable solution for you.


The Complete Java Developer Course.*

*Affiliate link. See Terms of Use.

RuntimeException

In many cases, you will see that people use these kinds of solutions to repack the exception into a RuntimeException or a more specific implementation of an unchecked Exception. By doing so, the method can be called inside a lambda and be used in higher order functions.

I can relate a bit to this practice because I personally do not see much value in checked exceptions in general, but that is a whole other discussion that I am not going to start here. If you want to wrap every call in a lambda that has a checked into a RuntimeException, you will see that you repeat the same pattern. To avoid rewriting the same code over and over again, why not abstract it into a utility function? This way, you only have to write it once and call it every time you need it.

To do so, you first need to write your own version of the functional interface for a function. Only this time, you need to define that the function may throw an exception.

@FunctionalInterface
public interface CheckedFunction<T,R> {
    R apply(T t) throws Exception;
}


Now, you are ready to write your own general utility function that accepts a CheckedFunction as you just described in the interface.You can handle the try-catch in this utility function and wrap the original exception into a RuntimeException (or some other unchecked variant). I know that we now end up with an ugly block lambda here and you could abstract the body from this. Choose for yourself if that is worth the effort for this single utility. 

public static <T,R> Function<T,R> wrap(CheckedFunction<T,R> checkedFunction) {
  return t -> {
    try {
      return checkedFunction.apply(t);
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  };
}


With a simple static import, you can now wrap the lambda that may throw an exception with your brand new utility function. From this point on, everything will work again. 

myList.stream()
       .map(wrap(item -> doSomething(item)))
       .forEach(System.out::println);


The only problem left is that when an exception occurs, the processing of the your stream stops immediately. If that is no problem for you, then go for it. I can imagine, however, that direct termination is not ideal in many situations.

Either

When working with streams, we probably don't want to stop processing the stream if an exception occurs. If your stream contains a very large amount of items that need to be processed, do you want that stream to terminate when for instance the second item throws an exception? Probably not.

Let's turn our way of thinking around. Why not consider "the exceptional situation" just as much as a possible result as we would for a "successful" result. Let's consider it both as data, continuing to process the stream, and decide afterward what to do with it. We can do that, but to make it possible, we need to introduce a new type — the Either type.

The Either type is a common type in functional languages and not (yet) part of Java. Similar to the Optional type in Java, an Either is a generic wrapper with two possibilities. It can either be a Left or a Right but never both. Both left and right can be of any types. For instance, if we have an Either value, this value can either hold something of type String or of type Integer, Either<String,Integer>.

If we use this principle for exception handling, we can say that our Either  type holds either an  Exception or a value. By convenience, normally, the left is the Exception and the right is the successful value. You can remember this by thinking of the right as not only the right-hand side but also as a synonym for “good,” “ok,” etc.

Below, you will see a basic implementation of the Either type. In this case, I used the  Optional type when we try to get the left or the right because we:

public class Either<L, R> {

    private final L left;
    private final R right;

    private Either(L left, R right) {
        this.left = left;
        this.right = right;
    }

    public static <L,R> Either<L,R> Left( L value) {
        return new Either(value, null);
    }

    public static <L,R> Either<L,R> Right( R value) {
        return new Either(null, value);
    }

    public Optional<L> getLeft() {
        return Optional.ofNullable(left);
    }

    public Optional<R> getRight() {
        return Optional.ofNullable(right);
    }

    public boolean isLeft() {
        return left != null;
    }

    public boolean isRight() {
        return right != null;
    }

    public <T> Optional<T> mapLeft(Function<? super L, T> mapper) {
        if (isLeft()) {
            return Optional.of(mapper.apply(left));
        }
        return Optional.empty();
    }

    public <T> Optional<T> mapRight(Function<? super R, T> mapper) {
        if (isRight()) {
            return Optional.of(mapper.apply(right));
        }
        return Optional.empty();
    }

    public String toString() {
        if (isLeft()) {
            return "Left(" + left +")";
        }
        return "Right(" + right +")";
    }
}


You can now make your own functions return an Either instead of throwing an  Exception. But that doesn't help you if you want to use existing methods that throw a checked  Exception inside a lambda right? Therefore, we have to add a tiny utility function to the Either type I described above.

public static <T,R> Function<T, Either> lift(CheckedFunction<T,R> function) {
  return t -> {
    try {
      return Either.Right(function.apply(t));
    } catch (Exception ex) {
      return Either.Left(ex);
    }
  };
}


By adding this static lift method to the Either, we can now simply "lift" a function that throws a checked exception and let it return an Either. If we take the original problem, we now end up with a Stream of Eithers instead of a possible RuntimeException that may blow up my entire Stream.

myList.stream()
       .map(Either.lift(item -> doSomething(item)))
       .forEach(System.out::println);


This simply means that we have taken back control. By using the filter function in the Stream APU, we can simply filter out the left instances and, for example, log them. You can also filter the right instances and simply ignore the exceptional cases. Either way, you are back in control again and your stream will not terminate instantly when a possible RuntimeException occurs.

Because Either is a generic wrapper, it can be used for any type, not just for exception handling. This gives us the opportunity to do more than just wrapping the Exception into the left part of an Either. The issue we now might have is that if the  Either only holds the wrapped exception, and we cannot do a retry because we lost the original value. By using the ability of the Either to hold anything, we can store both the exception and the value inside a left. To do so, we simply make a second static lift function like this.

public static <T,R> Function<T, Either> liftWithValue(CheckedFunction<T,R> function) {
  return t -> {
    try {
      return Either.Right(function.apply(t));
    } catch (Exception ex) {
      return Either.Left(Pair.of(ex,t));
    }
  };
}


You see that in this liftWithValue function within the Pair type is used to pair both the exception and the original value into the left of an Either.  Now, we have all the information we possibly need if something goes wrong, instead of only having the Exception. 

The Pair type used here is another generic type that can be found in the Apache Commons lang library, or you can simply implement your own. Anyway, it is just a type that can hold two values.

public class Pair<F,S> {
    public final F fst;
    public final S snd;

    private Pair(F fst, S snd) {
        this.fst = fst;
        this.snd = snd;
    }

    public static <F,S> Pair<F,S> of(F fst, S snd) {
        return new Pair<>(fst,snd);
    }
}


With the use of the liftWithValue, you now have all the flexibility and control to use methods that may throw an  Exception inside a lambda. When the  Either is a right, we know that the function was applied correctly and we can extract the result. If, on the other hand, the Either is a left, we know something went wrong and we can extract both the Exception and the original value, so we can proceed as we like. By using the Either type instead of wrapping the checked  Exception into a  RuntimeException, we prevent the Stream from terminating halfway.

Try

People that may have worked with for instance Scala may use the  Try instead of the Either for exception handling. The  Try type is something that is very similar to the  Either type. It has, again, two cases: “success” or “failure.” The failure can only hold the type Exception, while the success can hold anything type you want. So, the Try is nothing more than a specific implementation of the  Either where the left type (the failure) is fixed to type Exception.

public class Try<Exception, R> {

    private final Exception failure;
    private final R succes;

    public Try(Exception failure, R succes) {
        this.failure = failure;
        this.succes = succes;
    }
}


Some people are convinced that it is easier to use, but I think that because we can only hold the Exception itself in the failure part, we have the same problem as explained in the first part of the Either section. I personally like the flexibility of the Either type more. Anyway, in both cases, if you use the Try or the Either, you solve the initial problem of exception handling and do not let your stream terminate because of a RuntimeException.

Libraries

Both the Either and the  Try are very easy to implement yourself. On the other hand, you can also take a look at functional libraries that are available. For instance, VAVR (formerly known as Javaslang) does have implementations for both types and helper functions available. I do advise you to take a look at it because it holds a lot more than only these two types. However, you have ask yourself the question of whether you want this large library as a dependency just for exception handling when you can implement it yourself with just a few lines of code.

Conclusion

When you want to use a method that throws a  checkedException, you have to do something extra if you want to call it in a lambda. Wrapping it into a RuntimeException can be a solution to make it work. If you prefer to use this method, I urge you to create a simple wrapper tool and reuse it, so you are not bothered by the try/catch every time. 

If you want to have more control, you can use the Either or  Try types to wrap the outcome of the function, so you can handle it as a piece of data. The stream will not terminate when a RuntimeException is thrown and you have the liberty to handle the data inside your stream as you please.


If you enjoyed this article and want to learn more about Java Streams, check out this collection of tutorials and articles on all things Java Streams.

Stream (computing) Java (programming language)

Opinions expressed by DZone contributors are their own.

Related

  • Thread-Safety Pitfalls in XML Processing
  • Java Stream API: 3 Things Every Developer Should Know About
  • Understanding Lazy Evaluation in Java Streams
  • Exploring TakeWhile and DropWhile Functions in Java

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

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

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!