Since Java 8 appeared a few years ago, along with the introduction of Optional, I have seen many instances of misuse or incorrect use of Optional. In this post, we will discuss Java Optional and best practices, as using Optional correctly is not an option ;).
Brian Goetz provided a good definition of Java Optional in response to a question:
Question: Is it good practice to return Optional<Foo> instead of Foo, considering that the value can be null?
Answer:
Of course, people will do what they want. But we did have a clear intention when adding this feature, and it was not to be a general-purpose Maybe type, as much as many people would have liked us to do so. 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.
For example, you probably should never use it for something that returns an array of results or a list of results; instead, return an empty array or list. You should almost never use it as a field of something or a method parameter.
I think routinely using it as a return value for getters would definitely be overuse.
There’s nothing wrong with Optional that it should be avoided; it’s just not what many people wish it were, and accordingly, we were fairly concerned about the risk of zealous overuse. Brian Goetz
Essentially, what Brian Goetz is saying in this quote is that Optional should not be excessively used, and it’s not bad to avoid its use. It should not be used for getters, lists, or wrapping any object.
Now let’s take a look at a few things we often do with Optional that we should avoid or refrain from using Optional in those cases. Here we go:
Best Practices with Optional
Avoid assigning null to an Optional
One common excessive use of Optional is when we try to use it in the traditional way by assigning null to an Optional:
Optional<User> user = null;
If you need something like the above, it’s better to assign it to empty or not use it:
Optional<User> user = Optional.empty();
Avoid using Optional in Constructors
Keep in mind that Optional is essentially a wrapper, so using it within a constructor adds unnecessary complexity:
Avoid doing this:
public class Car { private final String model; private final Optional<String> color; public Car(String model, Optional<String> color) { this.model = model; this.color = color; } }
Better to do it this way:
public class Car { private final String model; private final String color; public Car(String model, String color) { this.model = model; this.color = color; } }
Avoid using Optional with Lists
It is not recommended to create an Optional of a list since if the list is null, Optional would also be null, and its handling becomes more complex.
Avoid: Optional<List<String>> cars = Optional.of(List.of("ford","renault");
Use: List<String> cars = List.of("ford","renault");
Avoid using Optional as a parameter in a method
Passing a parameter with Optional in a method is counterproductive as it requires adding conditional logic to handle this parameter. Let’s see an example:
Avoid:
public void car(Optional<String> models) { if (models.isPresent()) { doSomething(models.get()); } else { doSomethingElse(); } }
Better option:
public void car(String models) { if (null != models) { doSomething(models); } else { doSomethingElse(); } }
The previous one would be a better option; you can see that the code is quite similar, but computationally speaking, you’re creating an unnecessary wrapper, which is more costly for the compiler.
Using orElse() or orElseThrow()
instead of isPresent() or get()
Instead of having to go through multiple steps to retrieve a value, we can use lambda expressions to chain operations and obtain the value.
Avoid:
List<Car> cars = //lista de coches; Optional<Car> findCar = cars.stream() .filter(car -> c.getColor().equalsIgnoreCase("blue") .findFirst(); if (findCar.isPresent()) { return findCar.get().getModel(); } else { return throw new NotFoundExcepcion("Car blue not found"); }
A better option would be:
List<Car> cars = //lista de coches; Optional<Car> findCar = cars.stream() .filter(car -> c.getColor().equalsIgnoreCase("blue") .findFirst() .map(Car::getModel) .orElseThrow(()-> new NotFoundException("Car blue not found")
Whenever possible, avoid using .isPresent(
)
and chain the operations instead.
Avoid using Optional as a ternary operator
We might think that Optional can be a solution to avoid dealing with null values, but if we apply Optional everywhere, our code becomes more verbose.
Avoid:
return Optional.ofNullable(value).orElse("BLUE");
Better
return value == null ? "BLUE" : value;
Avoid using Optional when returning a method’s value
We should avoid returning a method with an Optional.
Avoid:
public Optional<List<String>> getDoctors(String name) { Hospital hospital = new Hospital (List.of("Pepe", "Juan")) List<String> doctors = hospital.getDoctors(); return Optional.ofNullable(doctors); }
Better:
public List<String> getDoctors(String name) { Hospital hospital = new Hospital (List.of("Pepe", "Juan")) List<String> doctors = hospital.getDoctors(); return doctors!= null ? items: Collections.emptyList(); }
There’s no need to use Optional to return an empty list or return a method as Optional to avoid null values; it should be checked beforehand.
Don’t confuse between Optional.of() and Optional.ofNullable()
It’s quite common to misuse these two concepts. Just remember that Optional.of(null)
will throw a NullPointer exception, while Optional.ofNullable(null)
will result in Optional.empty()
.
Avoid:
public Optional<String> getModelCar() { String modelCar = null; return Optional.of(modelCar); }
If modelCar
is null, it will throw a NullPointer exception.
Better:
public Optional<String> getModelCar() { String modelCar = null; return Optional.ofNullable(modelCar); }
When you’re certain that there’s a value, then use Optional.of("something")
.
Consume an Optional if present, otherwise perform an empty action
Using ifPresentOrElse
with Optional is a good alternative to isPresent()
and get()
.
Avoid:
Optional<String> modelCar = ... ; if(modelCar.isPresent()) { System.out.println("Model is: " + modelCar.get()); } else { System.out.println("Model not found"); }
Better:
Optional<String> modelCar = ... ; modelCar.ifPresentOrElse( System.out::println, () -> System.out.println("Model not found") );
Return null with orElse()
instead of using isPresent()
and else
If we ever need to return null when an Optional doesn’t have a value, it’s better to use the orElse()
option instead of using ifPresent()
followed by an else
statement:
Avoid:
Optional<String> modelCar = ... ; if(modelCar.isPresent()) { return modelCar.get() } else { return null; }
Better
Optional<String> modelCar = ... ; return modelCar.orElse(null);
Avoid using Optional<T>
and use its generics OptionalInt
, OptionalLong
, or OptionalDouble
If you need to use an Optional for an int
, long
, or double
, it’s better to use OptionalInt
, OptionalLong
, or OptionalDouble
. They are computationally less expensive, simplify the code, and are considered best practices:
Avoid:
Optional<Long> value = Optional.of(1L); Optional<Double> value = Optional.of(1.5d); Optional<Integer> value = Optional.of(1);
Better
OptionalDouble value = OptionalDouble.of(1.5d); OptionalLong value = OptionalLong.of(1L); OptionalInt value = OptionalInt.of(1);
Conclusion
In this post, we have tried to analyze the options we have with Java Optional and best practices to make better use of Optional.