Using Java Optional Vs. Vavr Option
Learn more about implementing Java Optionals, as well at the Vavr library's alternative, Option.
Join the DZone community and get the full member experience.
Join For FreeToday, I would like to discuss an essential Java topic – the usage of Optional
class — and compare it with an alternative from the Vavr library. Optional was initially introduced in Java 8 and defined as “a container object which may or may not contain a non-null value.”
You may also like: 26 Reasons Why Using Optional Correctly Is Not Optional
Developers utilize Optionals in order to avoid null checking in places when code execution leads to "not a result" but also to a null value, and it can, in this regard, result in a NullPointerException
. In such cases, Optional offers us some fancy functionality, but not all of it was introduced in the 8th release, some features require Java 11. Another way to handle these issues is with Vavr’s Option
class.
In this post, we learn how to use Java’s Optional
class, and then compare it to Vavr’s Option
class. Note: this code requires Java 11 + and was tested with Vavr 0.10.2.
Let's get started.
Introducing Java Optional
The concept of Optional is not new and has been already implemented in functional programming languages like Haskell or Scala. It proves to be very useful when modeling cases when a method call could return an unknown value or a value that does not exist (e.g. nulls). Let's see how to handle it.
Creation of Optional
First things first, we need to obtain an Optional’s instance. There are several ways to do it – and moreover, we also can create an empty Optional. Check out the first method – creating from value, it is pretty straightforward:
Optional<Integer> four = Optional.of(Integer.valueOf(4));
if (four.isPresent){
System.out.println("Hoorayy! We have a value");
} else {
System.out.println("No value");
}
We build an Optional
from an Integer
value of 4, meaning that there always should be a value and it cannot be null, but this is just an example. We check an existence or absence of value with the ifPresent()
method. You can note that four
is not an Integer
; it is a container that holds integer inside. When we are sure that the value is inside, we can “unpack” it with the get()
method. Ironically, if we use get()
without checking, we can end it with NoSuchElementException
.
Another way to obtain an Optional
is using streams. Several terminal stream’s methods return Optionals, so we can manipulate them and check their existence or absence, for example:
findAny
findFirst
max
min
reduce
Check out the code snippet below:
Optional<Car> car = cars.stream().filter(car->car.getId().equalsIgnoreCase(id)).findFirst();
The next option is to create Optional
from code that can potentially produce null, e.g. from Nullable
:
Optional<Integer> nullable = Optional.ofNullable(client.getRequestData());
Finally, we can create an empty Optional
:
Optional<Integer> nothing = Optional.empty();
How to Use Optional
As long as we obtained Optional
, we can use it. One of the most widespread cases is using it in Spring repositories to find one record by Id, so we can build our logic on Optional
and avoid null checking (btw, Spring also supports Vavr Options). Let's say we have a book repository and want to find one book.
Optional<Book> book = repository.findOne("some id");
First of all, we can execute some logic, in this case, if the book is presented. We did it with if-else
in the previous section, but we don’t need to: Optional
provides us a method that accepts a Consumer with the object:
repository.findOne("some id").ifPresent(book -> System.out.println(book));
Or, we can make this even simpler; we can write the same with method references:
repository.findOne("some id").ifPresent(System.out::println);
If we don’t have a book in the repository, we can provide an alternative callback with theifPresentOrElseGet
method:
repository.findOne("some id").ifPresentOrElseGet(book->{
// if value is presented
}, ()->{
// if value is absent
});
Alternatively, we can get another value if the result of the operation is not presented:
Book result = repository.findOne("some id").orElse(defaultBook);
However, with Optionals, we need to remember possible drawbacks. In the last example, we “guarantee” ourselves that we could obtain a book anyway; either it presents the underlying repository or comes from orElse
. But what if this default value is not constant, but also requires some complex method? First, Java anyway evaluates findOne
. Then, it has to process the orElse
method. Yes, if it is just a default constant value, it is OK, but it can be, as I said before, time-consuming as well.
Another Example
Let's create a simple example to check how to practically use Optional
and Option
classes. We would have a CarRepository
that would find a car based on the supplied ID (e.g. on the license plate number). Then, we would see how to manipulate Optionals and Options.
First, Let's Add Some Code
Start with the POJO class Car
. It follows the immutable pattern, so all fields are final and we have only getters without setters. All data is supplied during initialization.
public class Car {
private final String name;
private final String id;
private final String color;
public Car (String name, String id, String color){
this.name = name;
this.id = id;
this.color = color;
}
public String getId(){
return id;
}
public String getColor() {
return color;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Car "+name+" with license id "+id+" and of color "+color;
}
}
The second thing is to create the CarRepository
class. It requires two options for finding car by Id — using the old way with a possible null result and using Optional
, as we do in Spring repositories.
public class CarRepository {
private List<Car> cars;
public CarRepository(){
getSomeCars();
}
Car findCarById(String id){
for (Car car: cars){
if (car.getId().equalsIgnoreCase(id)){
return car;
}
}
return null;
}
Optional<Car> findCarByIdWithOptional(String id){
return cars.stream().filter(car->car.getId().equalsIgnoreCase(id)).findFirst();
}
private void getSomeCars(){
cars = new ArrayList<>();
cars.add(new Car("tesla", "1A9 4321", "red"));
cars.add(new Car("volkswagen", "2B1 1292", "blue"));
cars.add(new Car("skoda", "5C9 9984", "green"));
cars.add(new Car("audi", "8E4 4321", "silver"));
cars.add(new Car("mercedes", "3B4 5555", "black"));
cars.add(new Car("seat", "6U5 3123", "white"));
}
}
Note that we also populate our repository with some mock cars during initialization, so we don’t have any underlying database. We need to avoid complexities, so let's concentrate on Optional
and Option
, not on repositories.
Finding Cars With Java Optional
Create a new test with JUnit:
@Test
void getCarById(){
Car car = repository.findCarById("1A9 4321");
Assertions.assertNotNull(car);
Car nullCar = repository.findCarById("M 432 KT");
Assertions.assertThrows(NullPointerException.class, ()->{
if (nullCar == null){
throw new NullPointerException();
}
});
}
The above code snippet demonstrates the old way. We found a car with the Czech license plate 1A9 4321 and checked that it exists. Then we found a car that is absent, as it has a Russian license plate and we only have Czech ones in our repository. It is null, so it may lead to NullPointerException
.
Next, let's move to Java Optionals. The first step is to obtain an Optional
instance from the repository using a dedicated method that returns Optional
:
@Test
void getCarByIdWithOptional(){
Optional<Car> tesla = repository.findCarByIdWithOptional("1A9 4321");
tesla.ifPresent(System.out::println);
}
In this case, we use the findCarByIdWithOptional
method and then print a car (if it presents). If you run it, you will get the following output:
Car tesla with license id 1A9 4321 and of color red
But what if we don’t have that special method in our code? In this situation, we can obtain Optional
from a method that could potentially return a null value. It is called nullable
.
Optional<Car> nothing = Optional.ofNullable(repository.findCarById("5T1 0965"));
Assertions.assertThrows(NoSuchElementException.class, ()->{
Car car = nothing.orElseThrow(()->new NoSuchElementException());
});
In this code snippet, we found another way. We create Optional
from findCarById
and that can return null if no car is found. We manually use the orElseThrow
method to throw a NoSuchElementException
when the desired car with the license plate 5T1 0965 is present. Another situation is to use orElse
with a default value if the requested data is not available in the repository:
Car audi = repository.findCarByIdWithOptional("8E4 4311")
.orElse(new Car("audi", "1W3 4212", "yellow"));
if (audi.getColor().equalsIgnoreCase("silver")){
System.out.println("We have silver audi in garage!");
} else {
System.out.println("Sorry, there is no silver audi, but we called you a taxi");
}
Ok, we don’t have the silver Audi in our garage, so we have to call a taxi!
Finding Cars With the Vavr Option
Vavr Option
is another way to handle these tasks. First, install Vavr in your project with dependency management (I use Maven):
<dependency>
<groupId>io.vavr</groupId>
<artifactId>vavr</artifactId>
<version>0.10.2</version>
</dependency>
In a nutshell, Vavr has similar APIs to create Option
instances. We can create Option
from nullable, as shown here:
Option<Car> nothing = Option.of(repository.findCarById("T 543 KK"));
Or we can create an empty container with thenone
static method:
Option<Car> nullable = Option.none();
Also, there is a way to create Option
from Java Optional
! Take a look at the code snippet below:
Option<Car> result = Option.ofOptional(repository.findCarByIdWithOptional("5C9 9984"));
With Vavr Option
, we can use the same API as with Optional
to accomplish the previously mentioned tasks. For example, we can set a default value in a similar manner:
Option<Car> result = Option.ofOptional(repository.findCarByIdWithOptional("5C9 9984"));
Car skoda = result.getOrElse(new Car("skoda", "5E2 4232", "pink"));
System.out.println(skoda);
Or we can throw an exception based on an absence of requested data:
Option<Car> nullable = Option.none();
Assertions.assertThrows(NoSuchElementException.class, ()->{
nullable.getOrElseThrow(()->new NoSuchElementException());
});
Alternatively, we can perform an action when data is unavailable:
nullable.onEmpty(()->{
///runnable
});
What if we need to perform an action based on a presence of data, as we did with Optional’s ifPresent
? We can do this in several ways. There is an equal method toisPresent
in Optional
that in Option
is called isDefined
:
if (result.isDefined()){
// do something
}
However, we use Option
to get rid of if-else constructs. Can we float in the same way as with Optional
? We can perform operations based on existence with peek
:
result.peek(val -> System.out.println(val)).onEmpty(() -> System.out.println("Result is missed"));
Also, there are some other very useful methods in Vavr Option
that can make your code even more functional than with the built-in Optional
class. So, I encourage you to take some time and explore Vavr Option javadocs and experiment with these APIs. I will go ahead and note some cool features like map
, narrow
, isLazy
, and when
that you definitely need to check out.
Also, Vavr Option
is a part of the Vavr family and is heavily integrated with other Vavr classes, and making such a comparison with Optional in the absence of such classes is not correct. So, I also plan to write other posts on Vavr topics like Try, Collections, and Streams. Stay tuned!
Conclusion
In this post, we talked about the Optional
class in Java. The concept of Optional
is not something new and has been already implemented in other functional programming languages like Haskell and Scala. It proves to be very useful when modeling cases when a method call could return an unknown value or a value that does not exist (e.g. nulls). Then, we explored its APIs and created some examples finding cars and manipulating results with Optional
logic. And finally, we discovered an alternative to Optional
– Vavr’s Option
and described its methods as well.
Hope you enjoyed! Be sure to leave thoughts or questions in the comments.
Further Reading
Published at DZone with permission of Yuri Mednikov. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments