Java 8 Optional Basics Tutorial with Examples
Introduction
This tutorial covers the
Definition of
By returning an
Comparison: Defensive Null-Checks vs. Optional's Fluent API
To see the difference in readability, let's look at a nested object hierarchy where we want to retrieve a User's Country ISO Code. Each of
Now, compare this with the Java 8 approach using
Each
OUTPUT of the above code
Explanation of the code
OUTPUT of the above code
Explanation of the code
Default values and exceptions: orElse(), orElseGet(), orElseThrow()
Rather than checking for presence, it is usually cleaner to tell the Optional what to do when it is empty -
OUTPUT of the above code
Explanation of the code
OUTPUT of the above code
Explanation of the code
java.util.Optional<T> class introduced in Java 8, which was designed to address the age-old problem of NullPointerExceptions. We will start by examining the inherent flaws of the null reference in Java and how it leads to logic buried under layers of null checks. We will then go through the methods of Optional API for creating Optional instances, consuming their values, providing defaults, and transforming them in a fluent, functional style using map(), flatMap() and filter() methods. We will end with best practices on when to use, and when not to use, Optional.
Null and NullPointerException: The Billion Dollar Mistake
Sir Tony Hoare, who introduced null references in 1965, later called them his "billion-dollar mistake". Every Java programmer has seen the kind of issues the value null and the associated NullPointerException bring with them. The value null represents the absence of a value, which in itself is useful. However, the fact that at runtime any reference can be null presents two unique challenges -
- Ubiquity of NPEs: A
NullPointerExceptioncan be thrown by any object reference at runtime. This leads to unstable production systems where unhandled edge cases crash the application simply because an attribute was accessed without a null check. - Lack of Type Safety: There is no way to design a method signature that explicitly informs the programmer whether a return value could be null. This forces developers into a highly defensive coding style, checking every object before use.
Optional has been designed to solve these challenges by indicating, by its very definition, that a value may be absent. An Optional instance is essentially a container that either holds a non-null value or is empty.Definition of
Optional class in java.util package is -public final class Optional<T> extends ObjectOptional<T>, an API author communicates to the consumer - "I might return a value of type T, or I might return nothing. You must handle both cases." The possible absence of a value thus becomes part of the method's signature, and the compiler makes sure the caller deals with it.
User, Address and Country could be null.
Java 7 style: Nested Null Checks (The Pyramid of Doom)
//Traditional approach - hard to read and maintain
public String getIsoCode(User user) {
if (user != null) {
Address address = user.getAddress();
if (address != null) {
Country country = address.getCountry();
if (country != null) {
return country.getIsocode();
}
}
}
return "UNKNOWN";
}
Optional. The focus moves from checking for failure to describing the transformation -
Java 8 style: Using Optional for Clean Code
//Modern approach - expressive and safe
public String getIsoCode(User user) {
return Optional.ofNullable(user)
.map(User::getAddress)
.map(Address::getCountry)
.map(Country::getIsocode)
.orElse("UNKNOWN");
}
map() call is applied only if the previous step produced a value. As soon as any step returns null, the chain short-circuits to an empty Optional, and orElse() supplies the fallback value "UNKNOWN". We will look at each of these methods in detail in the following sections.
Creating Optional instances
Optional has no public constructor. Instead, it provides 3 static factory methods to create its instances -
Optional.empty()- returns an empty Optional which holds no value.Optional.of(T value)- returns an Optional holding the given non-null value. It throws aNullPointerExceptionif the value passed is null.Optional.ofNullable(T value)- returns an Optional holding the value if it is non-null, otherwise returns an empty Optional.
Java 8 code showing creation of Optional instances
//OptionalCreationExample.java
import java.util.Optional;
public class OptionalCreationExample {
public static void main(String args[]) {
//An empty Optional
Optional<String> emptyOptional = Optional.empty();
System.out.println("emptyOptional -> " + emptyOptional);
//An Optional holding a non-null value
Optional<String> nameOptional = Optional.of("Tom Jones");
System.out.println("nameOptional -> " + nameOptional);
//An Optional from a value which may be null
String nullName = null;
Optional<String> nullableOptional = Optional.ofNullable(nullName);
System.out.println("nullableOptional -> " + nullableOptional);
//Optional.of() does not accept null
try {
Optional<String> invalidOptional = Optional.of(nullName);
} catch (NullPointerException npe) {
System.out.println("Optional.of(null) threw a NullPointerException");
}
}
}
emptyOptional -> Optional.empty nameOptional -> Optional[Tom Jones] nullableOptional -> Optional.empty Optional.of(null) threw a NullPointerException
Optional.empty()creates an empty Optional which, when printed, shows asOptional.empty.Optional.of()wraps the String"Tom Jones". Printing it shows the wrapped value asOptional[Tom Jones].Optional.ofNullable()is passed a null reference, so it returns an empty Optional instead of throwing an exception.- Passing the same null reference to
Optional.of()throws aNullPointerException. UseOptional.of()only when you are certain the value is non-null, andOptional.ofNullable()otherwise.
boolean isPresent()- returnstrueif a value is present, elsefalse.T get()- returns the value if present, else throws aNoSuchElementException.void ifPresent(Consumer<? super T> consumer)- executes the given ConsumerClick to read tutorial on Consumer Functional Interface with the value if a value is present, and does nothing otherwise.
Employee and Department classes. Note that an employee may not have been assigned a department yet, which Employee.getDepartment() communicates by returning an Optional<Department>.
Employee and Department classes used in the examples
//Employee.java
import java.util.Optional;
public class Employee {
private String name;
private Integer age;
private Department department;
public Employee(String name, Integer age, Department department) {
this.name = name;
this.age = age;
this.department = department;
}
public String getName() {
return name;
}
public Integer getAge() {
return age;
}
//department is optional - an employee may not be assigned one yet
public Optional<Department> getDepartment() {
return Optional.ofNullable(department);
}
}
//Department.java
public class Department {
private String name;
public Department(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Department[" + name + "]";
}
}
Java 8 code showing isPresent(), get() and ifPresent() methods
//OptionalConsumptionExample.java
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
public class OptionalConsumptionExample {
static List<Employee> employeeList =
Arrays.asList(new Employee("Tom Jones", 45, new Department("Sales")),
new Employee("Harry Major", 25, null));
//Return type tells the caller that an employee may not be found
static Optional<Employee> findEmployee(String name) {
return employeeList.stream()
.filter(emp -> emp.getName().equals(name))
.findFirst();
}
public static void main(String args[]) {
//isPresent() & get()
Optional<Employee> tomOptional = findEmployee("Tom Jones");
if (tomOptional.isPresent()) {
System.out.println("isPresent() & get() -> " + tomOptional.get().getName());
}
//ifPresent() executes the Consumer only if a value is present
findEmployee("Tom Jones")
.ifPresent(emp -> System.out.println("ifPresent() -> " + emp.getName()));
findEmployee("Nancy Smith")
.ifPresent(emp -> System.out.println("This line is never printed"));
System.out.println("Is Nancy Smith present? " + findEmployee("Nancy Smith").isPresent());
}
}
isPresent() & get() -> Tom Jones ifPresent() -> Tom Jones Is Nancy Smith present? false
findEmployee()searches the employee list using the findFirst() method of Streams APIClick to read tutorial on findFirst() and findAny() methods, which itself returns anOptional<Employee>. The return type makes it clear to every caller that an employee may not be found.- The first block checks
isPresent()before callingget(). This works, but is not much better than a null check. ifPresent()is the more idiomatic alternative. The lambda passed to it is executed for"Tom Jones", while for"Nancy Smith", who is not in the list, it is simply skipped. Hence"This line is never printed"does not appear in the output.
T orElse(T other)- returns the value if present, otherwise returnsother.T orElseGet(Supplier<? extends T> other)- returns the value if present, otherwise invokes the given SupplierClick to read tutorial on Supplier Functional Interface and returns its result.<X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier)- returns the value if present, otherwise throws the exception created by the given Supplier.
Java 8 code showing orElse(), orElseGet() and orElseThrow() methods
//OptionalDefaultsExample.java
import java.util.Optional;
public class OptionalDefaultsExample {
public static void main(String args[]) {
Optional<String> emptyName = Optional.empty();
Optional<String> tomName = Optional.of("Tom Jones");
//Fallback values when the Optional is empty
System.out.println("orElse() -> " + emptyName.orElse("Unknown"));
System.out.println("orElseGet() -> " + emptyName.orElseGet(() -> "Generated Name"));
//orElse() vs orElseGet() when a value IS present
System.out.println("orElse() on Tom -> " + tomName.orElse(getDefaultName()));
System.out.println("orElseGet() on Tom -> " + tomName.orElseGet(OptionalDefaultsExample::getDefaultName));
//orElseThrow() throws the supplied exception if empty
try {
emptyName.orElseThrow(() -> new IllegalArgumentException("Employee name not found"));
} catch (IllegalArgumentException iae) {
System.out.println("orElseThrow() -> " + iae.getMessage());
}
}
static String getDefaultName() {
System.out.println(" ..getDefaultName() invoked");
return "Default Name";
}
}
orElse() -> Unknown orElseGet() -> Generated Name ..getDefaultName() invoked orElse() on Tom -> Tom Jones orElseGet() on Tom -> Tom Jones orElseThrow() -> Employee name not found
- On the empty
emptyNameOptional,orElse()returns the fallback value"Unknown", whileorElseGet()invokes its Supplier lambda to produce"Generated Name". - The important difference between
orElse()andorElseGet()shows up when a value IS present. The argument toorElse()is an ordinary method argument, sogetDefaultName()is evaluated beforeorElse()is even called - even though OptionaltomNameholds a value and the default is discarded. This is why"..getDefaultName() invoked"is printed.orElseGet(), on the other hand, invokes its Supplier only when the Optional is empty, sogetDefaultName()is not called the second time. - Hence, use
orElse()for constants or already-computed values, andorElseGet()when computing the default is expensive or has side effects, such as a database or network call. orElseThrow()throws theIllegalArgumentExceptioncreated by its Supplier, asemptyNameis empty. This is the right choice when a missing value is truly an error condition.
<U> Optional<U> map(Function<? super T, ? extends U> mapper)- applies the given FunctionClick to read tutorial on Function Interface to the value if present, and returns an Optional describing the result. If the function returns null, an empty Optional is returned.<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper)- similar tomap(), but for functions which themselves return an Optional. It returns that Optional directly instead of wrapping it in another Optional.Optional<T> filter(Predicate<? super T> predicate)- returns the same Optional if a value is present and it matches the given PredicateClick to read tutorial on Predicate Functional Interface, otherwise returns an empty Optional.
Java 8 code showing map(), flatMap() and filter() methods
//OptionalTransformationExample.java
import java.util.Optional;
public class OptionalTransformationExample {
public static void main(String args[]) {
Employee tom = new Employee("Tom Jones", 45, new Department("Sales"));
Employee harry = new Employee("Harry Major", 25, null);
//map() - transform the value inside the Optional
Optional<String> upperName = Optional.of(tom)
.map(Employee::getName)
.map(String::toUpperCase);
System.out.println("map() -> " + upperName.orElse("Unknown"));
//map() with a getter that itself returns an Optional gives a nested Optional
Optional<Optional<Department>> nestedDept = Optional.of(tom).map(Employee::getDepartment);
System.out.println("map() with Optional getter -> " + nestedDept);
//flatMap() - flattens Optional<Optional<Department>> into Optional<Department>
String tomDept = Optional.of(tom)
.flatMap(Employee::getDepartment)
.map(Department::getName)
.orElse("No Department");
String harryDept = Optional.of(harry)
.flatMap(Employee::getDepartment)
.map(Department::getName)
.orElse("No Department");
System.out.println("flatMap() -> Tom's department: " + tomDept);
System.out.println("flatMap() -> Harry's department: " + harryDept);
//filter() - keep the value only if it matches the Predicate
Optional.of(tom)
.filter(emp -> emp.getAge() > 40)
.ifPresent(emp -> System.out.println("filter() -> " + emp.getName() + " is above 40"));
boolean isHarryAbove40 = Optional.of(harry)
.filter(emp -> emp.getAge() > 40)
.isPresent();
System.out.println("filter() -> Is Harry Major above 40? " + isHarryAbove40);
}
}
map() -> TOM JONES map() with Optional getter -> Optional[Optional[Department[Sales]]] flatMap() -> Tom's department: Sales flatMap() -> Harry's department: No Department filter() -> Tom Jones is above 40 filter() -> Is Harry Major above 40? false
map(): Twomap()calls are chained using method referencesClick to read tutorial on Method References. The first convertsOptional<Employee>toOptional<String>holding the name, and the second converts the name to uppercase.- Why
flatMap()is needed:Employee.getDepartment()already returns anOptional<Department>. Passing it tomap()wraps that Optional into another one, giving the awkward nested typeOptional<Optional<Department>>, as seen in the output. flatMap()flattens this into a simpleOptional<Department>, on whichmap(Department::getName)is then applied. For Tom, the chain yields"Sales". Harry has no department, soflatMap()returns an empty Optional, the subsequentmap()is skipped, andorElse()returns"No Department". No null checks were needed anywhere.filter(): Tom's age of 45 satisfies the predicateemp -> emp.getAge() > 40, so the Optional remains non-empty andifPresent()prints the message. Harry's age of 25 does not, sofilter()returns an empty Optional andisPresent()returnsfalse.
map(), flatMap() and filter() of Optional work the same way as their namesakes in the Streams API. You can think of an Optional as a stream which holds at most one element. To learn more, read Mapping with Streams using map and flatMap methodsClick to read how Mapping with Java 8 Streams works.
Best practices for using Optional
Optional is a powerful tool, but it was designed for a specific purpose - as a return type for methods which may not have a result to return. Following are some guidelines for using it well -
- Never return null from a method whose return type is Optional. Return
Optional.empty()instead. A null Optional defeats the entire purpose of using one. - Avoid calling
get()without checking for a value. An uncheckedget()simply replaces aNullPointerExceptionwith aNoSuchElementException. PreferorElse(),orElseGet(),orElseThrow()orifPresent(). - Do not use Optional for fields or method parameters. Optional is not
Serializable, and an Optional parameter forces every caller to wrap its arguments. Use overloaded methods, or plain null checks internally, instead. - Do not wrap collections in Optional. Return an empty collection instead of
Optional<List<T>>, since an empty list already represents "no results". - Use the primitive variants for primitive values.
OptionalInt,OptionalLongandOptionalDoubleavoid the boxing overhead ofOptional<Integer>,Optional<Long>andOptional<Double>. - Prefer
orElseGet()overorElse()when the default value is expensive to compute, as explained earlier.
ifPresentOrElse(), or() and stream() in Java 9, the no-argument orElseThrow() in Java 10, and isEmpty() in Java 11. This tutorial covers the Java 8 API, which forms the basis for all of these.
Summary
In this tutorial we saw how the Optional class acts as a powerful alternative to the null reference. We looked at how to create Optional instances using empty(), of() and ofNullable(), how to consume their values using isPresent(), get() and ifPresent(), and how to provide defaults using orElse(), orElseGet() and orElseThrow(). We then saw how map(), flatMap() and filter() allow writing null-safe code in a fluent style, and ended with best practices for using Optional.Continue Reading Java 8 Tutorials
Java 8 Lambda Expressions TutorialClick to read Lambda Expressions Tutorial Java 8 Method References TutorialClick to read tutorial on Method References Java 8 Streams API TutorialClick to read tutorial on Streams API Basics Mapping with Streams using map and flatMap methodsClick to read how Mapping with Java 8 Streams works Stream API's findFirst, findAny methods tutorialClick to read tutorial on findFirst() and findAny() methods java.util.function package overviewClick to read overview of java.util.function package
Java 8 Lambda Expressions TutorialClick to read Lambda Expressions Tutorial Java 8 Method References TutorialClick to read tutorial on Method References Java 8 Streams API TutorialClick to read tutorial on Streams API Basics Mapping with Streams using map and flatMap methodsClick to read how Mapping with Java 8 Streams works Stream API's findFirst, findAny methods tutorialClick to read tutorial on findFirst() and findAny() methods java.util.function package overviewClick to read overview of java.util.function package