4 Reasons Why You Should Use Java Optional — or Not?
The battle was fierce. The developers were exhausted.
Arguments were fired both ways. Each side had piles of bodies lying on the ground, burned out or collapsed.
Wars were fought over emails, merge requests, and Zoom Calls, but there was no clear winner. Let’s settle the Battle Of Optional once and for all!
Overview
We can use the Optional class to wrap our data and avoid the classical null checks and some of the try-catch blocks.
As a result, we’ll be able to chain method calls and have more fluent, functional code.
On the other hand, using it abusively can lead to a drop in performance and code cluttering.
In this article, we’ll explore some common use cases where we need protection against null values. For each of them, we’ll decide if Optional can be used to simplify our code or if we should stick to the classical null checks.
PS: For the code snippets, we will use the AccountRepository class with the following methods:
public Account get(String username);
public Optional<Account> find(String username);
1. Optional as Method Return Type 👍
One way of using Optional would be to wrap data before returning - if it can potentially be null. This approach was quickly adopted by developers and frameworks such as SpringDataJPA.
As a result, the caller will be aware that the result can be null. Furthermore, this gives the caller some flexibility: for example, it allows him to easily throw a custom exception if the Optional is empty.
Furthermore, if we need to perform additional checks on the data retrieved, Optional provides a nice API for it.
Let’s look into some scenarios and compare the two solutions:
We can notice the expression of the if statement is growing and becoming harder to read. For these use-cases, we can leverage the filter and map methods from the Optional’s API.
2. Wrapping the Getters 👍
As we can notice from the code snippet above, the Account class exposes a getMembershipOptional() method.
Optional<Membership> getMembershipOptional() {
return Optional.ofNullable(membership);
}
Of course, this is not the actual getter or the membership field. But that’s just because I have used the same data model for all the examples. In a real project we’ll either have the classic getter or the one returning Optional, but not both.
Making the getters return Optional can be a good practice for the fields where null is a valid value from a business point of view.
Since this would help us enrich our domain model, we should not apply it to DTO objects. This way, we’ll also avoid potential serialization issues.
3. Wrapping Local Variables For Very Simple Logic👎
Wrapping variables in Optional just for leveraging its API for simple operations is starting to become a common anti-pattern.
For example, using Optional just to inline if statements:
Optional.ofNullable(account)
.ifPresent(acc -> processAccount(acc));
The usage of Optional here is not bringing any value. In this case, we should shamelessly use the classic null check:
if (account != null) {
processAccount(account);
}
Let’s see one more example where the usage of Optional is unnecessary:
var accountType = Optional.ofNullable(account)
.map(acc -> acc.getAccountType())
.orElse(DEFAULT_ACCOUNT_TYPE);
Similarly, we can use a classic if statement or a ternary operator to make this piece of code more readable.
4. Optional Fields and Method Parameters 👎
Optional is not meant to be used to wrap class fields. This will lead to the creation of objects where they are not needed and, if used repeatedly, in a performance drop.
Besides, the Optional class is not serializable. Consequently, having Optional fields can cause serialization issues.
Moreover, using Optional as a method parameter can also be considered an anti-pattern.
Let’s assume we have the following method signature:
createAccount(String userName, Optional<Membership> membership)
If this is the case and the membership parameter can be nullable, I believe it is better to simply overload the method with both versions of it.
This way we can properly check and validate the membership parameter for the method where it’s required:
createAccount(String userName);
createAccount(String userName, Membership membership);
Conclusion
As we have seen together in this article, the Optional class can bring expressivity to our domain model (through getters) and some degree of safety.
Though, if it’s not properly used, it can lead to bad design and code cluttering. Consequently, there are developers sharing the opinion that it should be completely avoided.
My opinion is that with some solid rules established among the team and some proper code reviews, Optional can be a great feature for Java Developers.
Resources
There are plenty of articles on this topic. If you want to dive deeper into the subject, here are, in my opinion, some of the best ones out there:
- Avoiding NullPointerException by Victor Rentea
- Nothing is better than the Optional type by Michael Ernst
- Optional, the Mother of all Bikesheds by Stuart Marks
- 10 Examples of Optional in Java by Javin Paul