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 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

Enterprise AI Trend Report: Gain insights on ethical AI, MLOps, generative AI, large language models, and much more.

2024 Cloud survey: Share your insights on microservices, containers, K8s, CI/CD, and DevOps (+ enter a $750 raffle!) for our Trend Reports.

PostgreSQL: Learn about the open-source RDBMS' advanced capabilities, core components, common commands and functions, and general DBA tasks.

AI Automation Essentials. Check out the latest Refcard on all things AI automation, including model training, data security, and more.

Related

  • How To Get Started With New Pattern Matching in Java 21
  • All Things Java 8 [Tutorials]
  • Test Parameterization With JUnit 5.7: A Deep Dive Into @EnumSource
  • Mastering Exception Handling in Java Lambda Expressions

Trending

  • Getting Started With NCache Java Edition (Using Docker)
  • Data Processing in GCP With Apache Airflow and BigQuery
  • Being a Backend Developer Today Feels Harder Than 20 Years Ago
  • Implement RAG Using Weaviate, LangChain4j, and LocalAI
  1. DZone
  2. Coding
  3. Java
  4. The Beauty of Java Optional and Either

The Beauty of Java Optional and Either

Many Java developers often overlook its functional programming capabilities. Learn how to chain Optional and Either to write concise and beautiful code.

By 
Sanjay Patel user avatar
Sanjay Patel
DZone Core CORE ·
Feb. 15, 23 · Tutorial
Like (8)
Save
Tweet
Share
20.5K Views

Join the DZone community and get the full member experience.

Join For Free

Many of us Java developers — particularly beginners — often overlook its functional programming capabilities. In this article, we'd look at how to chain Optional and Either to write concise and beautiful code.

To illustrate, let's assume we have a bank where a user could have zero or more accounts. The entities look as below:

Java
 
record User(
   int id,
   String name
) {}

record Account(
    int id,
    User user // a user can have zero or more account
) {}


For fetching the entities, the repositories look as below:

Java
 
interface UserRepository {
    Optional<User> findById(int userId);
}

interface AccountRepository {
    Optional<Account> findAnAccountOfUser(User user); // given a user, find its any account if it has one
}


Now, get ready for a couple of assignments!

First Assignment

Let's code a method Optional<Account> findAnAccountByUserId(Integer userId) that would:

  1. Given a userId, return any one account of the user, if there's one
  2. If either there's no user with the given id, or there's no account of the user, return an empty Optional

A novice solution could be as follows:

Java
 
    public Optional<Account> findAccountByUserId(int userId) {

        Optional<User> possibleUser = userRepository.findById(userId);
        if (possibleUser.isEmpty())
            return Optional.empty();
        var user = possibleUser.orElseThrow();
        return accountRepository.findAnAccountOfUser(user);
    }


But, then the map method of Optional strikes our mind! Instead of checking for possibleUser.isEmpty(), we could just map the user, if present, to an account:

Java
 
    public Optional<Account> findAccountByUserId(int userId) {
        return userRepository
                .findById(userId)
                .map(accountRepository::findAnAccountOfUser);
    }


We land up with a compilation error because accountRepository.findAnAccountOfUser(user) returns an Optional<Account>, whereas the map method above needs an Account. For this exact use case, Optional provides a flatMap method, which flattens nested Optionals. So, changing map to flatMap would work.

Java
 
    public Optional<Account> findAccountByUserId(int userId) {
        return userRepository
                .findById(userId)
                .flatMap(accountRepository::findAnAccountOfUser);
    }


Cool! Get ready for a more complex assignment.

Second Assignment

When a user/account is not found, instead of returning an empty optional, how about indicating exactly what was not found: user or account?

We could approach this problem in a few ways:

Throw Exceptions

We could define some custom exceptions, viz.  UserNotFoundException and AccountNotFoundException, and throw those:

Java
 
    public Account findAccountByUserIdX(int userId) {
        var user = userRepository.findById(userId).orElseThrow(UserNotFoundException::new);
        return accountRepository.findAnAccountOfUser(user).orElseThrow(AccountNotFoundException::new);
    }


However, using exceptions for expected cases is considered an anti-pattern: Googling will get you numerous articles about the subject. So let's avoid that.

Use a Result Interface

Another approach would be returning a custom Result object instead of returning Optional; i.e., Result findAnAccountByUserId(Integer userId). The result would be an interface that would be implemented by custom error classes, as well as Account and User.

Use Either

A third approach, which I find simpler,  is to return an Either instead of Optional. Whereas an Optional holds zero or one value, an Either holds either of two values. It's typically used to hold either an error or a success value.

Unlike Optional, you don't get a Java Either implementation out of the box, but there are quite a few libraries. I prefer using jbock-java/either because it's lightweight and simple.

So, let's first define the error interface and classes:

Java
 
    interface Error {}
    record UserNotFound() implements Error {}
    record AccountNotFound() implements Error {}


Let's now attempt coding:

Java
 
public Either<Error, Account> findAccountByUserId(int userId) {
  ...
}


Did you notice above that we used Error as the left generic parameter, whereas Account as the right one? That wasn't accidental: the convention when using Either is that the left is used for errors whereas the right is used for success values. 

Either has a similar functional API like Optional. For example, we have map and flatMap for mapping the success values, whereas we have mapLeft and flatMapLeft for mapping the errors. We also have utility methods like Either.left(value) and Either.right(value) to create Either objects. Do have a look at its API. It has many interesting features for functional programming.

So, continuing our journey, we could first create an either having the user or error as below:

Java
 
public Either<Error, Account> findAccountByUserId(int userId) {
    var eitherErrorOrUser = userRepository
                .findById(userId)
                .map(Either::<Error, User>right)
                .orElseGet(() -> Either.left(new UserNotFound()))
    ...
}


Lines 4 and 5 above convert an Optional<User> to Either<UserNotFound, User>. Because converting an Optional to an Either would be a common use case, let's code a utility method for it:

Java
 
public class EitherUtils {

  public static <L, R>  Either<L, R> of(Optional<R> possibleValue, Supplier<L> errorSupplier) {
        return possibleValue.map(Either::<L, R>right).orElseGet(() -> Either.<L, R>left(errorSupplier.get()));
    }
}


It takes the optional and an errorSupplier. The errorSupplier is used for composing the error if the optional is empty.

Using it, our code now looks like this:

Java
 
public Either<Error, Account> findAccountByUserId(int userId) {
    var eitherErrorOrUser = EitherUtils
        .<Error, User>of(userRepository.findById(userId), UserNotFound::new)
    ...
}


Next, as above, eitherErrorOrUser could be mapped for an account in a similar way. The complete solution would then look like this:

Java
 
    public Either<Error, Account> findAccountByUserId(int userId) {
        return EitherUtils
                .<Error, User>of(userRepository.findById(userId), UserNotFound::new)
                .flatMap(user -> of(accountRepository.findAnAccountOfUser(user), AccountNotFound::new));
    }


Looks cute, doesn't it?

Many elegant uses of Optional and Either could be found in this GitHub repo, where we avoid the anti-pattern of using exceptions for validation/business rules.

Video

Summary

Consider using functional programming capabilities of Java wherever possible, and make your code cute and concise!

Functional programming Java (programming language) Data Types

Opinions expressed by DZone contributors are their own.

Related

  • How To Get Started With New Pattern Matching in Java 21
  • All Things Java 8 [Tutorials]
  • Test Parameterization With JUnit 5.7: A Deep Dive Into @EnumSource
  • Mastering Exception Handling in Java Lambda Expressions

Partner Resources


Comments

ABOUT US

  • About DZone
  • Send 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: