How to Use Java's Optional Class
What is Optional?
Optional is a partial replacement for using null. It allows you to represent a type as either containing a value or not containing a value. The Optional class is a wrapper class for a value. Optional is only intended to be used on a method's return type. This helps document the public API (Application Programming Interface) so developers know if a value could be empty or not.
Creating an Optional
There are three different ways of creating an Optional. Using the proper method to create an Optional will help document your code. You will be able to show what the intent was.
No Value
If you are wanting to create an empty Optional, you can use the empty() method.
final var value = Optional.empty();
Use this in place of null.
Non-null Value
If you are wanting to create an Optional with something that is guaranteed to have a value, use the of() method.
final var value = Optional.of("value");
Only use the of() method when you are guaranteed to be working with a non-null value. This will throw a NullPointerException if null is passed to it.
Nullable Value
If you are working with a value that can be null, use the ofNullable() method.
final var value = Optional.ofNullable("value");
This allows for a null value and will not throw a NullPointerException if the value is null.
Knowing if an Optional Has a Value or Not
There are two methods used to determine if an Optional has a value or not. Optional has both methods for if there is a value or if there isn't a value, so you don't need to reverse it using "!".
Contains a Value
If you want to check if the Optional has a value, use the isPresent() method.
if (value.isPresent()) {
// ...
}
Missing a Value
If you want to check if the Optional doesn't have a value, use the isEmpty() method.
if (value.isEmpty()) {
// ...
}
Working With the Value in an Optional
There are multiple ways to get the value from an Optional depending on your use case.
Getting the Value
If you want to get the value from an optional, use the get() method.
if (optionalValue.isPresent()) {
final var actualValue = optionalValue.get();
}
This will throw a NoSuchElementException if there isn't a value.
There are cases where you know you will have a value and just need to get the value from the Optional without checking if it has one first. In this case, since there won't be an if statement present to check if the value is present, you should use the following method instead:
final var actualValue = optionalValue.orElseThrow();
This method will throw a NoSuchElementException if there isn't a value. This helps document the code and tell other developers that it is expected to have a value.
You can supply your own exception type with the following:
final var value = optionalValue.orElseThrow(RuntimeException::new);
Supplying a Default Value
When supplying a default value using an optional, it is equivalent to the following code:
final String value;
if (optionalValue.isPresent()) {
value = optionalValue.get();
} else {
value = "no value";
}
This uses the default value "no value" if there isn't a value. Optionals have this functionality built into them. The code above can be rewritten using the orElse() method with the following:
final String value = optionalValue.orElse("no value");
This returns the value in the optional or the default value if the optional is empty.
You can execute a Supplier function for a default value as well using the orElseGet() method.
final String value = optionalValue.orElseGet(
() -> "no value"
);
Optionals also allow for the default value to be an optional value. You can use the or() method to return an Optional as a default.
final String optional = optionalValue.or(
() -> Optional.of("no value")
)
You can also execute a block of code if there is a value and not execute it if there isn't, using the ifPresent() method. This method takes a Consumer function as a parameter.
optionalValue.ifPresent(
value -> {
// Do something with value.
}
);
You should always try to use one of the methods above before using this. This introduces more complex code where a default value does not.
Conditionally Supplying a Value
If you want to conditionally supply a value, you can use the filter method.
final var value = Optional.of(1)
.filter(v -> v == 1)
.isPresent();
The filter method will be invoked on the optional if it has a value. If the filter method evaluates to true, it will return an Optional with a value. If the filter method evaluates to false, it will return an empty Optional.
Transforming Optionals
It is common to have a type and need to convert it to another type. If you needed to do this with Optionals, you would do it with the following:
final var value = Optional.of(1);
final Optional<String> convertedValue;
if (value.isPresent()) {
convertedValue = Optional.of(
String.valueOf(value.get())
);
} else {
convertedValue = Optional.empty();
}
Optionals have this functionality built into them using the map() method. The map() method takes a Function type as a parameter. The above code could be rewritten as the following:
final var value = Optional.of(1)
.map(String::valueOf);
If you have an Optional that contains an Optional, you can use the flatMap() method instead.
final var value = Optional.of(
Optional.of(1)
)
.flatMap(v -> "value")
.orElse("no value");
If the inner Optional equals 1, then the value "value" will be returned. If it is empty, then "no value" will be returned.
Streams
Optionals also support Java streams, so they have access to the Java Stream API via the stream() method.
How Not to Use Optionals
Java's design of Optional is to use it for return types and nothing else. Since this is what it is designed for, it should be used this way.
If you are coming from a language that uses optionals in place of null, this can be confusing because null is generally fully replaced by an optional type. In Java, optional is a runtime replacement, not a compile-time replacement. You do not have the compiler benefits that you have in other languages.
Class Variables
Using class instance variables that are Optionals will cause problems with serializable classes. Optional isn't a serializable class, so trying to use it in a serializable class will result in a NotSerializableException.
Method and Constructor Parameters
It may seem like a good idea to use Optional for optional method parameters. Consider the following method:
public void doSomething(
final Optional<String> parameter1,
final Optional<String> parameter2,
final Optional<String> parameter3) {
// ...
}
This seems good at first, until you consider how you would invoke this method.
doSomething(
Optiona.of("something"),
Optional.empty(),
Optional.of("something else")
);
This code is very ugly and hard to type. What is even worse is that you can pass null to these parameters. This completely defeats the goal and the trust of an Optional. Using Optional only on return types, you can't run into this.
Conclusion
Optionals are a great replacement for null in return types. They add clarity to the method signature and eliminate having to worry about null. If the signature returns a type, you know it won't be null. If it returns an optional type, you know that the value can potentially be empty.