java 8 optional

Java 8 Optional

In this post, we will be talking about  Java 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 indicate the absence of a given object and which can cause NullPointerException. The Optional class was introduced in Java8 to handle optional values instead of 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 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 indicate the absence of an object, for our case getMap() will return a null reference to indicate that Car does not have build in a navigation system. 

 

Let’s use 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 clearly indicate 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 to we handle this use case?

2. Use Defensive Check to Handle Null 

In order 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 due to nested check. All these checks are happening only to avoid NullPointerException and certainly 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 to check the null pointer for any other property).

To summarise, returning a null reference to indicate non-existence of an object or value is not a very good approach and definitely, need some other good alternate.

 

3. Java Optional Class

To handle the absence of 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.

Optional class provide a way to design better API in which consumer of the API has a better understanding as to what can be expected back from the API.

java.util.Optional<T> is like a container, either container have some object or it is empty (can not 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 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 below 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 really significant, 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 given 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 be used 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 violate 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 favour of other alternates.

 

7. Use Optional Value

ifPresent() method from Optional API can be used to 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 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 of that invocation. 

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 very 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 some property, filter method provided by the Optional object can be used 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 empty Optional object will be returned. 

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 above 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 null object is passed, it will not throw any exception.
  2. Above code is cleaner as we just 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, in order 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 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 Optional class to handle it in 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();
    }
}

Conclusion

In this post, we cover Java Optional API in details along with some of the reasons as to why 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 clearly 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

JavaDevJournal

Hello!! I am Umesh- an engineer by profession and a photographer by passion.I like to build stuff on the web using OSS and love to capture the world through my lens.

Leave a Reply

1 Comment on "java 8 optional"

Notify of
avatar
Sort by:   newest | oldest | most voted
wpDiscuz