How to escape NullPointerExceptions in Java using Optional

Abdulcelil Cercenazi - Mar 24 '21 - - Dev Community

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

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player