Using java.util.Optional everywhere. To be or not to be?
The origin of the question
With its default inspection settings, Intellij Idea complains about usage of java.util.Optional
as a field or as a method parameter:

But why such usage of Optional
is not recommended? Here’s a quote from the SO answer that is cited throughout the internet:
Our intention was 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.
Brian Goetz
Not everyone follows this stance though:
- Jackson allows serializing/deserialising objects with
Optional
fields - Spring 4 supports
Optional
as a field and as a method parameter
Fact: Optional is not serializable
Authors of JSR 335 (where the class was introduced) explicitly decided to make it non-serializable. The reasons for that decision are not readily available but here’s what I found on the Internet:
- To avoid lock-in by serialization
- Some speculations that this may stand in a way of future value objects
This fact somewhat complicates the usage of Optional
as a class field. But in reality nothing prevents one from doing so in classes that are not meant to be serialized (which is rarely required these days).
BTW another consequence of this limitation is that Optional
can no longer be used as a return type in remote invocation calls that rely on standard Java serialization.
Further reading on the subject:
- https://mail.openjdk.org/pipermail/jdk8-dev/2013-September/003186.html
- https://nipafx.dev/why-isnt-java-optional-serializable/
What if we ignore the warning and use Optional everywhere?
Benefits
More expressive and consistent code
If we use Optional
in class fields, method parameters, and method return values, it may help making the code self-documented.
For instance, the below record screams that middleName
is not always set:
import java.util.Optional;
public record User(String firstName,
Optional<String> middleName,
String lastName) {
}
This approach should be used consistently though:
- If something can be absent, then the type should be
Optional<X>
- Conversely, if something has type
X
(notOptional<X>
) then it should never be absent
Otherwise, it may do more harm than good.
Access to the fluent API of Optional
In the below example we can enjoy using map
and orElse
:
import java.util.Optional;
public class FluentOptional {
public static void main(String[] args) {
User user1 = new User("Linus", Optional.of("Benedict"), "Torvalds");
User user2 = new User("Martin", Optional.empty(), "Odersky");
System.out.println(shortName(user1)); // Prints "L. B. Torvalds"
System.out.println(shortName(user2)); // Prints "M. Odersky"
}
private static String shortName(User user) {
String firstNameSegment = user.firstName().charAt(0) + ". ";
String middleNameSegment = user.middleName()
.map(s -> s.charAt(0) + ". ")
.orElse("");
String lastname = user.lastName();
return "%s%s%s".formatted(firstNameSegment, middleNameSegment, lastname);
}
}
Soul-soothing for a Clear Architecture adept
It may be undesirable to Entity classes to assume any particular serialization mechanism (including as subtle as the standard Java serialization) because according to The Clean Architecture, Entities should be independent of any specific technology.
Drawbacks
Certainly there’re also (potential) negative consequences of the above approach:
- In case of zealous use, it can backfire and make code less readable, particularly when used in collections:
LinkedHashMap<String, Optional<String>> fileId2OverrideNameMap
- The necessity to disable the warning in IDE
- Impossibility of some performance-optimizations: one, two
An alternative: Checker Framework
Checker Framework provides compile-time nullness checks:
@Nullable Object obj; // might be null
@NonNull Object nnobj; // never null
// ...
obj.toString() // checker warning: dereference might cause null pointer exception
nnobj = obj; // checker warning: nnobj may become null
if (nnobj == null) // checker warning: redundant test
However, the framework requires some magic to setup:
javac \
-J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \
-J--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED \
-J--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED \
-J--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED \
-J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED \
-J--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED \
-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED \
-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED \
-J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED \
-processorpath $CHECKERFRAMEWORK/checker/dist/checker.jar \
-cp $CHECKERFRAMEWORK/checker/dist/checker-qual.jar \
-processor org.checkerframework.checker.nullness.NullnessChecker
I definitely need more time to assess this promising option.
Conclusion
As usual, there’s no definite answer when to use and when not to use Optional
. You need to use your team’s best judgement.
In my personal opinion it is worthwhile to use Optional
throughout codebase for method return types and incoming parameters, as well as class fields, as long as it’s used consistently:
- If something can be absent, then the type should be
Optional<X>
- And vice versa, if something has type
X
(notOptional<X>
) then it should never be absent. - Finally, be careful using
Optional
in collections.