Reduce NullPointerExceptions with Optional in Java 8 & Beyond

Hey, Tea Lovers! Today we will talk about the Optional class in Java 8. But before that, Did you know that Tony Hoare, The inventor of the null reference, has said that the null reference is his “Billion Dollar Mistake”? Since NullPointerException is the most common and frequently appeared error compared to others, especially for Java developers.

I have seen people literally wrapping their each code block in a try-catch to handle this sneaky fool. That’s how much we are scared of this exception.

In most of the cases, a simple check is done like, obj !== null, Objects.isNull(obj). But in modern Java, from Java8, we have a new approach to handle the null object, or I would say, protecting us from NullPointerException. And that’s called Optional.

What is Optional In Java8

Optional.java is a class in java.util package that, as the name suggests, holds the optional value. Meaning, the value is optional and it may or may not be present. It encapsulates your object and with multiple methods, it gives you the power to retrieve the object in various manners.

Optional has been introduced in Java8 and is used in various parts since then. If you look at the Stream API, it has used Optional in many cases. Spring JPA also returns Optional values for a single object. And you can also use it in your code. Let us look at how.

Create an Optional

To create an Optional, you just have to use the Optional’s static method(s). It depends on what kind of object you are encapsulating. Let’s see them.

If your object is not null, then you can use Optional.of(obj). It returns the Optional of the object. But if your object is null, it will give you an NullPointerException. So you have to use Optional.ofNullable(obj) which will return an empty Optional. You can directly return an empty Optional with Optional.empty().

Optional<String> nonEmptyOptional = Optional.of("Not null");
System.out.println(nonEmptyOptional.isPresent()); //true

Optional<String> nullObject = Optional.ofNullable(null);
System.out.println(nullObject.isPresent()); //false empty optional

Optional<String> emptyOptional = Optional.empty();
System.out.println(emptyOptional.isEmpty()); //true  only work in Java 11 or above

Check Optional State

In the previous block, I have used isPresent() for checking whether the optional object holds any value or is it empty. It will return true if it holds value i.e not null object, otherwise, it returns false for empty Optionals. It is similar to checking whether the object is null or not. You can also use isEmpty(), introduced in Java 11, is exactly the opposite of isPresent(). For empty Optional, it will return true otherwise false.

if(nonEmptyOptional.isPresent()){
    System.out.println("The value is present");
}
if(nullObject.isEmpty()){
    System.out.println("No value is not present");
}

Get the Value

OK, so we encapsulated the object into Optional. I have also checked for it. But How do I get the original value? Well, you can do so by calling get(). It will return the object from Optional. But beware, you shouldn’t be using the get() directly. Since it returns the value directly and if it has null value it will throw an exception. So better to be used with isPresent() then go on with the get().

 if (nonEmptyOptional.isPresent()) {
    System.out.println("The value " + nonEmptyOptional.get());
}
if (!nullObject.isPresent()) {
    System.out.println("No value is not present");
}

The above code, is OK but not so advantageous, we could have done it with a simple null check, right? There is a better way of handling things. There are quite a few depending on your requirement.

ifPresent(Consumer)

It is the combination of isPresent() and get(), but in a functional way. As the name suggests, do this if the value is present. It takes a Consumer as an input. The Consumer will only be called if the value is present i.e non-null object. More about the Consumer here.

Optional<String> codersTea = Optional.of("CodersTea.com");
nonEmptyOptional.ifPresent(site -> System.out.println("World's best website is " + site));

Optional<String> empty = Optional.empty();
empty.ifPresent(x -> System.out.println("No this will not run."));

Get Default Value if Empty

Now, what if you want to assign a default value if the object is null. You can do so by calling orElse(defaultObj). This way, if the Optional is empty defaultObj will be returned otherwise, the original value will be returned.

Optional<String> empty = Optional.empty();
String name = empty.orElse("Default Name"); //Default Name

String name1 = Optional.of("CodersTea").orElse("Default Name"); // CodersTea

It’s a great approach, but the default object will be created every time you call the function regardless of empty optional. Since you are passing it as the value it is getting created eagerly. It is an unnecessary burden. Here’s how.

public static void main(String[] args) {   
    Optional<String> empty = Optional.empty();
    String name = empty.orElse(printAndReturnDefaultName()); //Default Name
    
    String name1 = Optional.of("CodersTea").orElse(printAndReturnDefaultName()); // CodersTea

}
static String printAndReturnDefaultName(){
    System.out.println("Default Name");
    return "Default Name";
} 

Output:

Default Name
Default Name

As you can see it is printed two times, even though it is needed for 1 time only. To overcome this, we can use the Functional Way. Here is how.

orElseGet(Supplier)

It takes in a Supplier as a parameter. Read more about Supplier in my post “Be More Functional with Java’s Functional Interfaces“. Since Functional programming is Lazy in nature, It won’t be created until called. And that’s the major difference between orElse() which uses eager initialization of default object and orElseGet() which initializes lazily i.e when needed.

Optional<String> empty = Optional.empty();
String name = empty.orElseGet(() -> printAndReturnDefaultName()); //Default Name

String name1 = Optional.of("CodersTea").orElseGet(() -> printAndReturnDefaultName()); // CodersTea
String name2 = Optional.of("SuperHero").orElseGet(OptionalClass::printAndReturnDefaultName); //with method reference

Output:

Default Name

As you can see, it is called only once for empty Optional.

orElseThrow()

Sometimes you can’t take it anymore and it’s enough. No more null value or you will burst out. To take out that frustration you can use is orElseThrow(). It takes a Supplier<? extends Throwable> as a parameter and if the optional is empty throws the given Exception. You can use it if you can’t proceed without a value or any condition where null object is not tolerated.

Optional.empty().orElseThrow(() -> new Exception("Provide me a Name, man"));
Optional.ofNullable(null). orElseThrow(FileNotFoundException::new);

Do More than Null Checks

Now that we have seen how it manages the null values let us see how it expand beyond null checks. Optional have more functions than listed above. Just like Stream API, you can create your own pipeline with the Optional. The only difference is that Stream API works on Collection, while Optional is on the given Object. You can do the filter, map, flatMap, and many more. I have discussed this in my “Stream API: The Hero Without a Cape“. For now, I will go through the pipeline.

//lombok
@Data
@Builder
class Employee{
    private String name;
    private int age;
    private boolean isManager;
    private List<Integer> tasksIds;

    public static void main(String[] args) {
        //get the total task numbers if employee age gt 23 
        Employee employee = Employee.builder()
                .age(23).name("Bob").isManager(false)
                .tasksIds(Arrays.asList(1,2,3,4))
                .build();

        int empGt20TaskCount = Optional.of(employee)
                .filter(e -> e.getAge() > 20)
                .map(Employee::getTasksIds)
                .map(List::size)
                .orElse(-1);
        System.out.println("Employee with age over 20 and its task count is " + empGt20TaskCount);


        //manager
        Employee manager = Employee.builder()
                .age(23).name("Bob").isManager(true)
                .tasksIds(Arrays.asList(1,2,3,4))
                .build();

        Optional.of(manager)
                .filter(e -> e.getAge() > 20)
                .filter(Employee::isManager)
                .map(Employee::getTasksIds)
                .map(List::size)
                .ifPresent(tasks -> System.out.println("Manger is having" + tasks + " tasks"));

        //if employee is not manager then throw error
        Optional.of(employee).filter(Employee::isManager).orElseThrow(() -> new Exception("IT must be manager"));
    }

}

As you can see, the code has become more declarative and Optional is providing more than just null value check. By the way, if you are wondering whats the @Data or @Builder is checkout my previous post on Lombok “Say Good-Bye to Boilerplate Code with Lombok: Part 1-Installation” and “Say Good-Bye to Boilerplate Code with Lombok: Part 2-The Code“.

Conclusion

I think I have pretty much covered the Optional for Java 8. If you use this class then you will have much more flexibility in handling null values and may greatly decrease the possibility of NullPointerException. And it goes beyond null checks and provides you Functional Programming experience. Please let me know in the comments how did you like the post or want any improvement in the content.

The examples used in this post can be found on GitHub here or the full project here

See you in the next post.