Complete Guide for Optional in Java
Optional was introduced in Java 8 as a new core library class to represent the presence or absence of a value. Optional class aimed at reducing the number of NullPointerExceptions in Java applications.
![](https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*fu1O5dtN_LWhOAkER3-tMQ.png)
It is a valuable tool for handling scenarios where a value might be missing, without resorting to the infamous NullPointerException
(NPE). Optional provides a common interface for representing optional values instead of just using null references. In this comprehensive guide, we will cover when and why to use Optional, how to create and leverage Optionals, and how and when to use methods of Optional.
The Problems Optional Solves: Null Values
Before we dive into Optional
, let's understand the problem it addresses. In Java and many other programming languages, null is often used to denote the absence of a value. A major source of bugs in Java programs is the use of null references. Calling methods or accessing fields on a null reference results in a NullPointerException, which can crash your program. To avoid this, Java developers have to litter their code with null checks before using a potentially null object.
if (user != null) {
System.out.println(user.getName());
}
This results in unreadable and cluttered code. Even with diligent null checking, NPEs may still occur and can be difficult to debug. Also, null comes with significant drawbacks:
- NullPointerExceptions (NPEs): Accessing a null value results in an NPE, which is a runtime exception that can crash your application if not handled properly.
- Ambiguity: In some cases, it’s not clear whether a method can return null, leading to confusion and potential bugs.
- Lack of Expressiveness: Null does not convey why a value is missing. Was it intentional, or is it an error?
Optional
was introduced to address these issues by providing a more structured and safer way to represent the absence of a value. Optional forces developers to explicitly think about the possibility of a null value and how to handle it. Hence developer either checks for a value or provides a default.
Creating an Optional
You can create an Optional
using three different ways.
1. of
- Wrap a non-null value using Optional.of(). This is used when you are certain that the value is non-null.
Optional<String> name = Optional.of("John");
2. ofNullable
- Create an Optional that may contain a null value using Optional.ofNullable()
Optional<String> name = Optional.ofNullable(username);
3. empty
- Construct an empty Optional with Optional.empty()
Optional<String> empty = Optional.empty();
In most cases, you’ll want to use Optional.ofNullable() to make your intents explicit that the value could be null.
Accessing the Optional Value
1. get
You can use the get()
method to access the value inside an Optional
. However, be cautious because it can throw a NoSuchElementException
if the Optional
is empty.
A safer approach is to use isPresent()
andget()
. Use isPresent()
to check if a value is present. It returns true if the optional is not empty. Then retrieve the value with get()
call.
if (name.isPresent()) {
System.out.println(name.get());
} else {
// handle no value
}
However, this method doesn’t contribute significantly to the codebase. Because it does not make any difference then null check (!= null
). So it is not recommended.
2. orElse/orElseGet/orElseThrow
If it is possible to provide a default value or a default flow, orElse()
and orElseGet()
can be used to access the value.
orElse()
returns the provided default value when the optional is empty.
String blogName = optionalName.orElse("codimis");
orElseGet()
executes the provided supplier and returns the result value when the optional is empty.
public String getName() {
return "default";
}
String blogName = optionalName.orElseGet(()-> getName());
There is a crucial difference between orElse()
and orElseGet()
methods. When you use the orElse()
method and provide a default value, the method or expression used to calculate that default value will always be executed, regardless of whether the Optional
contains a value or is empty. But the supplier on the orElseGet() method runs only when it is needed. You can see it in the below example.
public static void main(String[] args) {
String blogName1 = Optional.of("Codimis").orElseGet(() -> getName());
System.out.println(blogName1);
String blogName2 = Optional.of("Example").orElse(getName());
System.out.println(blogName2);
}
public static String getName() {
System.out.println("getName called");
return "default";
}
Output of the above code is:
Codimis
getName called
Example
Output contains getName called
because orElse()
always evaluates the provided default value. Hence, if you don’t have a default value and computation is required to get the default value, using the orElseGet()
is suggested because of the lazy evaluation.
orElseThrow()
is used when you want to throw an exception if theOptional
is empty.
This method is particularly useful when you expect an Optional
to always contain a value, and if it doesn't, it indicates an exceptional condition that should be handled by throwing an appropriate exception.
import java.util.Optional;
import java.util.NoSuchElementException;
public class OptionalExample {
public static void main(String[] args) {
Optional<String> optionalValue = Optional.empty();
try {
String result = optionalValue.orElseThrow(() -> new NoSuchElementException("Value is not present"));
System.out.println("Result: " + result); // This line won't be reached
} catch (NoSuchElementException ex) {
System.out.println("Exception: " + ex.getMessage()); // Exception: Value is not present
}
}
}
3. ifPresent(Java 8+)/ifPresentOrElse(Java 9+)
Two more ways exist to access and execute a code block with the optional value. They areifPresent()
and ifPresentOrElse()
methods. Both of them are used to execute a block of code in the event of the existence of the value. They execute the provided consumer block if optional is present. In addition to that, ifPresentOrElse()
provides a chance to run a runnable if the value is missing.
name.ifPresent(n -> System.out.println(n));
name.ifPresentOrElse(n->System.out.print(n), ()->System.out.print("no name"));
Processing and Converting the Optional Value
1. map
If you need to apply a function to value of the optional, map()
is your function. It applies the given function to the value if present and returns an Optional
as a result. If you want to extract a field of an optional value and process it if it exists, you can use map function.
Optional.ofNullable(order)
.map(Order::getDetails)
.map(OrderDetails::getEmailAddress)
.ifPresent(/* send email to user*/);
2. flatMap(Java 9+)
Similar to map
but allows the mapping function to return an Optional
, then flattens the result. If getDetails method of order instance returns Optional<OrderDetails>
, flatMap function avoids having nested Optional object after mapping.
public Optional<OrderDetails> getDetails() {
return Optional.ofNullable(this.orderDetails);
}
Optional.ofNullable(order)
.map(Order::getDetails) // returns Optional<Optional<OrderDetails>>
.map(OrderDetails::getEmailAddress)
.ifPresent(/* send email to user*/);
Optional.ofNullable(order)
.flatMap(Order::getDetails) // returns Optional<OrderDetails>
.map(OrderDetails::getEmailAddress)
.ifPresent(/* send email to user*/);
3. filter
Returns an Optional
containing the value if it satisfies the given predicate, otherwise empty. The returned optional contains initial values. The filter method does not have any impact on the value.
// returns optional empty
Optional.of("Hello, World!").filter(val -> val.contains("Hell,"));
// returns Optional<String>
Optional.of("Codimis").filter(val -> val.contains("Cod"));
4. stream(Java 9+)
Converts the Optional
into a Stream containing zero or one element. The stream method is useful when you use optional collections. It can be in two ways, either optional can wrap a list or list can be consist of optional values.
Optional<List<Integer>> optionalList = getListOfIntegers();
// Use stream() to process the list if it's present
optionalList
.stream() // Converts the Optional to a Stream
.flatMap(List::stream) // Flattens the Stream of lists into a Stream of integers
.forEach(System.out::println); // Print each integer
public static void streamExample() {
List<Optional<BigDecimal>> amounts = new ArrayList<>();
amounts.add(Optional.of(BigDecimal.TEN));
amounts.add(Optional.empty());
amounts.add(Optional.of(BigDecimal.ONE));
BigDecimal totalAmount = amounts.stream()
.flatMap(Optional::stream)
.reduce(BigDecimal.ZERO, BigDecimal::add);
System.out.println("Total Amount: " + totalAmount);
}
In this comprehensive guide, we’ve covered the fundamentals of Optional
, its methods and operations. Armed with this knowledge, you can write Java code that is more resilient, readable, and maintainable. Optional
empowers you to handle potentially missing values with grace and clarity, making your codebase more robust and your development process more enjoyable.
👏 Thank You for Reading!
👨💼 I appreciate your time and hope you found this story insightful. If you enjoyed it, don’t forget to show your appreciation by clapping 👏 for the hard work!
📰 Keep the Knowledge Flowing by Sharing the Article!
✍ Feel free to share your feedback or opinions about the story. Your input helps me improve and create more valuable content for you.
✌ Stay Connected! 🚀 For more engaging articles, make sure to follow me on social media:
🔍 Explore More! 📖 Dive into a treasure trove of knowledge at Codimis. There’s always more to learn, and we’re here to help you on your journey of discovery.