Each of the 15 Methods of Java Optional’s API Explained in Less than 20 Seconds
Hands-on and beginner-friendly breakdown of Java 8’s Optional API.
What is “Optional”?
As you may already know, Java 8 introduced some functional features to the language: one of these features is the Optional type.
An Optional object wraps a variable or the return of a function call and, based on this data, it can be in one of the following states:
- present — if the data it wraps is not null;
- empty — if it’s used to wrap a null value;
In this article, we’ll briefly explain each method of the Optional class and we’ll use it in a practical example.
Code Examples
For the code examples in this article, we’ll use the ColorsRepository class. This class exposes a static method getColor(String colorName) that will return a Color object wrap in an Optional<>.
For example, ColorsRepository.getColor(“red”) will return an Optional object which is present and has the “red” color object inside.
On the other hand, ColorsRepository.getColor(“spencer”) will return with an empty optional, because it doesn’t know about any color with the name “spencer”.
Without further ado, let’s get started.
1. isPresent()
We can use isPresent() to check the state of the Optional object. This will return true if the Optional is present and false if the Optional wrapper is empty.
Optional<Color> color = ColorsRepository.getColor("red");
if (color.isPresent()) {
System.out.println("'red' color was found");
}2. isEmpty()
Similarly, we can check if we’ve wrapped a null value by calling isEmpty(). This method is the opposite of isPresent() and it can be used for more readable code:
Optional<Color> color = ColorsRepository.getColor("spencer");
if (color.isEmpty()) {
System.out.println("'spencer' color was NOT found");
}3. get()
get() can be used to access the data inside of an Optional. Be careful though, if the optional is present, this will return the data it wraps — but, if the optional is empty, calling get() will throw a NoSuchElementException.
Optional<Color> color1 = ColorsRepository.getColor("red");
System.out.println(color1.get()); // will print the color object
Optional<Color> color2 = ColorsRepository.getColor("spencer");
System.out.println(color2.get()); // will throw NoSuchElementExceptionjava4. orElseThrow()
orElseThrow() is doing exactly the same thing as get() — returning the data if present and throwing a NoSuchElementException if empty.
Having the get() method to throw an exception can be a bit counterintuitively. Therefore, with the next java release, the authors have provided another method with the same functionality, but, this time, they made sure the name suggests that an exception will be thrown if the data is missing.
Consequently, the usage of orElseThrow() is preferred over get() — some IDEs or code analyzers may signal this and highlight counterintuitive code.
Optional<Color> color1 = ColorsRepository.getColor("red");
System.out.println(color1.orElseThrow()); // will print the color object
Optional<Color> color2 = ColorsRepository.getColor("spencer");
System.out.println(color2.orElseThrow()); // will throw NoSuchElementException5. orElseThrow(exceptionSupplier)
The orElseThow(exceptionSupplier) is overloading the method we discussed previously. This time, we can provide our own exception that will be thrown in case the data is missing.
This can be used if we want to throw a custom exception or provide a specific exception message:
Optional<Color> color1 = ColorsRepository.getColor("red");
System.out.println(color1.orElseThrow(() -> new ColorNotFoundException("cannot find color with key = 'red'")));
// will print the color object
Optional<Color> color2 = ColorsRepository.getColor("spencer");
System.out.println(color2.orElseThrow(() -> new ColorNotFoundException("cannot find color with key = 'spencer'")));
// will throw ColorNotFoundException with the given message6. orElse(defaultValue)
We can specify a default value using the orElse() method of the API. If the Optional is present, the data will be returned. Otherwise, the specified default value will be returned instead:
Optional<Color> color1 = ColorsRepository.getColor("red");
System.out.println(color1.orElse(Color.WHITE));
// will print the RED color object
Optional<Color> color2 = ColorsRepository.getColor("spencer");
System.out.println(color2.orElse(Color.WHITE));
// will print the WHITE color object7. orElseGet(defaultValueSupplier)
This method is very similar to the previous one. The only difference is that this time, we will provide the default value as a supplier. The benefit of providing a supplier is that the expression will be lazily evaluated.
In other words, if the optional is present, the supplier will not be evaluated. This can allow you to avoid creating objects unnecessarily — or even worst, external calls or DB operations. Therefore, the usage of orElseGet is preferred over orElse:
Optional<Color> color1 = ColorsRepository.getColor("red");
System.out.println(color1.orElse(new Color(150, 150, 200)));
// orElse() will create the new Color, but will print the RED Color object
Optional<Color> color2 = ColorsRepository.getColor("red");
System.out.println(color2.orElseGet(() -> new Color(150, 150, 200)));
// orElseGet() will print the RED Color without creating any other object8. ifPresent(consumer)
ifPresent requires an implementation of the Consumer interface as a parameter. If the optional is present, the consumer will be applied, otherwise, it will be ignored:
Optional<Color> color1 = ColorsRepository.getColor("red");
color1.ifPresent(color -> System.out.println("color retrieved: " + color));
// will print the message
Optional<Color> color2 = ColorsRepository.getColor("spencer");
color2.ifPresent(color -> System.out.println("color retrieved: " + color));
// will not do anything9. ifPresentOrElse(consumer, runnable)
Furthermore, we can use ifPresentOrElse if we want to provide a consumer that is going to be applied if the data is present as well as a runnable which runs if the optional is empty:
Optional<Color> color1 = ColorsRepository.getColor("red");
color1.ifPresentOrElse(
color -> System.out.println("color retrieved: " + color),
() -> System.out.println("color not found")
);
// will print color retrieved the message
Optional<Color> color2 = ColorsRepository.getColor("red");
color2.ifPresentOrElse(
color -> System.out.println("color retrieved: " + color),
() -> System.out.println("color not found")
);
// will print "color not found"10. filter(filterPredicate)
The filter receives a Predicate implementation and returns another Optional object. There are 3 possible scenarios here:
- if the original Optional object is empty, filter() will return an empty optional as well.
- if the original Optional object is present, the predicate will be evaluated, if it returns ‘false’, then filter() will return an empty optional.
- if the original Optional object is present, the predicate will be evaluated, if it returns ‘true’, an optional wrapping data will be returned.
Optional<Color> color1 = ColorsRepository.getColor("spencer");
color1 = color1.filter(color -> color.getRed() > 100);
System.out.println(color1.isPresent());
// will print 'false'
// because the original optional is empty ('spencer' is not a known color name)
Optional<Color> color2 = ColorsRepository.getColor("blue");
color2 = color2.filter(color -> color.getRed() > 100);
System.out.println(color2.isPresent());
// will print 'false'
// because the Blue Color object has the 'red' value < 100.
Optional<Color> color3 = ColorsRepository.getColor("red");
color3 = color3.filter(color -> color.getRed() > 100);
System.out.println(color3.isPresent());
// will print 'false'
// because the Red Color object has the 'red' value > 100.11. map(mappingFunction)
The map() method receives a function as a parameter and it will apply it to the data if present.
Finally, map() returns another Optional<>, of a different type. For instance, it can convert an Optional<Color> to an Optional<Integer> if we map a Color to a specific value of the instance:
Optional<Color> color1 = ColorsRepository.getColor("red");
Optional<Integer> redValue1 = color1.map(color -> color.getRed());
// will hold the value 255 wrapped into an Optional<> object
Optional<Color> color2 = ColorsRepository.getColor("spencer");
Optional<Integer> redValue2 = color2.map(color -> color.getRed());
// will be an empty optional
// because color2 was empty in the first place12. flatMap(mappingFunction)
We should use flatMap when our mapping function already returns an Optional object. We need to do this to avoid a nested Optional<> structure:
Optional<String> colorName = Optional.of("red");
Optional<Optional<Color>> color1 = colorName
.map(name -> ColorsRepository.getColor(name));
//getColor() returns a Optional<Color>
//therefore, map() will create a nested Optional<> structure
Optional<String> colorName2 = Optional.of("red");
Optional<Color> color2 = colorName2
.flatMap(name -> ColorsRepository.getColor(name));
//flatMap will perform the map
//and take care of the nested Optional<> structure13. Optional.ofNullable(nullableObject)
The static method ofNullable creates an optional based on the data passed in. If the data is null, it will return an empty optional. Otherwise, the optional will be present and it will wrap the data provided.
Optional<String> optional1 = Optional.ofNullable("some random String");
System.out.println(optional1.isPresent());
//will print 'true'
Optional<String> optional2 = Optional.ofNullable(null);
System.out.println(optional2.isPresent());
//will print 'false' because optional2 is empty14. Optional.empty()
The static empty() method simply creates and returns an empty optiona:
Optional<String> optional = Optional.empty();
System.out.println(optional.isPresent());
//will print 'false'15. Optional.of()
The third and last way of creating an Optional is through the static of() method. This method should only be used for non-null values, otherwise, it will throw a NullPointerException:
Optional<String> optional1 = Optional.of("some random value");
System.out.println(optional1.isPresent());
//will print 'true'
Optional<String> optional2 = Optional.of(null);
//will throw a NullPointerExceptionThank You!
Thanks for reading the article and please let me know what you think! Any feedback is welcome.
If you want to read more about clean code, design, unit testing, functional programming, and many others, make sure to check out my other articles.
If you like my content, consider following or subscribing to the email list.
Finally, if you consider becoming a Medium member and supporting my blog, here’s my referral.
Happy Coding!
