Java 8 Optional

Optional is a new class added in java 8 in java.util package.
This article will offer an in-depth explanation about java Optional class and its objects.

Why Optional
In a single line, Optional is designed to deal with NullPointerException in java in an elegant manner.

You might be familiar with null references which lead to the most commonly found NullPointerException as shown below.

String str = null;
if(str.equals("codippa")) {
   // do something
}

Line 2 in above example will throw a java.lang.NullPointerException since we are trying to invoke a method on null reference.
Optional class has been added to address this problem.

You might say that NullPointerException can be avoided by a simple null check such as in code below.

String str = null;
if(str != null && str.equals("codippa")) {
   // do something
}

Then why a separate class for this.

You are right but have a look at below code.

String country = student.getStudentDetails().getAddress().getCountry().toLowerCase();
switch(country) {
  case "India":
   // do something
  case "USA":
   // do something
   
   // other countries
}

This example has chances of NullPointerException at multiple places if any of the objects student, studentDetails, address or country are null.

Now you might be willing to put null checks as before. But, doing that would transform above code to

String country = null;

if(student != null) {
   StudentDetails studentDetails = student.getStudentDetails();
   if(studentDetails() != null) {
      Address address = studentDetails().getAddress();
      if(address() != null) {
         Country studentCountry = address.getCountry();
         if(studentCountry != null) {
            country = studentCountry.toLowerCase();
         }
      }
   }
}

// more code

This code is pretty difficult to read as you can see and is also hard to maintain and modify.
Optional is created to solve this problem and we shall re-visit it after learning Optional.

What is Optional
Optional is a container or a wrapper over an object. It might contain a value(or object) or it might not.
Optional can contain any object and hence it is declared with a generic type such as Optional<String>, Optional<Cat> etc., and it looks as below.
Optional with and without valueWith Optional, you can choose to get its value only if it is present, return a default value if the object that it holds is not present or null etc.

Thus, it solves the problem of NullPointerPointerException making the code more readable as compared to null checks.
We will see how the above example can be modified with Optional objects after learning it in depth.

Create Optional object
An Optional object can not be created using its constructor since its constructor is private.
Optional object can only be created using its static methods as explained below.
1. Using of() method
An Optional can be created by calling its static of() method supplying it the object that the Optional will hold or the object wrapped by Optional.
Example,

Student student = new Student();
Optional<Student> optional = Optional.of(student);

If the object provided to of is null, then it will raise a NullPointerException.
If there is a chance of the object being null, use the method discussed next.

2. Using ofNullable() method
This is the second way of creating an Optional object.
ofNullable() is also a static method which accepts an object as argument and creates an Optional that contains this object.

If the object is null, it creates and returns an empty Optional object. Example,

Student student = new Student();
Optional<Student> optional = Optional.ofNullable(student);

3. Creating empty Optional
It is also possible to create an Optional object with empty value using its static empty method as shown below.

Optional<Student> optional = Optional.empty(student);

Optional returned by empty can be assigned to any reference type meaning it could be Optional<String>, Optional<Shape>, Optional<Object> and so on.
Value of Optional
As stated before, Optional is a wrapper around an object but how do you access the underlying object.
Optional provides a get method which returns the object which it wraps.
Type of this object is the same as the type of Optional. Example,

String color = "red";
Optional<String> optional = Optional.of(color);
String back = optional.get();
System.out.print("Color is : " + back);

which outputs

Color is : red

Check Optional value
If an Optional object does not contain a value, that is, it is an empty Optional created using its empty() or ofNullable() methods and you try to access its value by using get(), you will face an error

Exception in thread “main” java.util.NoSuchElementException: No value present

To prevent this error, it is better to check if Optional contains a value before accessing it using its isPresent() or isEmpty() methods.
isPresent()
This method returns true if Optional contains a value and false otherwise. Example,

// create empty Optional value
Optional<String> optional = Optional.empty();
System.out.print("Optional contains a value? " + optional.isPresent());

which outputs

Optional contains a value? false

isEmpty()
This methods works opposite to isPresent() and returns true if the Optional object does not contain a value, false otherwise. Example,

// create empty Optional value
Optional<String> optional = Optional.empty();
System.out.print("Optional contains a value? " + optional.isEmpty());

This outputs

Optional contains a value? true

Action on value present
In the last section we saw how to check if Optional contains a value.
If you want to perform some action if Optional contains a value, then you might think of following lines.

Optional<String> o = Optional.empty();
// check if optional contains a value
if(o.isPresent()) {
   System.out.print(o.get());
}

However, Optional provides a shortcut method that does not require an if condition.
It is by using ifPresent() method of Optional.

This method accepts an argument of type java.util.function.Consumer which is a functional interface, hence you can directly supply its implementation as a Lambda expression.

Example of ifPresent() is given below.

Optional<String> o = Optional.of("Learning optional");
// print value of optional if non-empty
o.ifPresent((e) -> System.out.println(o.get()));

This prints

Learning optional

Default optional value
Above example showed what action to take if Optional has a value but what if it does not.
Optional provides methods to perform some action if it is empty.
These methods are
1. orElse()
This method accepts a value as argument and returns this value if the Optional is empty.
Type of this argument must be same as the type of Optional.
Example,

// simple pizza
Pizza plainPizza = new Pizza();
plainPizza.setDescription("Pizza without any toppings");
// create empty Optional
Optional<Pizza> empty = Optional.empty();
// get pizza from optional
Pizza p = empty.orElse(plainPizza);
System.out.print(p.getDescription())

This example creates an empty Optional and then invokes its orElse() method with a default object as argument. Output of this code is

Pizza without any toppings

which shows that the object returned by orElse() is the same as its argument.
2. orElseGet()
This method is also similar to orElse() and returns a value if the Optional is empty.
It accepts an argument of type java.util.function.Supplier and thus it returns the value returned by the supplier.

Example of orElseGet() is given ahead

// create empty optional
Optional<String> empty = Optional.empty();
String s = empty.orElseGet(() -> "Optional is empty");
System.out.println(s);

which prints

Optional is empty

Note that the argument to orElseGet() method is a Lambda expression since Supplier is a Functional interface.
If above syntax seems confusing, then go through the below code which breaks the call to orElseGet() in separate lines.

// define a supplier
Supplier<String> supplier = () -> "Optional is empty"; 
String s = empty.orElseGet(supplier);

Difference between orElse() & orElseGet()
1. A visible difference between the two methods is that orElse() accepts an argument of the same type as the Optional object while orElseGet() method accepts an argument of type java.util.function.Supplier.

2. Another difference is orElse() simply returns the argument supplied to it while orElseGet() invokes a Supplier method to retrieve the result.

3. But there is another difference behind the scenes and this difference can be demonstrated in the below example.

private void getData() {
   System.out.println("Fetching data");
}

// create non-empty optional
Optional<String> o = Optional.of("Learning optional");
System.out.println("------------ orElse ------------");

// using orElse
String s = o.orElse(getData());
System.out.println(s);
System.out.println("\n------------orElseGet-------------");

// using orElseGet
s = o.orElseGet(() -> getData());
System.out.println(s);

Instead of directly supplying values to orElse() and orElseGet(), we are calling a method that returns a value.

This code when executed produces following output

———— orElse ————
Fetching data
Learning optional

————orElseGet————-
Learning optional

From this output, it is evident that even though the Optional is not empty,
orElse() still executes the method while orElseGet() executes it only when the Optional is empty.

This is considerable when there are network or database calls involved for fetching data.
3. or()
This method was added in java 9. If the Optional contains a value, then it returns the same Optional otherwise it returns another default Optional object.

or() expects and argument of type java.util.function.Supplier. The default Optional returned is provided by this argument Supplier function.
Example,

String s = "optional value";
// create an optional
Optional<String> o = Optional.ofNullable(s);
// get Optional with or
Optional<String> or = o.or(()-> Optional.of("Default"));
System.out.println("Optional value:: " + or.get());

// create an optional with null value
Optional<String> empty = Optional.ofNullable(null);
// get Optional with or
Optional<String> defaultOptional = empty.or(()-> Optional.of("Default"));
System.out.println("Optional value:: " + defaultOptional.get());

This code outputs

Optional value:: Optional value
Optional value:: Default

When the Optional contains a value, then or returns the same Optional.
But when it is empty, then the Optional supplied by the argument function of or is returned.

Throwing exceptions
Till now we saw how Optional can return a default value if it is empty. But sometimes empty Optional should be considered as an error.

For handling such situations, it is also possible to throw an error when the Optional is empty.
Following are the methods for this
1. orElseThrow(Supplier)
This method accepts an argument of type Supplier interface and throws an exception that is returned by this Supplier function.

orElseThrow() will throw an exception only when the Optional is empty. Example,

// create empty optional
Optional<String> o = Optonal.ofNullable(null);
// throw exception when optional does not have any value
o.orElseThrow(() -> new IllegalArgumentException("Empty Optional"));

Above code when executed produces following result.

Exception in thread “main” java.lang.IllegalArgumentException: Empty Optional
at com.codippa.demo.DemoApplication.lambda$0(DemoApplication.java:22)
at java.base/java.util.Optional.orElseThrow(Optional.java:408)
at com.codippa.demo.DemoApplication.main(DemoApplication.java:22)

2. orElseThrow()
This method is similar to orElseThrow() discussed above but it does not accept any argument and throws a java.util.NoSuchElementException by default with an error message “No value present”, if the Optional is empty.
Example,

// create empty optional
Optional<String> o = Optonal.ofNullable(null);
// throw exception when optional does not have any value
o.orElseThrow();

This will result in

Exception in thread “main” java.util.NoSuchElementException: No value present
at java.base/java.util.Optional.orElseThrow(Optional.java:382)
at com.codippa.demo.DemoApplication.main(DemoApplication.java:22)

This method was added in java 10.
Filtering data
There may be condition when you want to return a result only when the value meets a certain criteria.

Optional provides a filter method which accepts a Predicate representing a condition to match and returns the Optional object if the value matched the predicate(or condition) else returns an empty Optional.
Example,

Laptop apple = new Laptop("Apple");
Optional<Laptop> laptop = Optional.of(apple);
// check if 
Optional<Laptop> o = laptop.filter((l) -> "Apple".equals(l.getBrand()));
System.out.println(o.orElseThrow());

Above example checks the laptop brand and filters it to return an empty Optional if the brand is other than Apple.

Note that the filter() method will return an empty Optional if the supplied Predicate returns false, that is, the condition is not matched.
orElseThrow throws an exception if the returned Optional is empty.
Transforming Optional
It is possible to convert an Optional of some type to an Optional of a different type using map and flatMap methods.
map()
map() accepts an argument of type java.util.function.Function which is a Functional interface with a single method that accepts an argument and returns a value.

map() argument function is also called mapper function.

Below example of map() method converts an Optional<Laptop> to an Optional<String>

// create an object
Laptop apple = new Laptop("Apple");
// create Optional object
Optional<Laptop> laptop = Optional.of(apple);
// map laptop Optional to a string Optional
Optional<String> strOptional = laptop.map(l -> l.getBrand());
// get Optional value
String brand = strOptional.get();
System.out.println(brand); //prints Apple

Following are some important points regarding the map method in above example.

In the statement map(l -> l.getBrand()), map is supplied a Lambda expression with a single argument and that returns a value.
map() returns an Optional with its type modified to the return type of Lambda expression supplied as argument to map().
Thus, in above example, map() returns an Optional of type String.

Since map() returns an Optional, it is possible to chain other Optional methods after map().
Example,

String brand = laptop.map(l -> l.getBrand()).map(s -> s.toUpperCase()).get();
System.out.println(brand); //prints APPLE

Note that the type of value to second map() method is a String which is the type of Optional returned by the first map() method.
If the argument function returns null, then map() returns an empty Optional.
flatMap()
Another method to transform an Optional object is using flatMap() method.

There are two differences between map() and flatMap() method.
1. Argument of flatMap() expects a function whose return type is an Optional object while that of map() method expects a function whose return type is a value such as a String in the example above.

2. map() returns an Optional wrapping the value of some type while flatMap() returns the value directly, it does not return an Optional.

3. flatMap() returns an Optional if its argument Optional is empty and a value if the argument Optional is non-empty while map() returns an Optional in both cases.
Example of flatMap() is given below.

Optional<Laptop> laptop = Optional.of(apple);
String result = laptop.flatMap(l -> l.getBrandOptional()).orElseThrow();
System.out.println(result); // prints Apple

In this example, the method getBrandOptional() returns an Optional<String> which wraps the brand value and thus flatMap() returns the value of brand directly.
How Optional avoids NullPointerException
At the start of this article, following problem stating NullPointerException was discussed.

String country = student.getStudentDetails().getAddress().getCountry().toLowerCase();

For avoiding it, multiple null checks were required or the solution was using Optional.

Now after learning about Optional, you must be thinking how does Optional avoids NullPointerException.

Modify all the classes used in this example as below.

class Student{
  
  StudentDetails studentDetails;
  
  public Optional<StudentDetails> getStudentDetails() {
    return Optional.ofNullable(studentDetails);
  }
  
}

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

class Address {
  private Country country;
  
  public Optional<Country> getCountry(){
    return Optional.ofNullable(country);
  }
}

class Country {
  private String country;
  
  public Optional<String> getCountry(){
    return Optional.ofNullable(country);   
  }
}

As you can see, the getter method now returns an Optional wrapping the required object instead of the object directly.
For getting the country, the statement will be written as

Student st = null;
String country = Optional.ofNullable(st).
                          flatMap(s -> s.getStudentDetails()).
                          flatMap(sd -> sd.getAddress()).
                          flatMap(a -> a.getCountry()).
                          flatMap(c -> c.getCountry()).
                          orElse("Country not found");

Each call to flatMap() returns an Optional object and finally, if the Optional returned by getCountry() is empty, then an error message is returned but no NullPointerException.
That is all on Optional class added in java 8.
Hope the article was useful.

Leave a Reply