Java 8 Optional

--

In this article, we’ll talk about Java 8 Optional class and how you can take full advantage, through detailed examples.

1. What is Optional in Java?

Optional is a wrapper class that was introduced in Java 8, to have a more elegant way to handle null values. Also Optional works in conjunction with lambdas and streams.

Additionally, when dealing with Optionals you can use directly (meaning you do not have to call stream() method first) methods like map(), filter() and flatMap().

2. How to Create an Optional Object in Java

You can create an optional using either Optional.of(T value), Optional.ofNullable(T value); the difference between the two is that of expects only non-null values and it will throw an exception if the passed value is null.

On the contrary, ofNullable expects some null values and it will not throw a NullPointerException. Now, consider the following example:

static void ofVSofNullable(){
try {
Optional.of(null);
} catch (NullPointerException npe){
System.out.println("Passed null to .of method");
}

Optional.ofNullable(null);
System.out.println("It worked");

}

Of course, this will output the following:

Passed null to .of method
It worked

Here we should note that we can also create an Optional using Optional.empty() which eventually has the same result as Optional.ofNullable(null).

3. Intermediate Methods of Java Optional Class

We consider as intermediate, all the methods that will return an Optional (of course the methods of the previous section do return an optional but we won’t go through them again).

3.1 Optional<T> filter(Predicate<? super T> predicate)

This method accepts a predicate and returns an optional if the predicate was matched.

Consider the following example:

Optional<String> opt = Optional.of("hello").filter(x -> x.contains("ell"));
System.out.println(opt.isEmpty()); // false since "hello" contains "ell"
opt = Optional.of("hello").filter(x -> x.contains("g"));
System.out.println(opt.isEmpty()); // true since "hello" does not contain "g"

The isEmpty() method just returns true if the optional's underlying value is present and false otherwise.

3.2 Optional<U> map(Function<? super T, ? extends U> mapper)

This method accepts a function that has the underlying value of the Optional as a parameter and returns an Optional with the transformed value.

For example:

Optional<Integer> integerOptional = Optional.of("12").map(Integer::parseInt);
System.out.println(integerOptional.get()); // Will print 12

By using the map() method, we successfully transformed an Optional<String> to an Optional<Integer>; then we used the .get() method to retrieve the underlying value.

3.3 Optional<U> flatMap(Function<? super T, ? extends Optional<? extends U>> mapper)

The difference between map and flatMap is that the Function must return an Optional and not the underlying value. Let’s go through an example:

Optional<Integer> optionalInteger = Optional.of("100").flatMap(
x -> Optional.of(Integer.parseInt(x)).filter(z -> z > 50)
);
System.out.println(optionalInteger.get()); // Will print 100

As you can observe, flatMap() allows us to transform a String to an Optional<Integer> and handle it as an optional inside the function; this was not possible with the map function as you must return the new value without wrapping it into an Optional.

Also, if we didn’t have flatMap() and used only map(), we would get the following result when trying to perform the same action:

Optional<Optional<Integer>> wrappedOptional = Optional.of("100").map(
x -> Optional.of(Integer.parseInt(x)).filter(z -> z > 50)
);

3.4 Optional<T> or(Supplier<? extends Optional<? extends T>> supplier)

This method was added in Java 9 and it works as described below:

  • If the optional is empty, then the result produced by the supplier will be returned when you call get()
  • If the optional has value, then the result produced by the supplier is ignored and the underlying value will be returned when you call get()

Let’s see that in action:

Optional<String> filledOptional = Optional.of("Hello").or(() -> Optional.of("It was empty"));
System.out.println(filledOptional.get()); // will print "Hello"

Optional<Object> emptyOptional = Optional.empty().or(() -> Optional.of("It was empty"));
System.out.println(emptyOptional.get()); // will print "It was empty"

4. Terminal Methods of Optional Class in Java

We consider as terminal, all the methods that return either a value that is not wrapped by Optional class or return nothing.

4.1 Void Methods

Optional provides two methods of that type. These are ifPresent and ifPresentOrElse.

4.1.1 void ifPresent(Consumer<? super T> action)

This method acts if the Optional is not empty, consider the example below:

Optional.of("Hello").ifPresent(System.out::println); // will print "Hello"
Optional.empty().ifPresent(System.out::println); // will print nothing

In the first case, the Optional is not empty so “Hello” is printed. However, when we have an empty optional the action will not be performed and it will print nothing.

Here we should not that the Consumer function could do various actions such as setting a value to an object's member.

4.1.2 void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction)

This method was added in Java 9 and allows you to act even if the Optional is empty. Let’s see how this method works:

Optional.of("Hello")
.ifPresentOrElse(
System.out::println,
() -> System.out.println("It was not present")
); // will print "Hello"
Optional.empty()
.ifPresentOrElse(
System.out::println,
() -> System.out.println("It was not present")
); // will print "It was not present"

The first case will print the value as expected since it is not empty, and the second will use the Runnable action since it is empty and print “It was not present”.

4.2 Retrieving The Underlying Value of an Optional

The simplest way to retrieve the underlying value is by using the get() method. Note that if the Optional is empty, you will get a NoSuchElementException so if you are not sure that the value is present, you shouldn't call this method.

For that reason, we have orElse, orElseGet and orElseThrow. But before we start, consider that we have created the following method:

private static String returnDefault(){
System.out.println("Returning the default");
return "Default";
}

4.2.1 T orElse(T other)

By using orElse you can specify what should be returned in case the Optional is indeed empty

Consider the example below:

String res = Optional.of("Hello").orElse(returnDefault());
System.out.println(res);// res is "Hello"
res = (String) Optional.empty().orElse(returnDefault());
System.out.println(res);// res is "Default"

This will print:

Returning the default
Hello
Returning the default
Default

This means that whatever is inside the orElse() will be called regardless of the presence of the Optional value.

Nevertheless, now we won’t get a NoSuchElementException like when we used the get() method.

4.2.2 T orElseGet(Supplier<? extends T> supplier)

Consider this example:

String res = Optional.of("Hello").orElseGet(OptionalExample::returnDefault);
System.out.println(res);// res is "Hello"
res = (String) Optional.empty().orElseGet(OptionalExample::returnDefault);
System.out.println(res);// res is "Default"

This will print:

Hello
Returning the default
Default

This means that orElse and orElseGet have 2 major differences:

  1. orElse accepts a value whereas orElseGet accepts a Supplier function
  2. While orElse will call whatever is the parameter regardless of the presence of the Optional, orElseGet will call the Supplier only if the Optional is absent.

4.2.3 T orElseThrow()

This method works exactly like get() and was introduced in Java 10 so that it is clear what the method does.

For example:

String res = Optional.of("Hello").orElseThrow();
System.out.println(res);// res is "Hello"
try {
res = (String) Optional.empty().orElseThrow();
} catch (NoSuchElementException exception){
exception.printStackTrace();
}

So when the Optional is empty, the same exception that get() method throws will be thrown.

4.2.4 <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier)

This method allows us to throw a custom exception, either one we have created ourselves or a predefined one.

String clh =  Optional.of("CodeLearnHub").orElseThrow(IllegalArgumentException::new);
System.out.println(clh);
try {
clh = (String) Optional.empty().orElseThrow(() -> new IllegalArgumentException("You passed null value"));

} catch (IllegalArgumentException e){
e.printStackTrace();
}

So when we call orElseThrow() from an empty Optional, we’ll get an IllegalArgumentException with the message "You passed null value".

4.3 Miscellaneous methods

The Optional class also has an isEmpty(Since Java 11) and isPresent method. In Addition, since Java 9, Optional class has a stream() method that allows you to perform all the operations the Java Stream API offers.

5. Converting a null check to use Optional

Consider the example below:

private static void nullCheckWithoutOptionals(){
List<String> list = null;
if(Math.random() >= 0.5){
list = Arrays.asList("Hello", "World");
}
if(list != null){
list = list.stream().map(String::toUpperCase).toList();
}
else {
list = Collections.emptyList();
}
list.forEach(System.out::println);
}

By using Math.random, we randomly assign a value to a null list, and then we transform the elements of the list to uppercase. As you can see we have to manually check if the list is null to call the stream() method and return an empty list if the list is null so that list.forEach(System.out::println); doesn't throw NullPointerException.

Let’s use Optional now:

private static void nullCheckWithOptionals(){
List<String> list = null;
if(Math.random() >= 0.5){
list = Arrays.asList("Hello", "World");
}
list = Optional.ofNullable(list)
.map(list1 -> list1.stream().map(String::toUpperCase).toList())
.orElse(Collections.emptyList());
list.forEach(System.out::println);
}

As you can see, with a single line of code, we managed to handle the null values and avoided if-else statements.

6. Conclusion

By now you should be able to take full advantage of Java 8 Optional and eliminate null checks. You can find the source code on our GitHub page.

7. Sources

[1]: Optional (Java SE 11 & JDK 11 ) — Oracle Help Center

--

--

Georgios Nikolaos Palaiologopoulos

Experienced Java Developer | Focused on Backend Software Development with Java & Spring Boot | Technical Writer