Optional Best Practices
Real-world Use Cases
Java introduced a game-changer with the advent of Java 8, the Optional
class. Optional
was designed to address a long-standing problem: handling of potentially null values. In this article, we will explore the best practices and bad practices of Java Optional
to help you to understand when and how to use Optional
If you don’t have a medium membership, you can use this link to reach the article without a paywall.
Best Practices
Use Optional.empty()
If your business requirements force you to return null, use optional as a return type. When a method might not have a meaningful result, consider returning an Optional
. Let’s explore it with an example. If you want to retrieve your user data with the id field, there is a chance that the id value does not belong to any of the stored users. Hence, you can return a null user object. In that case, Optional is the best candidate as a return type.
public User findUserById(int id) {
// this method can return null
}
public Optional<User> findUserById(int id) {
// this method can return Optional.empty
// But it forces the developer to make necessary checks
}
Prefer ifPresent() for Value Extraction
Instead of using get()
to extract a value, use ifPresent(Consumer<? super T> consumer)
to perform an action if the Optional
contains a value. This avoids the risk of NoSuchElementException
. This does not provide any performance advantage but reduces the verbosity of the code.
if (user.isPresent()) {
System.out.println("user: " + user);
}
user.ifPresent(System.out::println);
Consider orElseGet() for Lazy Default Values
If creating the default value is expensive or depends on conditions, use orElseGet(Supplier<? extends T> supplier)
to provide a lazy default value. If you acquire the default value with computation, using orElse()
is not a proper option. Because it always evaluates the provided method to extract the default value regardless of the existence of value in optional.
Use orElseThrow() for Custom Exceptions
When the absence of a value is exceptional, use orElseThrow(Supplier<? extends X> exceptionSupplier)
to throw a custom exception with a descriptive message.
public void updateOrder(long userId, Order order) {
User user = userService.getUserById(userId).orElseThrow(ResourceNotFoundException::new) ;
order.addUser(user);
}
Use with Stream API
Optional
integrates well with the Stream API. When working with streams, you can use methods like filter()
, map()
, and flatMap()
to work with optional values in a functional style.
Optional.of(order)
.filter(o -> o.user != null)
.map(Order::getUser)
.map(User::getAge);
By using functional methods, NullPointerException is easily avoided.
Bad Practices
Using isPresent() as a replacement for the null check
Using isPresent()
function as a replacement for the null check is not a good practice. Instead of using it that way, it is better to use it as an indicator of the existence of the value to decide the next step.
boolean keepVotingOpen = users.stream()
.filter(User::notVoted)
.findFirst()
.isPresent();
When you use the isPresent function for other purposes, you can consider it a bad practice.
Using Optional as Parameters
Using Optional
for method parameters can introduce unnecessary overhead. Optional
is designed to be used for return values, not method parameters.
When you use Optional
as a method parameter, it can make the method signature less clear. It's not immediately obvious whether the method expects the parameter to be present or not, leading to confusion for developers who use your code.
Also if a parameter is expected to be null, method overloading is better approach then using optional as parameter.
// Bad practice
public List<User> getUser(Optional<Boolean> voted) {
// DO something
}
// Good practice
public List<User> getUser() {
return getUser(null);
}
public List<User> getUser(Boolean voted) {
// DO something
}
Using Optional.get directly
The primary purpose of using Optional
is to clearly indicate that a value might be absent and encourage developers to handle that case explicitly. Using get()
bypasses this safety mechanism, defeating the purpose of using Optional
in the first place.
Using as an Object Field
Fields are often used to represent the state of an object. If you use an Optional
field, it may expose the internal state of the object in a way that doesn't respect encapsulation principles. Additionally, it can make it challenging to ensure the immutability of the object, as Optional
itself is mutable (you can change its value).
Moreover, when serializing objects, especially when dealing with older serialization frameworks, Optional
fields can introduce compatibility issues. Some frameworks may not handle them correctly, leading to unexpected behavior when deserializing objects.
public class User {
private String name;
private Optional<Boolean> voted;
}
Nesting Optionals
Avoid nesting Optional
instances within each other. It can make code less readable and harder to understand. Instead, consider using methods like flatMap()
to flatten nested Optional
structures.
// nested optional
Optional<Optional<Integer>> mapped = optional.map(val -> Optional.of(val.length()));
// optional flattened with flatMap
Optional<Integer> flatMapped = optional.flatMap(val -> Optional.of(val.length()));
Overuse of Optional
Use Optional sparingly where appropriate. Don’t reflexively use it everywhere. It’s meant for situations where a value might be absent. For non-nullable values, prefer using the actual type without Optional
. Consider this example. Application needs to show state of an order and there is a method to extract the state of the order. Using Optional as a return type for this method is not meaningful.
public Optional<String> fetchStateBadExample(Order order) {
User user = order.getUser();
return user != null ?
Optional.ofNullable(order.getState()) :
Optional.of("NOT_STARTED");
}
public String fetchStateGoodExample(Order order) {
User user = order.getUser();
return user != null && order.getState() !=null ?
order.getState() :
"NOT_STARTED";
}
Java Optional
is a powerful tool that enables cleaner, safer, and more expressive code by addressing the problem of null values. By following best practices and understanding its capabilities, you can confidently use Optional
to improve your Java codebase and reduce the risk of null pointer exceptions.
👏 Thank You for Reading!
👨💼 I appreciate your time and hope you found this story insightful. If you enjoyed it, don’t forget to show your appreciation by clapping 👏 for the hard work and subscribe
📰 Keep the Knowledge Flowing by Sharing the Article!
✍ Feel free to share your feedback or opinions about the story. Your input helps me improve and create more valuable content for you.
✌ Stay Connected! 🚀 For more engaging articles, make sure to follow me on social media:
🔍 Explore More! 📖 Dive into a treasure trove of knowledge at Codimis. There’s always more to learn, and we’re here to help you on your journey of discovery.