How to use Java Optional to handle NullPointerException.

We all know the pain of dealing with null references and the potential for NullPointerExceptions. That’s where Optional comes in, it’s a container object that may or may not contain a non-null value, and provides a variety of utility methods to check for the presence or absence of a value.
Tony Hoare, the inventor of the null reference, famously referred to it as his “billion-dollar mistake” due to the countless errors and system crashes that it has caused over the years. But with the introduction of Optional, we now have a way to effectively handle null references and prevent NullPointerExceptions.
In this article, we’ll discuss the ways to check for null references and how Optional can help us avoid NPEs. Whether you’re a seasoned Java developer or just starting out, this article will provide valuable insights into the best practices for handling null references in your code.
So let’s dive in!
Problems with Null Reference
Imagine you are building a Library management system. You are using java to fetch book details with the title of the book.
@Repository
public class BookRepository extends JpaRepository<Book, Long> {
Book findByTitle(String title);
}
public class SearchService {
BookRepository bookRepository;
Book searchByTitle(String title) {
return bookRepository.findByTitle(title);
}
}
Here BookRepository
is connecting to our database and SearchService
deals with API requests. The findByTitle
in BookRepository
will return a Book object only when there is a title present in our database.
So, what happens when there is no book with the same title?
The function will return a null reference.
Since, this function can be used for other features like borrowing, purchasing, reading, and others. These functionalities can break if a null reference is not handled properly and can lead to NullPointerException.
Null Check Before Optional
There are other alternatives to null reference checks that we used before Optional and still use in some places.
- If-else conditions: Checking if the function returns null before performing the operation on it.
public class SearchService {
BookRepository bookRepository;
Book searchByTitle(String title) {
Book book = bookRepository.findByTitle(title);
if (book == null){
return new IllegalArgumentException("Title is invalid or not present");
} else {
return book;
}
}
}
- Objects class: This class provides various static utility methods for operating on objects or checking certain conditions before operation.
public class SearchService {
BookRepository bookRepository;
Book searchByTitle(String title) {
Book book = bookRepository.findByTitle(title);
if (Objects.isNull(book)){
return new IllegalArgumentException("Title is invalid or not present");
} else {
return book;
}
}
}
Why do we need Optional
While the use of if-else
and Optional
can solve the issue of null checks, it is not a scalable solution. As the system grows and new features are added, it becomes increasingly difficult to keep track of all the null checks and handle them properly.
For example, if some other feature uses bookRepository.findByTitle(title)
or if someone wants to find the name of the author associated with the book using book.getAuthor().getName()
, this could lead to a cascading series of null checks, making the codebase harder to maintain and understand.
Java Optional
Java Optional
is a container object used to represent the presence or absence of a value. It was introduced in Java 8 and provides several features:
- Handling null values: Optional is used to avoid
NullPointerException
by explicitly representing the absence of a value. - Chaining operations: Optional can be used to chain together multiple operations that depend on the presence of a value and handle the absence of a value in a single place.
- Functional methods:
Optional
class provides functional methods likemap
,flatMap
,filter
andorElse
,orElseGet
which are used to handle the absence of a value in a more elegant and readable way. - Default values:
orElse
andorElseGet
methods can be used to provide a default value if the Optional is empty. - Type safety: Optional ensures that the value is of the correct type and eliminates the need for explicit casting.
- Thread safety: Optional is immutable, thus it is thread-safe.
- Empty Optional: It provides an easy way to create an empty
Optional
by callingOptional.empty()
.
Using as a wrapper
We can wrap our return type in findByTitle
with optional.
@Repository
public class BookRepository extends JpaRepository<Book, Long> {
Optional<Book> findByTitle(String title);
}
This way, the searchByTitle
method in SearchService
can check if a value is present in Optional
before using it, preventing any potential null pointer exceptions.
Creating an Instance
There are several ways to create an instance of Optional
in Java:
- Optional.of(value): Creates an
Optional
with the given non-null value. If the value passed isnull
, it will throw aNullPointerException
.
String value = "hello";
Optional<String> optional = Optional.of(value);
- Optional.ofNullable(value): Creates an
Optional
with the given value, whether it is null or not.
String value = "hello";
Optional<String> optional = Optional.ofNullable(value);
- Optional.empty(): Creates an empty
Optional
.
Optional<String> optional = Optional.empty();
It is important to keep in mind that Optional is a container object, it can’t be used to represent the presence of a null value, it’s used to represent the absence of a value.
Accessing
Once you have an instance of Optional
, there are several ways to access the value it contains:
- get(): Retrieves the value inside the
Optional
. If theOptional
is empty, it will throw aNoSuchElementException
.
Optional<String> optional = Optional.of("hello");
String value = optional.get(); // value = "hello"
- isPresent(): Returns
true
if theOptional
contains a value,false
otherwise.
Optional<String> optional = Optional.of("hello");
if(optional.isPresent()){
System.out.println("Is Present");
}
- ifPresent(Consumer<T> consumer): If a value is present, invoke the specified consumer with the value, otherwise do nothing.
Optional<String> optional = Optional.of("hello");
optional.ifPresent(val -> System.out.println(val)); //prints "hello"
- orElse(T other): Returns the value if present, otherwise returns
other
.
Optional<String> optional = Optional.empty();
String value = optional.orElse("default value"); // value = "default value"
- orElseGet(Supplier<T> other): Returns the value if present, otherwise invokes
other
and returns the result of that invocation.
Optional<String> optional = Optional.empty();
String value = optional.orElseGet(() -> "default value"); // value = "default value"
- orElseThrow(): Returns the contained value, if present. Otherwise, it will throw the exception provided as the parameter.
Optional<String> optional = Optional.empty();
String value = optional.orElseThrow(() -> new IllegalArgumentException("Value not present"));
These methods provide a way to safely access the value contained in an Optional
without the risk of a NullPointerException
.
Filters and Maps
The filter
and map
methods in the Optional
class are used to manipulate the value contained in the Optional
object.
- filter(Predicate<T> predicate): If a value is present and the value matches the given predicate, return an
Optional
describing the value, otherwise, return an emptyOptional
.
Optional<Integer> optional = Optional.of(5);
Optional<Integer> filteredOptional = optional.filter(val -> val > 3);
System.out.println(filteredOptional.get()); //5
- map(Function<T, R> mapper): If a value is present, apply the provided mapping function to it, and if the result is non-null, return an
Optional
describing the result. Otherwise, return an emptyOptional
.
Optional<Integer> optional = Optional.of(5);
Optional<String> mappedOptional = optional.map(val -> "Value: " + val);
System.out.println(mappedOptional.get()); //Value: 5
Both filter
and map
methods return a new Optional
containing the result of the operation. It is important to note that if the original Optional
is empty, the result of both methods will be an empty Optional
.
You can chain multiple filters and map operations together.
Optional<Integer> optional = Optional.of(5);
Optional<String> finalOptional = optional
.filter(val -> val > 3)
.map(val -> "Value: " + val)
.map(String::toUpperCase);
System.out.print(finalOptional);
Output
Optional[VALUE: 5]
FlatMap
The flatMap
method in the Optional
class is similar to the map
method, but it is used when the mapping function returns an Optional
instead of a value. The flatMap
method "flattens" the nested Optional
into a single Optional
.
Here is an example of using flatMap
:
Optional<String> opt1 = Optional.of("hello");
Optional<String> opt2 = opt1.flatMap(val -> Optional.of(val + " world"));
System.out.println(opt2.get()); // "hello world"
In the above example, the flatMap
method takes a lambda expression that maps the value "hello" to a new Optional
containing the string "hello world". The flatMap
method then "flattens" this nested Optional
and returns a new Optional
containing the final value "hello world".
The flatMap
method is useful when you have a sequence of operations that depend on the presence of a value, and you want to chain them together using Optional
. It eliminates the need for explicit null-checking and makes the code more readable.
Here is an example of using flatMap
with a class hierarchy:
class Outer {
Inner inner;
public Inner getInner() {
return inner;
}
}
class Inner {
String value;
public String getValue() {
return value;
}
}
Optional<Outer> outerOpt = Optional.of(new Outer());
Optional<String> valueOpt =
outerOpt.flatMap(outer -> Optional.ofNullable(outer.getInner()))
.flatMap(inner -> Optional.ofNullable(inner.getValue()));
Here, the flatMap
method is used to chain together the operations of getting the Inner
object from the Outer
object and then get the value
from the Inner
object, all within the context of an Optional
.
It’s important to note that if any of the intermediate Optional
is empty, the final result will be an empty Optional
as well.
Things to keep in mind
Before using Optional
in your code, there are several things to consider:
- Code readability: Using
Optional
can make the code more verbose and harder to read, especially if multipleOptional
objects are used in the same method or if they are nested inside each other. - Performance: Using
Optional
can have an impact on performance, as it creates additional objects and requires more memory. In situations where the performance is critical, it's better to use null checks or other alternatives. - Overuse: While
Optional
is a useful tool for preventingNullPointerException
, it should not be used everywhere. OverusingOptional
can make the code more complex and harder to understand. It's important to use it only when it provides real value. - Return type: Keep in mind that the return type of a method that uses
Optional
is different from the return type of a method that uses a direct value. This can make it harder to understand what the method returns and can create confusion when working with the method. - Return empty optional: Returning an empty Optional should only be done when it makes sense if the method should return an empty Optional when there is no matching value found.
- Familiarize with methods: To make the most of Optional, you should be familiar with functional methods like
map
,flatMap
,filter
andorElse
,orElseGet
which are used to handle the absence of a value in a more elegant and readable way.
It’s important to weigh the pros and cons of using Optional
and to use it only when it provides real value, in order to make the code more readable, maintainable, and performant.
In summary, Java Optional is a useful feature that provides a way to handle null values in a more elegant and readable way, it also allows to chain multiple operations together and provides functional methods like map
and flatMap
to deal with the absence of a value.
That’s all about “Java Optional”. Send us your feedback using the message button below. Your feedback helps us create better content for you and others. Thanks for reading!
If you like the article please click the 👏🏻 button below a few times. To show your support!
Follow us on Twitter and LinkedIn
