Java Optional Class: What And How To Use It Properly

Vinod Madubashana
unibench
Published in
5 min readFeb 13, 2024

The Optional class was introduced in Java 8 as a key element in the stream API. Let’s first understand the problem that was caused to introduce this class.

The problem

The stream API was introduced in Java 8 and I assume every Java developer has used this API in their datoday development. Let’s consider a simple example.

List<String> names = List.of("vinod", "madubashana");
names.stream()
.filter(name -> name.startsWith("a"))
.findFirst();

Now that you are designing the stream API, what should be the return value in the above case? No option other than returning null. The streams can be empty or they can become empty in the process of executing intermediate operations like filters as in the above case.

But if it is designed to return null the API does not indicate that it can be null. What if you need to explicitly express that the return value can be empty through the API?

The Solution

The only solution is to introduce a new wrapper class and that’s what is provided natively in the Java 8 using Optional class. It is not a solution to issues related to nulls, it is intended to represent return types of our APIs to indicate that it can be empty which will require care when dealing with them. Before Java 8 we wrote our implementations but now we don’t need to do so. So basically Optional class provides an API to represent a value which also can be empty.

Construction

Optional is a final class and hence we can’t subclass it. The constructors are also private and static factor methods are provided to create instances.

Optional.empty();
Optional.of("vinod");
Optional.ofNullable(null);

The empty method can be used to create empty optional. Never use nulls to reference an Optional instance, always use the empty method to create an empty Optional. If you return a null from a method where the return type is an Optional the whole purpose of introducing Optional is a waste.

The of method can be only used to wrap a non-null reference. If a null value is passed a NullPointerException will be thrown.

The ofNullable method allows to passing of null references. That does not mean that you can consume a null value from an Optional. It allows you to pass a null value but when you pass a null value it returns an empty Optional, not an Option which contains a null value.

Java Serialization

This is not a serializable class. So you can’t use Java serialization. This is intentionally done to support value objects which is a part of Project Valhalla, in the near future.

Specialized Optionals

To avoid boxing and unboxing specialized Optional classes are available like OptionalInt, OptionalLong, and OptionalDouble. Use these classes when applicable for better performance.

Consume an Optional

String value = optional.get();
if (optional.isPresent()) {
String value1 = optional.get();
}
optional.ifPresent(val -> System.out.println(val));
optional.ifPresentOrElse(System.out::println,
() -> System.out.println("no value"));
String alternative = optional.orElse("alternative");
String alternative1 = optional.orElseGet(() -> "alternative");
Optional<String> alternativeOptional = optional.or(
() -> Optional.of("alternative"));
String value2 = optional.orElseThrow();
String value3 = optional.orElseThrow(
() -> new RuntimeException("exception"));

The API is self-exploratory. Let’s understand some important points regarding using these methods.

The get method is the simplest way and the way you try to avoid always. This will throw NoSuchElementException when you call it for empty optional. This also suggests that this is not a solution for handling NullPointerException. It is always need to call isPresent or isEmpty methods to check the emptiness and then call the get method. But still, there are other better alternative ways available.

ifPresent method simplifies this check and concisely operates using a consumer. The consumer will be only invoked if the optional is non-empty. ifPresentOrElse accepts a Runnable as a second argument which can be used to run some logic when optional is empty.

orElse and orElseGet can be used to get a default value when the optional is empty. or method becomes handy when your return type is optional and you need to return a new option when the planned option to return becomes empty.

Finally, it is easy to throw runtime exceptions using orElseThrow methods.

Operations

Another advantage of having Optional rather than returning null is the ability to run transformation operations without null checks. It provides the ability to write a fluent chain of method calls. See the below example.

String name = optional.filter(name -> name.startsWith("ab"))
.map(String::toUpperCase)
.orElse(null);

Hope this is also self-exploratory. Another operation available is flatmap which can be used to flatten nested Optionals similar to flatmap operation in stream API.

The stream method is also available which can convert Optional into a Java stream. Then it is possible to use all the stream-related operations.

Best Practices

  • The intended and best place to use it is in the method return type. You might already experience this when using many open-source libraries.
  • Not suitable for method arguments and constructor arguments. This is because it clutters the client code and adds unnecessary complexity.
  • Not suitable as collection elements. This also makes it harder to read when we write the consume operations in stream pipelines.
  • Don’t wrap collections using optional. Instead, return an empty collection object by using some helper methods like Collections.emptyList()
  • Don’t try to be clever using tricks like wrapping objects that can be null and the use optional class API to handle the code, simple null checks will do the same thing for you which yields much more readable code.
  • No suitable class fields. This is an overuse of optional. The optional is a wrapper which means it consumes more memory than the normal references. Overusing them can slow down your application. If the using class is serializable then we have another problem because optional are not serializable. Instead, you can return optional from the getter methods to indicate that fields can be empty.
  • Try to avoid nested optional which makes code unreadable when consuming such optional.
  • Don’t use identity-sensitive operations like equality check using == which can give unpredictable results. The equals method works as expected by comparing values inside optional. Hence it can be used in unit test assertions like assertEquals without getting value out of optional.

Resources

--

--

Vinod Madubashana
unibench

Full-stack Developer, Java, Spring Boot, React, Angular, AWS ...