Preventing NullPointerExceptions in Java using Optionals

engels
If you are a Java programmer then you know Java has ‘null’ references. These ‘null’ references are the cause of NullPointerExceptions in our programs. Trying to access a field or method on a reference that does not point to an object will result in a NullPointerException at runtime.

For example, assume the class UserRepository defines a method to find a User by id:

public User findUser(String id);

 

We cannot be sure by looking at this signature, but the naming suggests that null might be returned if the User could not be found. We have to look at the implementation of the method to find out if that is the case.

If we are unaware of this, we might write code like this:

User user = userRepository.findUser("someUserId");

mailer.sendMail(user.getEmail());

 

This code will run just fine if a User is found. However, if a User was not found, the user variable will refer to ‘null’ and calling user.getEmail() will throw the infamous NullPointerException!

Before Optionals

Before we had Optionals, we could prevent the NullPointerException by checking if the user variable does not refer to null:

if (user != null) {
    mailer.sendMail(user.getEmail());
}

 

Of course, this required us to know that userRepository.findUser() can return null. We were probably writing this check because we already experienced the NullPointerException when running the application.

This only solves the problem for this particular use case. The next time we use the findUser() method we have to think again and remember that the method can return null. There is a possibility that we forget and introduce a source of NullPointerExceptions in the code.

Using Optionals

Now java 8 has given us the Optional class to help prevent NullPointerExceptions. But how do we use this the right way?

Refactor the findUser method to return an Optional of type User:

public Optional<User> findUser(String id);

 

This explicitly tells the compiler and the caller of the ‘findUser’ method that a User is not guaranteed to be found. The implementation of findUser must of course be changed so that it returns an Optional of type User, but this is not difficult.

Now we have to call the method as follows:

Optional<User> optionalUser = userRepository.findUser("someUserId");

 

Because the method now returns an Optional, we know that we have to handle the case where the user could not be found. With Optionals, there are multiple ways to do this depending on how you are going to use the value from the Optional.

In the Java documentation, you can find the methods you can call on an Optional. If you are going to use Optionals, I highly recommend going through the available methods. https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html

If you are not used to a functional style of programming the first solution that might come to mind is something like the following:

if(optionalUser.isPresent()) {
    mailer.sendMail(optionalUser.get().getEmail());
}

 

This solution is basically the same as checking if the reference is not null, but arguably uglier because of the additional .get() you have to do to retrieve the actual User from the Optional.

Luckily Optional has other methods we can use to make our code simpler. For example, the ifPresent method which takes a lambda expression which is only executed if the Optional contains a value. The lambda expression also provides the value wrapped by the Optional:

optionalUser.ifPresent(user -> mailer.sendMail(user.getEmail()));

 

We can shorten our code to the following now by chaining from the findUser method:

userRepository.findUser("someUserId")
    .ifPresent(user -> mailer.sendMail(user.getEmail()));

 

More Optionals

Let’s make it more interesting. What if the email of a User is not required and can be null? This makes the chance of NullPointerExceptions happening even bigger and more unpredictable since we are passing null as an argument to the sendMail method.

To prevent NullPointerExceptions without Optionals we have to add more checks to our code:

if (user != null && user.getEmail() != null) {
    mailer.sendMail(user.getEmail());
}

 

Since the business logic says the User’s email is optional, we should make getEmail() return an Optional:

public Optional<String> getEmail();

 

Using both Optionals for findUser and getEmail prevents the NullPointerExceptions from happening and makes the code more simpler to read, once we realize we only care about the email field of the User:

userRepository.findUser("someUserId")
    .flatMap(User::getEmail) // Map the User to its email Optional
    .ifPresent(mailer::sendMail); // If email Optional not empty, send mail

 

Don’t pass Optionals

You might have thought; why not pass the email Optional to the sendMail method like this?

mailer.sendMail(optionalUser.flatMap(User::getEmail));

 

I do not recommend passing Optionals as arguments to methods. It causes unneeded extra complexity inside the methods. Calling sendMail only makes sense if there is an email anyway. Passing an Optional requires the method to handle an extra case.

Summarizing

To prevent NullPointerExceptions, use Optional return types to make sure the caller of the method handles the case where the returned value is null.

When the business logic says that a value is optional (can be null), make this explicit in your code by using an Optional.

Generally, don’t use an Optional as a method argument, because it increases complexity in the receiving method. Especially if the receiving method requires the value to do something useful.

Only use an Optional when it’s a valid case from a business perspective that the value is null. If the returned value shouldn’t be null anyway, there is no need to add the extra complexity of handling the null value.

Good luck getting rid of those NullPointerExceptions!

Jeroen Berenschot

Jeroen Berenschot is sinds 2017 als Senior Software Engineer in dienst bij Profit4Cloud. Hij is AWS-CSA/A en Azure Developer Associate gecertificeerd.