java 8 Optional

Java 8 Optional

In this post, we will talk about Java 8 Optional class introduced in Java 8. As a Java developer, You must have faced NullPointerException at least (I have faced it countless time 🙂 ).NullPointerExceptions are a pain but the truth is you can not avoid it but can add certain checks to make sure we are ready for it when it happens.

A common (bad) practice is to return the null reference to show the absence of an object and which can cause NullPointerException. They introduced The Optional class in Java8 to handle optional values instead of a null reference.

1.  Introduction

To explain, Java 8 Optional feature, consider the following structure for a Car having a build in map (navigation) feature.

Car

public class Car {

    private GoogleMaps map;

    public GoogleMaps getMap() {
        return map;
    }

    public void setMap(GoogleMaps map) {
        this.map = map;
    }
}

Map

public class GoogleMaps {

    private String mapDetails;

    public String getMapDetails() {
        return mapDetails;
    }

    public void setMapDetails(String mapDetails) {
        this.mapDetails = mapDetails;
    }
}

Enable Map

Car car = new Car();
car.getMap().getMapDetails();

Above code looks good but as we know many cars (even a new one) don’t have the build in map (navigation) system, so getMapDetails() will throw NullPointerException for all those cars which do not have a built-in navigation system.

As a standard practice is to return a null reference to show the absence of an object, for our case getMap() will return a null reference to show that Car does not have build in a navigation system. Let’s use the Optional class to enrich the semantics of the above model

public class Car {

    private Option<GoogleMaps> map;

    public Option<GoogleMaps> getMap() {
        return map;
    }

    public void setMap(Option<GoogleMaps> map) {
        this.map = map;
    }
}

Based on the above change, Car refers to Optional<GoogleMap> which show that GoogleMap is an optional feature for a car. API user of your domain model can easily understand what to expect out from the API. How do we handle this use case?

2. Use Defensive Check to Handle Null 

To handle null pointer reference and to avoid NullPointerException, we will use standard null reference check in our code

public class OptionalTest {

    public static void main(String[] args) {

        Car car = new Car();
        car.getMap().getMapDetails();
    }

    public static String getMapDetails(final Car car) {
        if (car != null) {
            GoogleMaps map = car.getMap();
            if (map != null) {
                return map.getMapDetails();
            }
        }

        return "Unavailable";
    }
}

One of the major drawbacks of the above code is that it will become very unreadable because of nested check. All these checks are happening only to avoid NullPointerException and not adding anything to the business logic. I do not want to get into more details about the other drawbacks of the above code (what if you miss checking the null pointer for any other property).

To summarise, returning a null reference to show non-existence of an object or value is not a great approach and definitely, need other good alternative.

 

3. Java Optional Class

To handle the absence of an object or value in a more graceful way than returning a null reference java.util.Optional<T>was introduced as a new feature in Java 8. The optional class provides a way to design better API in which consumer of the API has a better understanding what can be expected back from the API.

java.util.Optional<T> is like a container, either container has an object or it is empty (cannot be null).In another term think of Optional as a wrapper, if the value is present, the Optional class will wrap that value else it will be empty.

 

4. How to Create Optional Class

There are multiple ways to create Optional objects. We can use empty() method to create an empty Optional object

Optional With Empty Value
Optional<GoogleMaps> emptyOptional = Optional.empty();

Optional.empty() is a static factory method which returns Singleton object of the optional class (see java source for more details).

Optional With  Not Null Value
GoogleMaps googleMaps = new GoogleMaps();
Optional<GoogleMaps> optionalMap= Optional.of(googleMaps);

Keep in mind that argument passed to Optional.of() can not be null, above code will throw NullPointerException if GoogleMaps were null. If we use code

Optional<GoogleMaps> optionalMap= Optional.of(null);

JRE will throw NPE

Exception in thread "main" java.lang.NullPointerException
    at java.util.Objects.requireNonNull(Objects.java:203)
    at java.util.Optional.<init>(Optional.java:96)
    at java.util.Optional.of(Optional.java:108)
    at com.umeshawasthi.tutorials.corejava.java8.optional.CreateOptionalObject.main(CreateOptionalObject.java:15)
Optional With  Not Null

To Create an Optional object for holding a null value, Optional class provide ofNullable() method.

Optional<GoogleMaps> nullGoogleMap = Optional.ofNullable(null);
System.out.println(nullGoogleMap.toString());

Output

Optional.empty

The difference between the null reference and empty Optional object is significant, a null reference will cause a NullPointerException whereas Optional.empty is a valid object and we can work on the object without worrying about NPE. 

 

5. Use Optional Value

Once We have an Optional object, we can work with methods provided by the optional object to handle presence or absence of a value. We can use isPresent() method from Optional class to check if a value exists or not.

Optional<String> optional = Optional.of("umesh");
System.out.println(optional.isPresent());

Optional<String> nullValue= Optional.ofNullable(null);
System.out.println(nullValue.isPresent());

Output


true

false

6. Use get() method

The optional class provides get() method which can get value from the Optional object. Keep in mind that get() method will throw NoSuchElementException in case Optional is empty.

Optional<String> nullValue= Optional.ofNullable(null);
System.out.println(nullValue.get());

Output

Exception in thread "main" java.util.NoSuchElementException: No value present
    at java.util.Optional.get(Optional.java:135)
    at com.umeshawasthi.tutorials.corejava.java8.optional.CreateOptionalObject.useGet(CreateOptionalObject.java:45)
    at com.umeshawasthi.tutorials.corejava.java8.optional.CreateOptionalObject.main(CreateOptionalObject.java:18)

We can use isPresent() method with Optional.get() to handle above exception

Optional<String> nullValue= Optional.ofNullable(null);
if(nullValue.isPresent()) {
    System.out.println(nullValue.get());
}

Of the main issue with Optional.get() method is that it violates the main principle of Optional class to avoid or handle such issue (Exceptions). It is not recommended to use get() method and should be avoided in favor of other alternates.

 

7. Use Optional Value

ifPresent() method from Optional API can perform conditional logic.

Optional<String> optional = Optional.of("umesh");
optional.ifPresent(System.out::println);

Optional<String> nullValue= Optional.ofNullable(null);
nullValue.ifPresent(System.out::println);

Without optional, we need to perform a null check before executing our logic, Optional make it easy to handle such use cases and provide a more clean API.

 

8. Default Value with orElse or orElseGet

A standard way to return the default value in case result is null is by using ternary operator similar to 

GoogleMaps map = googleMap !=null ? googleMap : defaultMap;

Using Optional, we can use it’s orElse() method to return the default value in case the Optional object is null.

String name=null;
String value= Optional.ofNullable(name).orElse("Hello");
System.out.println(value);

Default value with orElseGet

orElseGet() is almost similar to the orElse() , orElseGet() method takes a Supplier functional interface. It Returns the value if present otherwise invokes other and return the result. 

 

9. Difference Between orElse and orElseGet

To understand the difference, we will run following example.

public class OptionalElseGetExample {

    public static void main(String[] args) {

        Optional<String> testingOrElse = Optional.of("Testing orElse method");
        String finalOutput = testingOrElse.orElse(printHelloWorld());
        System.out.println("optional result:: " + finalOutput);

        Optional<String> testingOrElseGet = Optional.of("Testing orElse method");
        String finalOrElseOutput = testingOrElseGet.orElseGet(() -> printHelloWorld());
        System.out.println("optional result:: " + finalOrElseOutput);

    }

    public static String printHelloWorld() {
        System.out.println("Hello World");
        return "Say Hello";
    }
}

Output


Hello World
 orElse Test:: Testing orElse method
 orElseGet Test:: Testing orElse method
public class OptionalElseGetExample {

    public static void main(String[] args) {

        checkOrElseGet();
    }


    public static void checkOrElseGet() {

        Optional<String> testingOrElse = Optional.ofNullable(null);
        String finalOutput = testingOrElse.orElse(printHelloWorld());
        System.out.println("optional result:: " + finalOutput);

        Optional<String> testingOrElseGet = Optional.ofNullable(null);
        String finalOrElseOutput = testingOrElseGet.orElseGet(() -> printHelloWorld());
        System.out.println("optional result:: " + finalOrElseOutput);
    }

    public static String printHelloWorld() {
        System.out.println("Hello World");
        return "Say Hello";
    }
}

Output


Hello World
 optional result:: Say Hello
 Hello World
 optional result:: Say Hello

So orElseGet() will execute the function if Optional is empty, while for orElse() will execute function even if Optional is empty.Optional.orElse() can be used to assigning a literal value but we shouldn’t expect a control flow with it. In above example, this difference might not be visible but let’s say you are making an expensive DB call or connecting to an external web service, these small differences will make a lot of impacts.

Similarly, you can use the orElseThrow() method, which instead of providing a default value if Optional is empty, throws an exception.

 

10. Filtering Optional using filter method 

Most of the time, we need to call a method on an object and check property, we can use filter method provided by the Optional object to filter out values. Filter method takes a predicate as an argument if the Optional object is present and it matched the predicate, filter method will return the value else it will return empty Optional object. Let’s take at following example, Let’s say we will give 10% off on a product to a customer if the product price is greater than $20 but not more than $50. 

public class Product {

    private Double productPrice;

    public Product(Double productPrice) {
        this.productPrice = productPrice;
    }

    public Double getProductPrice() {
        return productPrice;
    }

    public void setProductPrice(Double productPrice) {
        
        this.productPrice = productPrice;
    }
}
public boolean checkIfEligibleForPromotion(Product product) {
    if (product != null && product.getProductPrice() != null
            && product.getProductPrice().doubleValue() > 20.00
            && product.getProductPrice().doubleValue() <= 50) {

        return true;
    }
    return false;
}

We need to write so much of code to accomplish this task, checking price range is part of the business workflow but other conditions are there just to avoid NullPointerException.

We will use Optional.filter() to handle use case with more clean and better approach.

public boolean checkIfEligibleForPromotionByFilter(Product product) {
    return Optional.ofNullable(product).map(Product::getProductPrice)
            .filter(productPrice->productPrice >20)
            .filter(productPrice->productPrice <=50)
            .isPresent();
}

Map method of the Optional class transformed the value inside the Optional Object by the function passed as an argument.

Note following points in the map and filter method

  1. Even if we pass we pass null object, it will not throw an exception.
  2. Above code is cleaner than we apply a filter to check if the product is eligible for promotion or not.
  3. API is cleaner and do not need an additional null check before executing business logic.

 

11. Transforming values using flatMap method 

Similar to map API, Optional also provides flatMap as another alternate for transforming values. map transforms values only when they are unwrapped whereas flatMap takes a wrapped value and unwraps it before transforming it.Let’s go back to our original example of Car having Navigation System, to get Map from the navigation system, we will do something like

car.getMap().getMapDetails() 

To Summarise, above code extract one object from another object and map method is a perfect candidate for it

myCar.map(Car::getMap)
        .map(GoogleMaps::getMapDetails)
        .orElse("Navigation Not Available");

 

Above code will not work, since map can only transform value when they are unwrapped, here are the details why above code will fail

  1. myCar is of type Optional<Car> and calling map on it will work, however, getMap will return an object of Optional<GoogleMap>.
  2. The result of above process will be of Optional<Optional<GoogleMap>> and getMapDetails() will not work (getMapDetails() is not valid for the outer Optional object)

In order to handle such use cases, Optional supports flatMap() which can take a wrapped value and unwraps it before transforming it.

myCar.flatMap(Car::getMap)
        .flatMap(GoogleMaps::getMapDetails)
        .orElse("Navigation Not Available");

 

12. Exceptions Vs Optional

Throwing an exception is a common pattern in Java API to return null in case a value is not present. Integet.parseInt(String) will throw NumberFormatException in case supplied argument is invalid. We can use the Optional class to handle it in a more interesting way (We no longer need to try catch block at each location where we need to parse String to Integer)

public static Optional<Integer> parseInt(String value) {

    try {
        return Optional.of(Integer.parseInt(value));
    } catch (NumberFormatException nfe) {
        return Optional.empty();
    }
}

 

13. Java 9 Improvements

Java 9 brought some improvement to the optional API. Let’s inspect some changes introduced in the Java 9:

 

13.1 The stream() Method

The Optional class in Java 9 added a stream() method. Java 8 introduced the Stream API which helps to use the functional programming concepts. Java 9 stream() method on Optional class allows to treat Optional instance as a Stream. Let’s look at a below example for better clarity:

Optional <String> optional = Optional.of("1");
List <String> list = optional.stream().map(String::toUpperCase).collect(Collectors.toList());

In above example, we defined the Optional and then calling the stream() method.With Optional in the picture, the stream() method creates an empty Stream:

Optional <String> emptyOptional = Optional.empty();
List <String> collect = emptyOptional.stream().map(String::toUpperCase).collect(Collectors.toList());<code>

Let’s take another example to understand the changes introduced in Java 9 Optional API:

public Stream <String> findCustomers(Collection <String> customerIds) {
 return customerIds.stream()
  .map(this::findCustomers)
  .filter(Optional::isPresent)
  .map(Optional::get);
}

This is for the Java 8 Optional class. In the above example, we combined 2 optional methods for our result. Let’s see how to do this in Java 9 using the stream() method added to the optional class.

public Stream <String> findCustomers(Collection <String> customerIds) {
   return customerIds.stream()
    .map(this::findCustomers)
    .flatMap(Optional::stream)
    .collect(toList());
  }

 

2. The or() Method

This is the other method added to the Optional API in Java 9. This method is an addition to the orElse() and orElseGet() method already available in the optional API. This method takes a function that creates an Optional as an argument. Let’s take a very simple example to get an initial understanding:

public static void main(String[] args) {

     char digit = Stream.of('a', 'b', 'c')
         .filter(c - > Character.isDigit(c))
         .findFirst()
         .or(() - > Optional.of('0')).get();
     System.out.println(digit);

 }

Let’s take another example of an ecommerce application, where we like to return the product information based on the product code. We start with the base product information and will try to move to the product variant if base is not available.In a typical workflow, this is how the workflow look like:

Product product = getBaseProductByCode(productCode);
if (product == null) {
    product = getVaraintProductByCode(productCode);
}
//check if variant is not null and work on the process.
//With Java 8 optional API, this is how it might look like:

Product client = 
        getBaseProductByCode(productCode)
        .orElseGet(() -> getVaraintProductByCode(productCode));

There is still one issue as the getVaraintProductByCode method may return null (because of invalid product code etc.). To ensure our method is returning the Optional and not the null, we can use the new or() method:

Product client = 
        getBaseProductByCode(productCode)
        .or(() -> getVaraintProductByCode(productCode));

Here, method will return Optional<Product> and we will have a value if the system can find product based on code else it will return empty Optional.

 

2. ifPresentOrElse() Method

This new method is helpful when we want to perform a certain action if the optional value is present or we want to trigger a different workflow if it’s absent. Let’s take a simple example where customer searching for a product. There are 2 outcomes of the action

  1. Product is valid and we want to show the details
  2. Product is not valid and we want to show popular products to the customer and not 404 page

This is how traditional method look like

Product product = getProduct(code);
if (product != null) {
   showProductDetailPage(product);
}
else{
    showPopularProductPage();
}

Java 8 do not have a way to handle both condition and one of the option is to call the isPresent() and get() methods, but this is not recommended. Another option is to use the ifPresent() method:

getProduct(code)
    .ifPresent(
        this::showProductDetailPage);

This is not very helpful as we can’t show the recommended product page in case 0 results. To handle these use cases, Java 9 Optional API came up with the ifPresentOrElse() method:

getProduct(code)
    .ifPresentOrElse(
        this::showProductDetailPage,
        this::showPopularProductPage);<code>

 

Conclusion

In this post, we cover Java 8 Optional API in details along with some reasons we should use it and how we can adapt it to our data model for the better API design. We learned how to use Optional class static factory methods like Optional.empty(), Optional.of() to create optional objects.Do not try to replace every single null reference in your code with Optional as it might lead to other issues, I will use Optional to create better API which can communicate to its user as what to expect from the API.

All the code of this article is available Over on Github. This is a Maven-based project.

References

Optional

3 thoughts on “java 8 Optional”

Comments are closed.