True power of Optional in Java
Optional in Java is a container for an object that may or may not hold a value. This is a fact seldom understood amongst Java engineers who use Optional as a ’fancy way’ to perform null checks and to avoid NullPointerException scenarios.
Null — the billion-dollar mistake?
A null is a special value, available as a literal in Java, that can be used to denote no-value. Its behavior is meant to be the same and neutral to any reference type it is assigned to. The concept of null was put forth by the British computer scientist Tony Hoare in the early 1960s but he later coined it as his billion-dollar mistake because, in a way, it laid the groundwork for the creation of malwares first introduced by the C language’s get utility. Therefore, modern programming languages handle null far more efficiently with fail-fast approaches.
But what is wrong with performing a null check the old-fashioned way?
In my opinion — there is nothing wrong with adding null checks where required. The new Optional class added in Java 8 is not to intended to replace every single null reference. Also to note, the improper use of Optional can introduce NullPointerException in your code as we will see in a later section.
Remember, Optional is a container for an object that may or may not contain a value. This basically means, the concept of Optional is used to denote that a particular entity or object may contain a value or may not contain a value. Therefore, Optional gives the power to differentiate between the absence of a value and the null reference. Null can therefore be used to indicate the occurrence of an error in the program flow until that point.
Let me use an example to cover this:
Optional<String> name = Optional.of("John Smith"); /* with a valid value */
This declares an Optional container with the provided value. Alternatively, you can also use the empty() method to denote that there is no value in the container as shown below.
Optional<String> name = Optional.empty(); /* shows the absence of a value */
Optional class also provides a method ofNullable() to create Optional containers with a null value.
Optional<String> name = Optional.ofNullable(null); /* can be a null value */
Suppose you need to print the name when it is present, and a default value when it is absent. You can do this with traditional null checks:
String patronName = .... ; /* may or may not be null */
if(patronName != null) {
System.out.println(patronName);
} else {
System.out.println("Anonymous");
}
And the same can be implemented using Optional as below:
Optional<String> patronName = Optional.ofNullable("John Smith");
System.out.println(patronName.orElse("anonymous"));
In this case, if the patronName contained a non-null value, that value will be printed, otherwise, the string ‘Anonymous’ will be printed.
Isn’t this more concise than the null-check way?
You can find many more examples of using Optional on the web, so let me focus on some of the anti-patterns of using Optional here.
1) Using Optional instead of plain null checks.
Example: Un-necessarily wrapping to Optional instead of plain null checks.
if (Optional.ofNullable(customer.getMiddleName()).isPresent())
instead of
if (customer.getMiddleName()!= null) /* one can also use the StringUtils class methods for a wider range of validations. */
Another Example: nested null checks using isPresent()
if (Optional.ofNullable(customer).isPresent() && Optional.ofNullable(customer.getPhoneNumber()).isPresent()) { savePhoneNumber(customer.getPhoneNumber());....}
2) The isPresent() — get() pattern
During code reviews, I have often seen folks using a combination of ifPresent() and get() methods to safely fetch the value from an Optional object. While this is legally allowed in Java, it is an inefficient use of Optional, often due to the lack of an in-depth understanding of why we use Optional. For example:
if(patronName.isPresent()){ String fullName = patronName.get();....}
You can use the plain old null-checks instead of the isPresent()-get() combination.
3) Using isPresent()-get() instead of ifPresent()
The ifPresent() method is a powerful way to perform an operation based on the presence of a value. Yet, this is sometimes overlooked by the misuse of the isPresent()-get() combination.
For example: using the below snippet
List<CustomerAddress> addresses = new ArrayList<>();
if (customerAddressOptional.isPresent()) {
addresses.add(customerAddressOptional.get());
....
}
instead of the below concise snippet:
customerAddressOptional.ifPresent(addresses::add);
Now, this might look pretty obvious at first but is a common pitfall many young Java engineers fall into when they are used to the if-else paradigm.
4) Using orElse() rather than orElseGet() where necessary
Remember that orElse() method always executes its argument whereas, orElseGet() takes a Supplier as its argument and is executed only when the optional is empty. Understanding the difference between them can be a game-changer especially when the operation being performed is resource-intensive such as a web service call, a database query, etc.
Example: Suppose we have a getDefaultValue() method defined as below:
public String getDefaultValue() {
System.out.println(“Inside getDefaultValue() method”);
return “Anonymous”;
}
Lets look at the behavior of orElse() and orElseGet() here:
/* getDefaultValue() is always executed in this case. */
Optional<String> patronName = Optional.of("John Smith");
System.out.println(patronName.orElse(getDefaultValue()));/* getDefaultValue() is not executed since patronName is present. */
Optional<String> patronName = Optional.of(“John Smith”);
System.out.println(patronName.orElseGet(() -> getDefaultValue()));
Upon executing the above code-snippet, you can see that “Inside getDefaultValue() method” is always printed when used with orElse() even if patronName is present or not.
5) Not leveraging FlatMap to check for nested Optional objects.
For example:
Customer customer = null;String lastname = Optional.ofNullable(customer)
.flatMap(c -> c.lastname)
.orElse(“Not available”);System.out.println(lastname);
6) Declaring parameters of a method as Optional
While this is not strictly a bad idea, it can lead to unnecessarily complicated code on the caller's side. It would be much easier to use overloaded methods to specify non-mandatory parameters.
Example:
public void findNewCustomers(String city, Optional<Integer> numberOfOrders) {
List<Customer> allCustomers = .... ;
allCustomers.stream()
.filter(customer -> customer.city.equals(city))
.filter(customer -> customer.numOrders >=numberOfOrders.orElse(0))
.collect(Collectors.toList());
}
The problem with this approach is that one can invoke the findNewCustomer() method by passing null as the second parameter and it would result in a NullPointerException at numberOfOrders.orElse(0)
To overcome the exception, we will have to put in a null-check on the city attribute, which then defeats the purpose of using Optional in the first place. Instead, we could declare the numberOfOrders parameter as an Integer and perform a null-check to default its value if it is absent during the method execution.
See the below snippet:
public void findNewCustomers(String city, Integer numberOfOrders) {
List<Customer> allCustomers = .... ;
Integer numOfOrders = numberOfOrders !=0 ? numberOfOrders : 0;
allCustomers.stream()
.filter(customer -> customer.city.equals(city))
.filter(customer -> customer.numOrders >=numOfOrders)
.collect(Collectors.toList());
}
Conclusion:
The Optional class introduced in Java 8 provides a container to represent an optional value for a reference. It can therefore be used to better handle the presence or absence of a value instead of using a null reference. Null references can therefore be reserved to indicate a break or an exception in the program flow. Remember that the Optional class was not introduced to avoid NullPointerException in Java and using Optional is not a fancy, sugar-coated way of performing null-checks.
These examples were based on Optional implementation on Java 8 release.
More methods such as or(), ifPresentOrElse(), and stream() have been added in recent Java versions for you to explore…