avatarViraj Shetty

Summary

This article discusses the use of Java Optional class for functional and fluent programming.

Abstract

The article explains the use of Java Optional class for functional and fluent programming. It covers the basics of Java Optional class and its methods like filter(), map(), flatMap(), and stream(). The article also provides examples of how to use these methods in a functional and fluent style. The author recommends using Java Optional class for writing libraries that will be consumed by many developers.

Opinions

  • The author recommends using Java Optional class for writing libraries that will be consumed by many developers.
  • The author suggests that using Java Optional class can make code more concise, readable, and robust.
  • The author encourages developers to consider using the methods provided by Java Optional class for functional and fluent programming.

Use Java Optional Fluently

The Optional class contains multiple public methods like filter(), map() and flatMap() which are useful for functional and fluent programming. In this article, we will explore the functional and fluent style of programming using Java Optional.

Photo by Clément Hélardot on Unsplash

Java Optional Basics

If you are not aware of the Java Optional class, I would recommend taking some time to read my Medium article on Java Optional basics and then come back to this article.

Let’s take a simple example of a User Record which has the following data attributes — user id, name, address and whether the user is the CEO. Here’s an example of how Optional class can be used in our code.

// User Record
record User(String uid, String name, 
            Address address, boolean ceo){

   public static final User DEFAULT_USER 
     = new User("0", "Dummy", null, false);

}

// Address record
record Address(String line, String zip) {
}

// A User Factory class to create User objects
public class Users {

  public static Optional<User> getUser(String uid) {

      User user = null;

      // logic to set user variable

      return Optional.ofNullable(user);
  }
}

// Calling the getUser method which returns Optional
Optional<User> oUser = Users.getUser(uid);
if (oUser.isPresent()) {

   // process User. It's not empty
   String name = oUser.get().name();
}

In the example you see that the Users class has a method called getUser(..) which returns an Optional of type User — indicating to the caller that the returned value of User can be null. Note that the Optional value itself must never be null.

The caller of the Users.getUser(..) will then use the isPresent() method of Optional to check if a valid value is available and if so will process the user. The end benefit of using the Optional class is that the caller is forced to “think” about handling the null return value. This prevents a Null Pointer Exception and makes the code robust. In spirit, the idea is similar to throwing a checked Exception which also forces the caller to handle the Exception.

The recommendation is to return Java Optional when writing libraries which will be consumed by many developers. It’s definitely not the intention to design every method call to return an Optional where null can be returned. As an example, libraries like Java Streams API return Java Optional in many places and so do Spring Boot libraries.

Most developers tend to use just these minimal features of Java Optional class (though useful). However — hidden among the various methods in the Optional class, are a number of functional style methods for consuming Java Optional, which can be extremely useful in making your code short and more readable.

Let’s start by looking at how to consume the Optional object in a functional style.

Functional Style with Optional

Optional class provides a method called ifPresent(..) where the programmer can provide a Lambda function (a Consumer) to handle the contained object. Here’s an example.

// Pass a Lamba object to handle the user
Optional<User> oUser = Users.getUser(uid);
oUser.ifPresent(user -> {
    
    // process User
    String name = user.name();
    System.out.println(name);
});

// Signature of "Optional.ifPresent"
void ifPresent(Consumer<? super T> action)

You can see here that the code is calling the ifPresent(..) and passing the Consumer to handle the contained user object. However, if variable oUser is an empty Optional — the Consumer will not be invoked. In other words, it’s a functional style of using the classic Java “if” statement. The method signature of ifPresent is also shown which takes a Consumer interface.

As part of the typical Lambda programming, we can pass a Method Reference instead of a Lambda. This makes the code more readable.

// Functional Style using Method Reference
Optional<User> oUser = Users.getUser(uid);
oUser.ifPresent(Main::handleUser);


// handleUser static method in Main.java
public static void handleUser(User u) {

    // process User
    String name = u.name();
    System.out.println(name);
}

Here you can see that the method reference Main::handleUser is passed as a parameter, so that the handler is isolated in a separate method.

What if the caller wants to use the classic if-then-else statement ?

That’s possible by using the method ifPresentOrElse(..) method. Here’s an example

// Functional Style if-then-else. 
// handleUser is called if value present
// Otherwise AppException is thrown
oUser.ifPresentOrElse(Main::handleUser, () -> {
    throw new AppException("Some Error");
});

// Signature of "Optional.ifPresentOrElse"
void ifPresentOrElse(
  Consumer<? super T> action, Runnable emptyAction)

If a user is available then the first parameter which is a Method Reference is invoked. Otherwise the second parameter which is a Lambda function representing a Runnable is called. This method is the functional equivalent of the classic Java if-then-else as applied to the Optional object.

The Optional class also has a number of orXXX(..) methods which are useful to express what needs to be done when the Optional is Empty.

orXXX(..) methods

Here is an example of using the orElse() method.

// Return default user if Optional is empty
User user = oUser.orElse(User.DEFAULT_USER);

We use the orElse(..) method where the user variable is assigned the contained value in oUser if it is available; otherwise the DEFAULT_USER is assigned to it. This method is a functional equivalent to the Elvis Operator in other languages like Groovy.

What if the default user is not as simple as a constant but needs to be retrieved by some piece of logic ?

Optional class has you covered. You can use the orElseGet(..) method.

// Return default user by calling Supplier
User user = oUser.orElseGet(() -> {
    // default logic to get the user
    return defUser;
});

This is similar to orElse(..) method with the exception that when the optional oUser is empty, the Supplier Lambda function is called which uses some internal logic to retrieve a default user. There is also a similar or(..) method in Optional which also uses a Supplier but the supplier supplies an “Optional of User” instead of the user.

What if you simply want to throw some exception if Optional is empty ?

// Extract user if available 
// Otherwise throw NoSuchElementException
User user = oUser.orElseThrow();

We use the orElseThrow() method to return the user if available; otherwise simply throw the default NoSuchElementException exception.

What if you want to throw a customized Exception if Optional is empty ?

// Extract user if available 
// Otherwise throw AppException
User user = oUser.orElseThrow(
        () -> new AppException("Error"));

We use an overloaded orElseThrow(..) method where we pass a Lambda (Supplier of Exception) expression as the parameter.

It gets even better from here. There are a bunch of methods in Optional which are useful for building a chain of method calls using a fluent style.

Fluent Style with Optional

Fluent style of programming uses method chaining to express and implement a particular use case. We see this kind of programming when using Java streams. The pseudo-code for such a style looks something like the following and it’s easy to recognize this style.

ReturnType output = object
                       .method1(..) 
                       .method2(..)
                       ...
                       .methodN(..); 

There are a few methods in Optional filter(..), map(..), flatMap(..) which can be used for fluent style development. These methods are similar to the equivalent streams methods but operate on one value. Let’s take a look at each one of them.

map() method

The map() method translates the Optional of one type to an Optional of another type. Consider the following code which extracts the name of the user in the imperative style (typical if statement).

// Getting the name of the user
String name = null;
Optional<User> oUser= Users.getUser(uid);
if (oUser.isPresent()) {
    name = oUser.get().name();
}

In the code above we are getting the user object for the user id uid and then extracting the name of that user. The variable name can be either null or a valid String. In the Optional world — the perfect way to represent it is Optional<String>. So, basically our real requirement is to convert from Optional<User> to an Optional<String> (name). The Optional.map() does that for us as we show below by using a fluent style.

// Optional<User> -> Optional<String>
Optional<String> name
        = Users.getUser(uid)
               .map(User::name);

As you see, we call the map() method on the optional. The map() method takes in a “function which takes a User object and returns the user name”. The Method reference User::name is passed to represent that function. So, here we used a much more fluent API to extract the name and the end result is pleasing to the eye.

filter() method

Let’s now change the requirement a bit to say that we should only get a valid name if the user is the CEO of the company. Remember, we have a flag in the User object which represents whether the user is a CEO or not. Using the imperative style, the method code would look something like the following.

// User -> name of CEO
String name = null;
Optional<User> oUser = Users.getUser(uid);
if (oUser.isPresent()) {
    User user = oUser.get();
    if (user.ceo()) {
        name = user.name();
    }
}

Here, we set the name field only if the user is the CEO. So we need an additional check to test for that by using the method user.ceo() which will return True or False. The problem with this imperative style programming is that there are way too many cascading “if” statements — leading to undesirable code.

We can replace the above code by a nice fluent combination of filter() and map() as follows.

// Use filter to only allow CEO
Optional<String> name
        = Users.getUser(uid)
               .filter(User::ceo)
               .map(User::name);

Here you see that after getting the Optional user object, filter(..) method will check if the user is a CEO. The filter() method takes a Predicate and if at runtime this predicate returns true then the filter returns the Optional user object again. Otherwise it will return an empty Optional. The next step is to call the map() method which will return an Optional string representing the user name.

You can see that there is a distinct improvement in the readability of the code using the Fluent style of development. The fluent style of programming does require some practice to get used to.

flatMap() method

Let’s now talk about the flatMap() method — which is a variation of the map() method. To appreciate this method, let’s modify our use case a bit. Let’s say we would like to extract the zip code of the user instead of the username. So, our first attempt in an imperative world would be as follows.

// User -> zip
String zip = null;
Optional<User> oUser = Users.getUser(uid);
if (oUser.isPresent()) {
    User user = oUser.get();
    Address address = user.address();
    if (address != null) {
        zip = address.zip();
    }
}

Though this code works, it’s less than ideal for obvious reasons. This cascading if statements can be eliminated elegantly by using the map() method— as we see below.

Optional<String> zip
        = Users.getUser(uid)
               .map(User::address)
               .map(Address::zip);

But what if I change the design of both User and Address as follows by adding two methods — getAddress() and getZip() which returns the Optional types.

// Record User
record User(String uid, String name, 
            Address address, boolean ceo) {

    public static final User DEFAULT_USER 
       = new User("0", "Dummy", null, false);

    public Optional<Address> getAddress() {
        return Optional.ofNullable(address);
    }
}

// Record Address
record Address(String line, String zip) {

    public Optional<String> getZip() {
        return Optional.ofNullable(zip);
    }
}

If a “function which returns an Optional type” is passed to a map method, then the returned result will be an “Optional<Optional<type>>”. The use of flatMap(..) will flatten the cascading optionals to Optional<type> and make it easier to work with it.

So, if we were to use the getAddress() and getZip() methods (instead of address() and zip() methods) in our fluent pipeline, we would end up with the following.

Optional<String> zip
        = Users.getUser(uid)
               .flatMap(User::getAddress)
               .flatMap(Address::getZip);

Notice the use of flatMap(..) methods. Method Reference User::getAddress returns an Optional<Address> and so flatMap(..) would simply return Optional<Address> whereas map(..) would have returned Optional<Optional<Address>>.

Bottom line — flatMap(..) should be used when the function passed to it returns an Optional; otherwise use map(..)

stream() method

Lastly, Optional also has a stream() method which creates a stream of 0 or 1 values. If the Optional is empty then it will return a stream of 0 elements and if the Optional has a valid value then it will return a stream of 1 element. Any of the available stream methods can then be used to manipulate the stream in a functional style.

Feel free to explore the Javadoc for the Optional class using the link below.

Also check out my YouTube Channel at https://www.youtube.com/@viraj_shetty and subscribe for quality software content.

Also Check out my discounted Udemy Courses at https://www.mudraservices.com/courses/

Summary

Though Optional can be used in its simplest form, it also has a number of methods which helps the developers in writing fluent style code. This can lead to more concise, readable and robust code in a lot of use cases.

Consider using them !

Java
Java Optional
Fluent Design
Functional Programming
Recommended from ReadMedium