Java 8 Optional Overview With Examples

Let's check these two classes: Employee and Department.

package com.dariawan.jdk8.optional;

import java.time.LocalDate;
import lombok.ToString;

@ToString
public class Employee {

    private Integer id;
    private String name;
    private LocalDate birthDate;
    private Department department;

    public Integer getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    public LocalDate getBirthDate() {
        return birthDate;
    }

    public void setBirthDate(LocalDate birthDate) {
        this.birthDate = birthDate;
    }

    public Department getDepartment() {
        return department;
    }

    public void setDepartment(Department department) {
        this.department = department;
    }
}
                    

package com.dariawan.jdk8.optional;

import lombok.ToString;

@ToString
public class Department {

    private Integer id;
    private String name;
    
    public Integer getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

Then let say (in messy way) we try to get the department name belongs to an employee with following codes:

package com.dariawan.jdk8.optional;

import java.time.LocalDate;
import java.time.Month;

public class NullPointerExample {

    public static void main(String[] args) {
        Employee emp = new Employee();
        emp.setId(1001);
        emp.setName("Clark Kent");
        emp.setBirthDate(LocalDate.of(1974, Month.JUNE, 18));

        System.out.println(emp.getDepartment().getName());
    }
}
                    

We'll encounter

$ java -cp . com.dariawan.jdk8.optional.NullPointerExample Exception in thread "main" java.lang.NullPointerException at com.dariawan.jdk8.optional.NullPointerExample.main(NullPointerExample.java:14)

What happen is, we all already know... end of the world... doomsday... As a Java programmer, we encounter this before. NullPointerExceptions are a RuntimeExceptions that can be thrown during the normal operation of the Java Virtual Machine (JVM). Null checks in programs are often overlooked by programmers causing serious bugs in program's code.

Yes, we can use standard null reference check in our code, similar like this:

if (emp != null) { emp.setId(1001); emp.setName("Clark Kent"); emp.setBirthDate(LocalDate.of(1974, Month.JUNE, 18)); if (emp.getDepartment() != null) { System.out.println(emp.getDepartment().getName()); } }

But, all these checks are happening only to avoid NullPointerException and not adding anything to the business logic, and it will involve a lot of "manual" check. Of course modern IDE can help us by trigger warning for unassigned variable, etc. But the job to ensure all properties are properly assigned is still manual job, and manual job is tedious. We need another alternative.

java.util.Optional<T>

Java 8 introduces a new class called java.util.Optional<T> that encapsulates an optional value. To make it simple, Optional is a container that either contains a value or "empty".

We can create an empty Optional by using the static method Optional.empty(). It will returns an empty Optional instance. No value is present for this Optional.

Optional<Department> od = Optional.empty();

To create an Optional with a non-null value, use the static factory method Optional.of(T value). It will returns an Optional with the specified present non-null value.

Department d = new Department(); Optional<Department> od = Optional.of(d);

In above case, the program will throw a NullPointerException immediately if Department d is null, rather than throwing it when we try to access any properties in the Department object. Check below codes:

package com.dariawan.jdk8.optional;

import java.util.Optional;

public class OptionalExample {
 
    public static void main(String[] args) {
        Department d = null;
        Optional.of(d);
    }
}
                    

$ java -cp . com.dariawan.jdk8.optional.OptionalExample Exception in thread "main" java.lang.NullPointerException at java.util.Objects.requireNonNull(Unknown Source) at java.util.Optional.<init>(Unknown Source) at java.util.Optional.of(Unknown Source) at com.dariawan.jdk8.optional.OptionalExample.main(OptionalExample.java:9)

Last but not least, static method Optional.ofNullable(T value) will returns an Optional describing the specified value, if non-null, otherwise returns an empty Optional.

public static void main(String[] args) {
    Department d = null;
    System.out.println(Optional.ofNullable(d));
}
                    

$ java -cp . com.dariawan.jdk8.optional.OptionalExample Optional.empty

If Department object is null, the resulting Optional object is empty, but it won't throw the NullPointerException. OK, now we can change our Employee class to:

Employee.java
package com.dariawan.jdk8.optional;

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

@ToString
public class Employee {

    private Integer id;
    private String name;
    private LocalDate birthDate;
    private Department department;

    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<Department> getDepartment() {
        return Optional.ofNullable(department);
    }

    public void setDepartment(Department department) {
        this.department = department;
    }
}
                    

The Java language authors have been quite frank that Optional was intended for use only as a return type, as a way to convey that a method may or may not return a value. Avoid to send an Optional to a method or constructor. The purpose of Java 8 Optional is clearly defined by Brian Goetz, Java’s language architect:

Optional is intended to provide a limited mechanism for library method return types where there needed to be a clear way to represent “no result," and using null for such was overwhelmingly likely to cause errors.

So getDepartment() is returning Optional but setDepartment(...) is expecting a Department.

public Optional<Department> getDepartment() {
    return Optional.ofNullable(department);
}

public void setDepartment(Department department) {
    this.department = department;
}
                    

What happen if we set null to department?

package com.dariawan.jdk8.optional;

public class OptionalExample {

    public static void main(String[] args) {
        Employee emp = new Employee();
        emp.setDepartment(null);
        System.out.println(emp);
    }
}
                    

We get following result:

Employee(id=Optional.empty, name=Optional.empty, birthDate=Optional.empty, department=Optional.empty)

Still, we get Optional.empty result.

Retrieving Value with get()

To retrieve the wrapped value in the Optional, we can use get() method:

Employee emp = new Employee();
try {
    System.out.println(emp.getDepartment().get());
}
catch (NoSuchElementException ex) {
    System.out.println(ex.getMessage());
}

emp.setDepartment(new Department(555, "Digital Transformation"));
System.out.println(emp.getDepartment().get());
                    

No value present Department(id=555, name=Digital Transformation)

isPresent() and ifPresent(...)

To check the presence of a value we can use following methods:

public static void main(String[] args) {
    Employee emp = new Employee();

    Optional<Department> od = emp.getDepartment();
    if (od.isPresent()) {
        System.out.println(od.get());
    }
    else {
        System.out.println(od);
    }
}
                    

Optional.empty

But what make it better than following codes?

Department d = emp.getDepartment();  // if not Optional
if (d != null) {
    System.out.println(d);
}
else {
    System.out.println("null department");
}
                    

Yes, we can easily use isPresent similarly on how we do in classical check. It's not the right way to use. When possible, use ifPresent().

Employee emp = new Employee();
emp.setDepartment(new Department());
Optional<Department> od = emp.getDepartment();

od.ifPresent(value -> {
    System.out.println("Value: " + od.get());
});
                    

Value: Department(id=null, name=null)

Get default value or throw Exception with Optional

Typically we need to return a default value if the result of an operation is null. Similarly we can return default value (or throw a Throwable) by using following methods:

You can use the orElse() or orElseGet(...) like in the following example:

Employee emp = new Employee();
Department dept = emp.getDepartment().orElse(new Department(888, "Temp"));
System.out.println(dept);

emp = new Employee();
dept = emp.getDepartment().orElseGet(() -> new Department(999, "Others"));
System.out.println(dept);

emp = new Employee();
emp.setDepartment(new Department(10, "IT"));
dept = emp.getDepartment().orElse(new Department(999, "Others"));
System.out.println(dept);
                    

Department(id=888, name=Temp) Department(id=999, name=Others) Department(id=10, name=IT)

The orElseGet(...) is similar to orElse() but instead of taking a value to return if the Optional value is not present, it takes a supplier functional interface which is invoked and returns the value of the invocation. The following example is orElseGet(...) with supplied function:

OrElseGetExample.java
package com.dariawan.jdk8.optional;

public class OrElseGetExample {

    static Department getNewDepartment() {
        return new Department(999, "Others");
    }
    
    public static void main(String[] args) {

        Employee emp = new Employee();
        Department dept = emp.getDepartment().orElseGet(() -> getNewDepartment());
        System.out.println(dept);
    }
}
                    

Department(id=999, name=Others)

We can also throw an exception if Optional doesn't contain any value by using the orElseThrow():

package com.dariawan.jdk8.optional;

public class OrElseThrowExample {

    public static void main(String[] args) {

        Employee emp = new Employee();
        Department dept = emp.getDepartment().orElseThrow(IllegalStateException::new);
        System.out.println(dept);
    }
}
                    

Exception in thread "main" java.lang.IllegalStateException at java.util.Optional.orElseThrow(Optional.java:290) at com.dariawan.jdk8.optional.OrElseThrowExample.main(OrElseThrowExample.java:8)

Which is more more simpler than:

Employee emp = new Employee();
Optional<Department> od = emp.getDepartment();

if (!od.isPresent()) {
    throw new IllegalStateException();
}
                    

Optional filter method

We can rejecting certain values using:

  • Optional<T> filter(Predicate<? super T> predicate): If a value is present, and the value matches the given predicate, return an Optional describing the value, otherwise return an empty Optional.
package com.dariawan.jdk8.optional;

import java.util.Optional;

public class FilterExample {

    public static void main(String[] args) {
        Employee emp = new Employee();
        emp.setDepartment(new Department(10, "IT"));
        
        Optional<Department> dept = emp.getDepartment();
        dept.filter(d -> "IT".equals(d.getName()))
                .ifPresent(v -> System.out.println("From IT Department"));
    }
}
                    

From IT Department

Transforming Value with map()

map() is used to transform the object. It applies a function to the value contained in the Optional object to transform it.

package com.dariawan.jdk8.optional;

import java.util.Optional;

public class MapExample {
    
    public static void main(String[] args) {
        Employee emp = new Employee();
        emp.setDepartment(new Department(555, "Digital Transformation"));
        
        Optional<Department> od = emp.getDepartment();
        Optional<String> name = od.map(Department::getName);
        System.out.println("Department name: " + name);
        
        Optional<String> optName = Optional.ofNullable(null);
        System.out.println("Map value: " + optName.map(String::toUpperCase));
        
        optName = Optional.of("Fintech");
        System.out.println("Map value: " + optName.map(String::toUpperCase));
        
        Optional<Department> dept = Optional.of(new Department(10, "IT"));
        dept.map(Department::getName)
                .filter(nm -> "IT".equals(nm))
                .ifPresent(v -> System.out.println("From IT Department"));
    }
}
                    

Department name: Optional[Digital Transformation] Map value: Optional.empty Map value: Optional[FINTECH] From IT Department

As you can see, we even can chain map(...), filter (...) and ifPresent(...) together.

Transforming Value with filterMap()

Just like map(...) method, we also have flatMap(...) method as another way for transforming values. The difference is, flatMap(...) can be used to replace unsafe cascading of code to a safe version, but map() only transforms values when they are unwrapped (unsafe).

Employee emp = new Employee();
emp.setId(1);
emp.setName("Bruce Wayne");
emp.setBirthDate(LocalDate.of(1973, Month.JULY, 23));

Optional<Employee> oe = Optional.of(emp);

Optional<Integer> oi = oe.flatMap(Employee::getId);
System.out.println("Id: " + oi.get());

String on = oe.flatMap(Employee::getName)
        .orElse("Unknown");
System.out.println("Name: " + on);

Optional<LocalDate> ob = oe.flatMap(Employee::getBirthDate);
System.out.println("BirthDate: " + ob.get());

Department dept = oe.flatMap(Employee::getDepartment)
        .orElse(new Department(999, "Others"));
System.out.println("Department: " + dept);
                    

Id: 1 Name: Bruce Wayne BirthDate: 1973-07-23 Department: Department(id=999, name=Others)

Let's change Department class:

Department.java
package com.dariawan.jdk8.optional;

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

@ToString
public class Department {

    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 Department() {
    }
    
    public Department(Integer id, String name) {
        this.id = id;
        this.name = name;
    }
}
                    

Then we have this following code:

Employee emp = new Employee();
emp.setDepartment(new Department(555, "Digital Transformation"));

Optional<Employee> oe = Optional.of(emp);
String deptName = oe.flatMap(Employee::getDepartment)
           .flatMap(Department::getName)
           .map(String::toUpperCase)
           .orElse("UNKNOWN");
System.out.println(deptName);
                    

The result is:

DIGITAL TRANSFORMATION

Yes, again... As you can see, we can chain several functions in Optional class altogether to provides optimal result. This is better than traditional checks where we need to check each part for null values.

Conclusion

There are some reason why we choose to use Optional instead of doing explicit null checking and input validation. Another benefit of using Optional is that it improves the readability and convey information which fields are optional which is must raise the awareness that the unwrapped value is possibly null, so we can manage our codes and logic flow correctly.

But remember there is also a chance of over-use Optional, or use it wrong way - even to use it similar like traditional check. Using Optional correctly is not optional but is a must.