on
Java Optional Recipes
In this tutorial, we’ll walk through the Optional class methods that was introduced in Java 8 and improved with new features in the last versions of Java.
About
The optional class is a type of container for a value that may be absent.
Origin
NullPointerException, NullPointerException, NullPointerException… The code below will throw the NullPointerException at runtime if there’s no null checking before using the employee.
Employee employee = findEmployee("1234");
System.out.println("Employee's Name = " + employee.getName());
Also, there’s no difference between null of an absent value and null value resulted after a method call. There is no point to return null values if there is nothing to output.
In both cases, it should be a value type that represents the absence or not of a returned value. Therefore, in Java 8, a new type was added called Optional, which indicates the presence or absence of a value.
Optional has not been invented to get rid of the NullPointerException. But, its principal aim is to deal gracefully with null values in streams.
Optional is mainly used as a method return type when it is clearly necessary to indicate “no result” and the use of null may cause an error. Variables of type Optional must never be null. An Optional should always carry to an Optional instance.
Now, Oracle is advising to use Optional to get rid of the if statement.
When and how?
- Use massively in streams, filter because
filter()copes withnullvalues - Primitive data types can’t be null, don’t do that
Optionalmethod parameters are a code smell- Wrap returned values with
Optional
Create
There are diverse ways of creating Optional objects.
Do
The easiest is empty():
Optional<String> empty = Optional.empty();
We can likewise create an Optional object with the method of():
String name = "Java";
Optional<String> maybeName = Optional.of(name);
In case we expect any null value, we can use the method ofNullable():
String name = ...;
Optional<String> maybeName = Optional.ofNullable(name);
By using ofNullable(), a null reference will not cause an exception but it will return an empty Optional object.
Don’t
The argument passed to the method of() can’t be null. Unless we’ll get a NullPointerException:
String maybeName = Optional.of(null);
Note
Optionalis a container that may hold a value, and it is useless to initialize it withnull.
Get The Value
The get() method is simply used to return a value from the Optional.
Don’t
Suppose the value is not present, then it throws the exception NoSuchElementException.
Optional<Employee> maybeEmployee = repository.getEmployeeById(1234);
Employee employee = maybeEmployee.get();
Do
So it is recommended to first check if the value is present or not before calling get().
Optional<Employee> maybeEmployee = repository.getEmployeeById(1234);
if (maybeEmployee.isPresent()) {
Employee employee = maybeEmployee.get();
... // do something with "employee"
} else {
... // do something that doesn't call employee.get()
}
Note
- The code above is wordy and is not preferable as it looks like the null checking style.
Get The Value Or Else
The method orElse() is used to return a default value if the object is empty.
Don’t
Optional<String> maybeEmployeeStatus = repository.getEmployeeById(1234);
if (maybeEmployee.isPresent()) {
Employee employee = maybeEmployee.get();
... // do something with "employee"
} else {
... // do something else like
Employee employee = Employee.of("0", "Unknown");
}
Do
Optional<Employee> maybeEmployee = repository.getEmployeeById(1234);
Employee employee = maybeEmployee.orElse(new Employee("0", "Unknown"));
Note
- Generally, you should avoid using
orElse(null); - The value returned by
orElse()is always evaluated regardless of the optional value presence. Hence the rule is to applyorElse()when you have already a preconstructed object without expensive calculations.
Get The Value Or ElseGet
The method orElseGet() method accepts a Supplier and it is invoked when Optional is empty.
Don’t
When the employee will be returned from the cache, the database query is still called too. It is very expensive as an operation!
Optional<Employee> getFromCache(int id) {
...
}
Optional<Employee> getFromDB(int id) {
...
}
public Employee findEmployee(int id) {
return getFromCache(id).orElse(
getFromDB(id).orElseThrow(
() -> new NotFoundException("Employee not found with id" + id)
)
);
}
Do
By using orElseGet(), you will get a performance improvement:
public Employee findEmployee(int id) {
return getFromCache(id).orElseGet(
() -> getFromDB(id).orElseThrow(
() -> new NotFoundException("Employee not found with id" + id);
)
);
}
Note
- Don’t even think of using
isPresent()andget()pairs because they are not elegant.
Get The Value Or Throw An Exception
There are situations when you need to throw an exception to show a value doesn’t exist.
Don’t
Employee maybeEmployee = repository.findById(id);
if (maybeEmployee.isPresent())
return maybeEmployee.get();
else
throw new NoSuchElementException();
Do
repository.findById(id).orElseThrow();
repository.findById(id)
.orElseThrow(() -> new EmployeeException("Employee not found with id " + id));
Note
- Generally, it is better to avoid using
orElse(null), but in such a situation, usingorElse(null)is preferable.
Get The Value If Present Only
Sometimes, we need to act only on the wrapped value when an Optional value is present.
Don’t
Optional<String> maybeName = Optional.of("Java");
if (maybeName.isPresent())
System.out.println(maybeName.get().length());
Do
Optional<String> maybeName = Optional.of("Java");
maybeName.ifPresent(s -> System.out.println(s.length()))
Note
- The
ifPresent()method returns nothing.
Get The Value For Empty-based Action
This method allows us to execute an action if the Optional is present or another action if not.
Don’t
Optional<Employee> employee = ... ;
if(employee.isPresent()) {
log.debug("Found Employee: {}" , employee.get().getName());
} else {
log.error("Employee not found");
}
Do
maybeEmployee.ifPresentOrElse(
employee -> log.debug("Found Employee: {}",emp.getName()),
() -> log.error("Employee not found")
);
Note
ifPresentOrElse()is similar toifPresent()with the only one difference which helps to cover theelsebranch as well.
Return Optional
In some cases, if the Optional yields present, then return an Optional describing the value; otherwise, return an Optional provided by the supplying function.
Don’t
public Optional<String> getStatus(int id) {
Optional<String> maybeStatus = ...
if (maybeStatus.isPresent())
return maybeStatus;
else
return Optional.of("Not started yet.");
}
Don’t overuse the orElse() or orElseGet() methods to fulfill this type of operation because both methods return an unwrapped value.
public Optional<String> getStatus(int id) {
Optional<String> maybeStatus = ...
return maybeStatus.orElseGet(() -> Optional.<String>of("Not started yet."));
}
Do
public Optional<String> getStatus(int id) {
Optional<String> maybeStatus = ...
return foundStatus.or(() -> Optional.of("Not started yet."));
}
Note
or()can throwNullPointerExceptionif the supplying function isnullor returnsnull.
Return Presence Status
In some cases, you need to get an Optional status regardless of whether it is empty.
Don’t
public boolean isEmployeeListEmpty(int id){
Optional<EmployeeList> maybeEmployeeList = ... ;
return !maybeEmployeeList.isPresent();
}
Do
You can directly use isEmpty() method, which returns true if the Optional is empty since Java 11.
public boolean isEmployeeListEmpty(int id){
Optional<EmployeeList> maybeEmployeeList = ... ;
return maybeEmployeeList.isEmpty();
}
Note
- Nope
Filter
You can use filter on Optional objects.
Don’t
if(employee != null && employee.getGender().equalsIgnoreCase("MALE")) {
...
// do something
}
Do
maybeEmployee
.filter(user -> employee.getGender().equalsIgnoreCase("MALE"))
.ifPresent(() -> {
...
// do something
});
Note
Extract & Transform
You can use map to extract and transform Optional object values.
Don’t
if (employee != null) {
Address address = employee.getAddress();
if (address != null && address.getCountry().equalsIgnoreCase("TN")) {
System.out.println("Employee belongs to TUNISIA");
}
}
Do
maybeEmployee
.map(Employee::getAddress)
.filter(address -> address.getCountry().equalsIgnoreCase("TN"))
.ifPresent(() -> {
System.out.println("Employee belongs to TUNISIA");
});
Note
- The above code is well readable, concise, and efficient.
Cascading Optional
Employee#getAddress is returning an Optional.
Don’t
Optional<Optional<Address>> maybeAddress =
maybeEmployee.map(Employee::getAddress);
Do
Optional<Address> addressOptional =
maybeEmployee.flatMap(Employee::getAddress)
Note
- The difference is that
maptransforms values only when they are unwrapped whereasflatMaptakes a wrapped value and unwraps it before transforming it.
Conclusion
We have seen what Java Optional is, its benefits, and dilemmas. Use it. But use it wisely. Optional is meant to be used as a return type. Trying to use it as a field type is not recommended. Additionally, we were able to better learn various antipatterns of Optional by studying some illustrative examples.