Hi there! I would like today to discuss some essential Java topic - usage of Optional class and compare it with an alternative from Vavr library. Optional intitially was introduced in Java 8 and is defined as "a container object which may or may not contain a non-null value". Developers utilize Optionals in order to avoid null checking in places when executing a code leads to not a result, but to null value and it can in its regard results in NullPointerException. In such cases, Optional offers us some fancy functionality, but not all of it was introduced in 8th release, some features require Java 11. Another way to handle these issues is Vavr's Option class.
In this post we would learn how to use Java's Optional class, and then compare it to Vavr's Option class. Note, that code requires at least Java 11 and was tested with Vavr 0.10.2.
Introducing Java Optional
The concept of Optional is not something new and has been already implemented in functional programming languages like in 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 see how to handle it.
Creation of Optional
First thing 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 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 can not be null, but this is just an example. We check an existence or absence of value with ifPresent() method. You can note, that four is not an Integer, it is a container, that holds integer inside. When we sure, that value is inside, we can "unpack" it with get() method. Ironically, if we would use get() without checking, we can end with NoSuchElementException.
Another way to obtain an Optional is using streams. Several terminal stream's methods return Optionals, so we can manipulate them and checking existence or absence, for example:
- findAny
- findFirst
- max
- min
- reduce
Check the code snippet below:
Optional<Car> car = cars.stream().filter(car->car.getId().equalsIgnoreCase(id)).findFirst();
Next option is to create Optional from code, that can potentialy 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 most widespread case is using it in Spring repositories in finding one record by id, so we can build our logic on Optional and avoid null checking (btw, Spring also supports Vavr Options). Let 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 case, if 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 object:
repository.findOne("some id").ifPresent(book -> {});
or even simplier, we can write it with method references:
repository.findOne("some id").ifPresent(System.out::println);
If we don't have a book in repository, we can provide some alternative callback with ifPresentOrElseGet method:
repository.findOne("some id").ifPresentOrElseGet(book->{
// if value is presented
}, ()->{
// if value is absent
});
Alternatively, we can get another value, if result of operation is not presented:
Book result = repository.findOne("some id").orElse(defaultBook);
However with Optionals we need to remember about possible drawbacks. At last example we "guarantee" ourselves that we would obtain a book anyway, either it presents in underlaying 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 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.
Let have an example
Let create a simple example to check how use practically Optional and Option classes. We would have a CarRepository that would find a car based on supplied ID (e.g. on license plate number). Then we would see how to manipulate Optionals and Options.
Add some code first
Start with POJO class Car. It follows 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;
}
}
Second thing to create is CarRepository class. It would have two options to find car by Id - using old way with possible null result and using Optional like 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, we also populate our repository with some mock cars during initialization, so we don't have any underlaying database. We need to avoid complexities and let concentrate on Optional and Option, not on repositories.
Finding cars with Java Optional
Create a new test with JUnit (I also have an excellent tutorial on JUnit 5 if you don't have experience with it).
@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();
}
});
}
This code snippet demonstates the old way. We found a car with Czech license plate 1A9 4321 and checked that it exists. Then we found a car that is absent as it has Russian license plate and we have only Czech ones in our repository. It is null, so it may lead to NullPointerException.
Next, let move to Java Optionals. First step is to obtain Optional instance from repository using 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 findCarByIdWithOptional method and then print a car (if it presents). If you run it you would get 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 method that potentially could return 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 use another way. We create Optional from findCarById that can return null if no car was found. We manually use orElseThrow method to throw NoSuchElementException when desired car with license plate 5T1 0965 is present. An another situation is to use orElse with some default value, if requested data is not available in 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 taxi!
Finding cars with Vavr Option
Vavr Option is the 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 API to create Option instances. We can create Option from nullable:
Option<Car> nothing = Option.of(repository.findCarById("T 543 KK"));
Or we can create an empty container with none static method:
Option<Car> nullable = Option.none();
Also, there is a way to create Option from Java Optional! Take a look on the code snippet below:
Option<Car> result = Option.ofOptional(repository.findCarByIdWithOptional("5C9 9984"));
With Vavr Option we can use same API like with Optional and accomplish aforesaid 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 thrown 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, like we did with Optional's ifPresent? We can do this with several ways. There is an equal method to isPresent 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 existance 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 gonna make your code much more functional, than with built-in Optional class. So I encourage you to take some time and explore Vavr Option javadocs and experiment with these APIs. I can note some cool things like map, narrow, isLazy and when that you definetly need to check out. Also, Vavr Option is a part of Vavr family and is heavily integrated with Vavr other classes, and make such comparasion with Optional in vaccuum, e.g. in abscence of such classes is not correct. So, I would also write posts on other Vavr topics, like Try, Collections, Streams. Don't forget to sign up for updates!
Conclusion
In this post we talked about Optional class in Java. The concept of Optional is not something new and has been already implemented in functional programming languages like in 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 see how to handle it. We explored its APIs and did some example finding cars and manipulating results with Optional logic. Then, we discovered an alternative to Optional - Vavr's Option and described its methods as well.
Read more
- José Paumard. Optionals: Patterns and Good Practices (2016) Oracle Community Directory, read here
- Mervyn McCreight and Mehmet Emin Tok. A look at the Optional datatype in Java and some anti-patterns when using it (2019) FreeCodeCamp, read here
- Radosław Nowak. Functional programming in Java with Vavr (2018) Pragmatists, read here
Top comments (0)