DEV Community

Cover image for How to escape NullPointerExceptions in Java using Optional
Abdulcelil Cercenazi
Abdulcelil Cercenazi

Posted on

How to escape NullPointerExceptions in Java using Optional

What is Optional? ๐Ÿค”

According to Oracle: "A container object which may or may not contain a non-null value."

Why use it? ๐Ÿ‘€

it was introduced in Java 8 to enable us to elegantly handle the Null pointer exception problem.

How?

Let's first start by learning how variables in Java are handled in memory and what NullPointerException means.

In java variables can be one of two types:

  • Primitives
    • They are 8 types (int, char, etc...)
    • They are saved in the execution stack of the current method.
    • Fast info: each method call has its own execution stack.
 public void someMethod(){  
        int x = 10;  
}
Enter fullscreen mode Exit fullscreen mode
  • Objects

    • They are instances of classes (User, Car, Integer)
    • They are saved in the Java Heap, however, references to them are saved in the execution stack of the current method as well.
    • Those references are saved into variables.
    public void someMethod(){  
        // the local variable car is a reference to the actual object value in the Heap  
      Car car = new Car();  
    }
Enter fullscreen mode Exit fullscreen mode

Where does null fit in the picture? ๐Ÿ–ผ๏ธ

If an object isn't referencing any value in the Heap, then its value is null.
When we try to do some operations using that variable we hit the famous NullPointerException.

    Car car;  
    car.getModel();
Enter fullscreen mode Exit fullscreen mode

How does the Optional help?

It wraps the objects we pass to the calling method, and it provides methods to handle the presence or absence of the null value in a clean code functional style.

Code time ๐Ÿค–

As an example, let's say we have a Room class, which has a Desk, and that Desk has Pen Holder

First, let's look at some code that doesn't use the Optional class

Room ๐Ÿ 

@Getter @RequiredArgsConstructor @AllArgsConstructor
public class Room {
    private Desk desk;
    private final int number;
}
Enter fullscreen mode Exit fullscreen mode

Desk ๐Ÿ—ƒ

@Getter @RequiredArgsConstructor @AllArgsConstructor
public class Desk {
    private PenHolder penHolder;
    private final String model;
}
Enter fullscreen mode Exit fullscreen mode

Pen Holder ๐Ÿ–Š

@Getter @RequiredArgsConstructor
public class PenHolder {
    private final int capacity;
}
Enter fullscreen mode Exit fullscreen mode

A service that tells us what is the capacity of the Pen holder in a room, if exists. It has two methods, one that does a null check and another that doesn't

public class FetchingService {
    // this method will throw a nullPointerException when
    // either the Desk or PenHolder are null
    public static int getCapacityOfPenHolder_NoNullCheck(Room room){
        return room.
                getDesk().
                getPenHolder().
                getCapacity();
    }
    // this method performs a null check on both Desk and PenHolder
    public static int getCapacityOfPenHolder(Room room){
        if (room.getDesk() == null){
            return 0;
        }
        else if (room.getDesk().getPenHolder() == null){
            return 0;
        }
        else return room.getDesk().getPenHolder().getCapacity();
    }
}
Enter fullscreen mode Exit fullscreen mode

Now let's apply Optional and see the results

Room ๐Ÿ 

@AllArgsConstructor @RequiredArgsConstructor @Getter
public class Room {
    private Desk desk;
    private final int number;

    public Optional<Desk> getDeskOpt(){
        return Optional.ofNullable(desk);
    }
}
Enter fullscreen mode Exit fullscreen mode

Desk ๐Ÿ—ƒ

@Getter @AllArgsConstructor @RequiredArgsConstructor
public class Desk {
    private PenHolder penHolder;
    private final String model;

    public Optional<PenHolder> getPenHolderOpt(){
        return Optional.ofNullable(penHolder);
    }
}
Enter fullscreen mode Exit fullscreen mode

Pen Holder ๐Ÿ–Š

@RequiredArgsConstructor @Getter
public class PenHolder {
    private final int capacity;
}
Enter fullscreen mode Exit fullscreen mode

The service

public class FetchingService {
    public static int getCapacityOfPenHolder(Room room){
        return room.
            getDeskOpt().
            flatMap(Desk::getPenHolderOpt).
            map(PenHolder::getCapacity).
            orElse(0);
    }
}
Enter fullscreen mode Exit fullscreen mode

The optional code is much more concise, clear, and clean. However, the most important side is that we got rid of the NullPointerException monster.

One thing to be careful of is to not return null instead of the Optional. ๐Ÿ”ฅ

public Optional<PenHolder> getPenHolderOpt(){
        if (penHolder == null)
            return null;
        else return Optional.ofNullable(penHolder);
    }
Enter fullscreen mode Exit fullscreen mode

Where to use Optional?

There are a number of options for the place that we might use the Optional wrapper class, for example, wrap method parameters, local variables. However, the most suitable place is on method return types.

public class Desk {
    // not good !!!!
    private Optional<PenHolder> penHolder;
    private final String model;
}
Enter fullscreen mode Exit fullscreen mode
public class Desk {
    private PenHolder penHolder;
    private final String model;
    // Yes !!!
    public Optional<PenHolder> getPenHolderOpt(){
        return Optional.ofNullable(penHolder);
    }
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

  • The Optional class is a wrapper that wraps objects that might not exist in the Heap (in other words that could be null).

  • It was created to reduce the possibility of facing a NullPointerException

  • This class has methods that help us write clean code.

Code ๐Ÿ‘ฉโ€๐Ÿ’ป

GitHub

Top comments (2)

Collapse
 
190245 profile image
Dave

At best, I feel this is a bad example of Optional.

Your code design allows for the fact that a desk may or may not be present, but always assumes that pens are within penholders on a desk, which begs the question "where is the pen holder when the desk is null? Is it on the floor? Window ledge? What happens when the desk is present, but someone (probably QA) leaves a pen on a chair?

Further I would argue that your original code, with it's NPE possibility, is infinitely more readable (the FetchingService shouldn't need to care if the desk exists or not, so shouldn't need to map() etc).

Your NullPointerException could equally have been solved by making Room.desk immutable, much like Room.number is... and in fact, Room.desk is probably better as some type of Collection (since a Room can logically have 0 -> N desks). Then Streams API becomes more useful to you.

I note from your bio here, that you work in Spring... returning Optional from a Repository is an infinitely better example, in my opinion. We do not know until we look in the datastore, if the row(s) exist or not.

NullPointerException is a symptom of poor design thoughts, Optional is for when the state is indeterminate even at runtime (and even then, returning null is a valid strategy).

Collapse
 
jarjanazy profile image
Abdulcelil Cercenazi

Thanks for your reply and arguments,
regarding the design choice, I chose this nested module of objects to demonstrate the null checks, I didn't really consider it to be a production example.

As for the readability issue in the FetchingService, I guess it's a matter of personal preference, for me the flatMap and map are better than the if-else statements.

The immutability of the Desk in the Room assumes that we can't add a Desk to the Room in the future. Moreover, the design choice of whether it's a single Desk or not isn't the issue we are dealing with in the post.

Yes, Optionals from the Repository is also a very good example for Optional usage.
I agree with your last statement about the cases we should use Optional (indeterminate state), which is why I left the Desk in the Room as Optional and not final.

Thanks again for your valuable feedback, I would love to hear more :)