Java Optional Class Features

One of (every) programmer (bad) practice is to return the null reference to show the absence of an object. But this can lead to NullPointerException if not handled properly. The Optional class was introduced in Java 8 to handle optional values instead of a null reference. With java.util.Optional we able to handle null value elegantly, as shown in following example:

Student.java
import java.time.LocalDate;
import java.util.Optional;
import lombok.ToString;

@ToString
public class Student {

    private Integer id;
    private String name;
    private LocalDate birthDate;
    private Batch batch;

    public Optional<Integer> getId() {
        return Optional.ofNullable(id);
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public Optional<String> getName() {
        return Optional.ofNullable(name);
    }

    public void setName(String name) {
        this.name = name;
    }

    public Optional<LocalDate> getBirthDate() {
        return Optional.ofNullable(birthDate);
    }

    public void setBirthDate(LocalDate birthDate) {
        this.birthDate = birthDate;
    }
    
    public Optional<Batch> getBatch() {
        return Optional.ofNullable(batch);
    }

    public void setBatch(Batch batch) {
        this.batch = batch;
    }
    
    public Student(Integer id, String name) {
        this.id = id;
        this.name = name;
    }
}
                    

Batch.java
import java.util.Optional;
import lombok.ToString;

@ToString
public class Batch {

    private Integer id;
    private Integer year;
    private String name;
    private Grade grade;
    
    public Optional<Integer> getId() {
        return Optional.ofNullable(id);
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public Optional<Integer> getYear() {
        return Optional.ofNullable(year);
    }

    public void setYear(Integer year) {
        this.year = year;
    }
    
    public Optional<String> getName() {
        return Optional.ofNullable(name);
    }

    public void setName(String name) {
        this.name = name;
    }

    public Optional<Grade> getGrade() {
        return Optional.ofNullable(grade);
    }

    public void setGrade(Grade grade) {
        this.grade = grade;
    }
    
    public Batch(Integer id, Integer year, String name) {
        this.id = id;
        this.year = year;
        this.name = name;
    }
}
                    

Grade.java
import java.util.Optional;
import lombok.ToString;

@ToString
public class Grade {

    private Integer id;
    private String name;

    public Optional<Integer> getId() {
        return Optional.ofNullable(id);
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public Optional<String> getName() {
        return Optional.ofNullable(name);
    }

    public void setName(String name) {
        this.name = name;
    }

    public Grade(Integer id, String name) {
        this.id = id;
        this.name = name;
    }
}
                    

OptionalExample.java
import java.time.LocalDate;
import java.time.Month;
import java.util.Optional;

public class OptionalExample {
    
    public static void main(String[] args) {
        Grade grade = new Grade(1, "Grade-01");
        
        Batch batch = new Batch(1, 2019, "Batch-01");
        batch .setGrade(grade);
        
        Student student = new Student(100, "Carol Danvers");
        student.setBirthDate(LocalDate.of(1989, Month.OCTOBER, 1));
        student.setBatch(batch);
        
        Optional<Student> os = Optional.of(student);
        os.orElseThrow(IllegalStateException::new)
                .getBatch().orElseThrow(IllegalStateException::new)
                .getGrade().orElse(new Grade(99, "Default"))
                .getName()
                .ifPresent(name -> System.out.println(name.toUpperCase()));
    }
}
                    

You can read about Java 8 Optional Features in previous article.

Optional class had some enhancements in subsequent Java release. In this article we will list down what features (methods) added for Optional class.

Optional New Methods in Java 9

In Java 9, three methods added to java.util.Optional class:

Optional::ifPresentOrElse Method

The ifPresentOrElse() method takes two arguments: a Consumer and a Runnable. If the value is present, then the Consumer action is executed; otherwise if no value is present, the Runnable action is performed.

ifPresentOrElseExample.java
import java.util.Optional;

public class ifPresentOrElseExample {

    public static void main(String[] args) {

        Student student = new Student(100, "Carol Danvers");
        Optional<Student> os = Optional.of(student);

        // Java 8 Version
        System.out.println("Java 8 Version:");
        if (os.isPresent()) {
            Student s = os.get();

            if (s.getId().get() == 99) {
                System.out.println(s.getName());
            } else {
                System.out.println("Not found");
            }

            if (s.getId().get() == 100) {
                System.out.println(s.getName());
            } else {
                System.out.println("Not found");
            }
        }

        // Java 9 Version
        System.out.println("\nJava 9 Version:");
        os.filter(s -> s.getId().get() == 99)
                .ifPresentOrElse(s -> System.out.println(s.getName()),
                        () -> System.out.println("Not found"));
        os.filter(s -> s.getId().get() == 100)
                .ifPresentOrElse(s -> System.out.println(s.getName()),
                        () -> System.out.println("Not found"));
    }
}
                    

Java 8 Version: Not found Optional[Carol Danvers] Java 9 Version: Not found Optional[Carol Danvers]

As you can see from our example above, Java 8 Optional class isPresent() and ifPresent(...) limitation make us - the programmer - to perform our "else" in traditional way.

Optional::or Method

Java 8 Optional class had orElse(...) and orElseGet(...) methods but both need to return unwrapped values. Java 9 introduces the or() method that returns another Optional lazily if the value is not present in our "checked" Optional.

OrExample.java
import java.util.Optional;

public class OrExample {

    public static void main(String[] args) {
        Student student1 = null;
        Student student2 = new Student(100, "Carol Danvers");

        Student s2 = Optional.ofNullable(student1)
                .or(() -> Optional.of(new Student(999, "Unknown"))).get();
        System.out.println(s2);
        Student s1 = Optional.of(student2)
                .or(() -> Optional.of(new Student(999, "Unknown"))).get();
        System.out.println(s1);
    }
}
                    

Student(id=Optional[999], name=Optional[Unknown], birthDate=Optional.empty, batch=Optional.empty) Student(id=Optional[100], name=Optional[Carol Danvers], birthDate=Optional.empty, batch=Optional.empty)

Optional stream() Method

Optional class stream() method allowing us to treat the Optional instance as a Stream. This will create a Stream of one element on which we can use all the methods that are available in the Stream API.

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class StreamExample {

    public static void main(String[] args) {
        List<Optional<Student>> students = Arrays.asList(
                Optional.of(new Student(101, "Reed Richards")),
                Optional.of(new Student(102, "Susan Storm")),
                Optional.of(new Student(103, "Johnny Storm")),
                Optional.of(new Student(104, "Ben Grimm"))
        );

        List<Student> list1 = students.stream()
                .map(Optional::get)
                .collect(Collectors.toList());
        System.out.println(list1);

        List<Optional<Student>> otherStudents = new ArrayList<>();
        otherStudents.addAll(students);
        otherStudents.addAll(Arrays.asList(Optional.empty()));

        List<Student> list2 = otherStudents.stream()
                .filter(Optional::isPresent)
                .map(Optional::get)
                .collect(Collectors.toList());
        System.out.println(list2);

        List<Student> list3 = otherStudents.stream()
                .flatMap(o -> o.map(Stream::of).orElseGet(Stream::empty))
                .collect(Collectors.toList());
        System.out.println(list3);
        
        List<String> list4 = otherStudents.stream()
                .flatMap(o -> o.isPresent() ? Stream.of(o.get().getName()) : Stream.empty())
                .map(Optional::get)
                .collect(Collectors.toList());
        System.out.println(list4);
        
        Student student = otherStudents.stream().findFirst()
                .orElse(Optional.of(new Student(999, "Unknown"))).get();
        System.out.println(student);
    }
}
                    

[Student(id=Optional[101], name=Optional[Reed Richards], birthDate=Optional.empty, batch=Optional.empty), Student(id=Optional[102], name=Optional[Susan Storm], birthDate=Optional.empty, batch=Optional.empty), Student(id=Optional[103], name=Optional[Johnny Storm], birthDate=Optional.empty, batch=Optional.empty), Student(id=Optional[104], name=Optional[Ben Grimm], birthDate=Optional.empty, batch=Optional.empty)] [Student(id=Optional[101], name=Optional[Reed Richards], birthDate=Optional.empty, batch=Optional.empty), Student(id=Optional[102], name=Optional[Susan Storm], birthDate=Optional.empty, batch=Optional.empty), Student(id=Optional[103], name=Optional[Johnny Storm], birthDate=Optional.empty, batch=Optional.empty), Student(id=Optional[104], name=Optional[Ben Grimm], birthDate=Optional.empty, batch=Optional.empty)] [Student(id=Optional[101], name=Optional[Reed Richards], birthDate=Optional.empty, batch=Optional.empty), Student(id=Optional[102], name=Optional[Susan Storm], birthDate=Optional.empty, batch=Optional.empty), Student(id=Optional[103], name=Optional[Johnny Storm], birthDate=Optional.empty, batch=Optional.empty), Student(id=Optional[104], name=Optional[Ben Grimm], birthDate=Optional.empty, batch=Optional.empty)] [Reed Richards, Susan Storm, Johnny Storm, Ben Grimm] Student(id=Optional[101], name=Optional[Reed Richards], birthDate=Optional.empty, batch=Optional.empty)

From the example above, this method can be used to transform a Stream of optional elements to a Stream of present value elements:

Stream<Optional<T>> os = .. Stream<T> s = os.flatMap(Optional::stream)

Optional Changes in Java 10

Java 10 add one method to java.util.Optional class:

Java doc states that orElseThrow() is a preferred alternative to get() method.

OrElseThrowExample.java
package com.dariawan.jdk10.optional;

import com.dariawan.jdk9.optional.Student;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

public class OrElseThrowExample {

    public static void main(String[] args) {

        List<Optional<Student>> students = Arrays.asList(
                Optional.of(new Student(101, "Reed Richards")),
                Optional.of(new Student(102, "Susan Storm")),
                Optional.of(new Student(103, "Johnny Storm")),
                Optional.of(new Student(104, "Ben Grimm"))
        );

        Optional<Student> od1 = students.stream()
                .filter(Optional::isPresent)
                .filter(o -> o.get().getId().isPresent())
                .filter(o -> o.get().getId().get() > 103)
                .findAny()
                .orElseThrow();
        System.out.println(od1);
        
        Optional<Student> od2 = students.stream()
                .filter(Optional::isPresent)
                .filter(o -> o.get().getId().isPresent())
                .filter(o -> o.get().getId().get() == 105)
                .findAny()
                .orElseThrow();
        System.out.println(od2);
    }
}
                    

Optional[Student(id=Optional[104], name=Optional[Ben Grimm], birthDate=Optional.empty, batch=Optional.empty)] Exception in thread "main" java.util.NoSuchElementException: No value present at java.base/java.util.Optional.orElseThrow(Optional.java:371) at com.dariawan.jdk10.optional.OrElseThrowExample.main(OrElseThrowExample.java:32)

Now compare with this code:

GetExample.java
package com.dariawan.jdk10.optional;

import com.dariawan.jdk9.optional.Student;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

public class GetExample {

    public static void main(String[] args) {

        List<Optional<Student>> students = Arrays.asList(
                Optional.of(new Student(101, "Reed Richards")),
                Optional.of(new Student(102, "Susan Storm")),
                Optional.of(new Student(103, "Johnny Storm")),
                Optional.of(new Student(104, "Ben Grimm"))
        );
        
        Optional<Student> od = students.stream()
                .filter(Optional::isPresent)
                .filter(o -> o.get().getId().isPresent())
                .filter(o -> o.get().getId().get() == 105)
                .findAny()
                .get();
        System.out.println(od);
    }
}
                    

Exception in thread "main" java.util.NoSuchElementException: No value present at java.base/java.util.Optional.get(Optional.java:148) at com.dariawan.jdk10.optional.GetExample.main(GetExample.java:24)

One reason of this new orElseThrow() addition is the method get() name is misleading because generally a getter method does not throw error. Method orElseThrow() is the preferred alternative to get() method, since the method name is consistent with the behavior. Both get() and orElseThrow() throws NoSuchElementException if the value is not present.

Optional Changes in Java 11

One method is added in Java 11 for java.util.Optional class.

  • booleanisEmpty(): If a value is not present, returns true, otherwise false.
IsEmptyExample.java
package com.dariawan.jdk11.optional;

import com.dariawan.jdk9.optional.Student;
import java.time.LocalDate;
import java.time.Month;
import java.util.Optional;

public class IsEmptyExample {
    
    public static void main(String[] args) {
        Student student = new Student(100, "Carol Danvers");
        student.setBirthDate(LocalDate.of(1989, Month.OCTOBER, 1));
        
        Optional<Student> os = Optional.of(student);
        boolean bornBefore90s = !os.orElseThrow(IllegalStateException::new)
                .getBirthDate()
                .filter(bd -> bd.getYear() > 1990)
                .isPresent();
        System.out.println(bornBefore90s);
        
        bornBefore90s = os.orElseThrow(IllegalStateException::new)
                .getBirthDate()
                .filter(bd -> bd.getYear() > 1990)
                .isEmpty();
        System.out.println(bornBefore90s);
    }
}
                    

true true

Yes, isEmpty() is similar like !isPresent(). isEmpty is semantically correct compare to not isPresent (negated value).

Conclusion

Java although is very established language also evolving becomes more and more functional (at least since Java 8). Part of these progress, Java equipped with more tools, classes or APIs to help us embrace the changes. java.util.Optional which introduced in Java 8 is part of this initiatives. Optional class is keep improving through Java release. As Java Programmers, we must understand on how to choose the correct tools or best APIs that match our project needs. Java Optional is one of the class at our disposal that we need to understand and master it.