How To handle NullPointerException Using Optional Class

How To handle NullPointerException Using Optional Class

ยท

5 min read

If you use Java as your primary programming language, you've come across a NullPointerException at some point in your life.

At a very high level, a NullPointerException is a RuntimeException that happens when you try to access a variable, but this variable is pointing to no object.

Take a look at the code snippet below:

public class Country {

    public static void main(String[] args) {

        String[] countries = new String[5];

        String asianCountry = countries[3].toUpperCase();
        System.out.println(asianCountry);
    }
}

What we get is:

Exception in thread "main" java.lang.NullPointerException
    at Country.main(Country.java:7)

We're getting a NullPointerException because we have a String array of countries, and we're trying to access its third element, which is null.

We can use some alternative ways to handle the NullPointerException, such as surrounding the code in a try...catch block (like in exampleOne) or if statements (like in exampleTwo).

public class Country {

    public static void main(String[] args) {

        String country = "India";
        exampleOne(null);
        exampleTwo(null);

    }

    static void exampleOne(String country){
        try {
            System.out.println("Printing the country using try/catch: " + country.toUpperCase());
        } catch(NullPointerException e){
            System.out.println("Throwing a NullPointerException");
        }
    }

    static void exampleTwo(String country){
        if(country != null){
            System.out.println("Printing the country using IF/ELSE statement: " + country.toUpperCase());
        } else {
            System.out.println("There is no such country");
        }
    }
}

Outcome:

Throwing a NullPointerException
There is no such country

But this way, we're just making the code unpleasant to read.

Plus, what if we have to check more data? The code would become more verbose.

Java 8 has introduced the Optional class in the Java.util package, which functions like a box that may or may not contain null values.

What Are the Benefits of Using This Class?

  1. At runtime, we won't get a NullPointerException.

  2. We don't have to carry out null checks.

  3. We reduce boilerplate code.

  4. We get to develop tidier APIs.

The example above can be rewritten like this using the Optional class.

import java.util.Optional;

public class Country {

    public static void main(String[] args) {

        String[] countries = new String[5];

        Optional<String> checkCountry = Optional.ofNullable(countries[3]);

        if(checkCountry.isPresent()){
            String country = countries[3].toUpperCase();
            System.out.println(country);
        } else {
            System.out.println("The country is null");
        }

    }
}
  • We created a String Optional corresponding to the third country in the string array.

  • The ofNullable() method returns an Optional with that value if it exists. Otherwise, it returns an empty Optional.

  • In the if...else block, you're seeing if the country is present in the Optional or not. If it is, we print out the country in uppercase. Otherwise, we print a message saying, "The country is null."

Since we haven't initialized the String array, the output would be:

The country is null

Otherwise, you can initialize the variable like here:

import java.util.Optional;

public class Country {

    public static void main(String[] args) {

        String[] countries = new String[5];
        countries[3] = "India";

        Optional<String> checkCountry = Optional.ofNullable(countries[3]);

        if(checkCountry.isPresent()){
            String country = countries[3].toUpperCase();
            System.out.println(country);
        } else {
            System.out.println("The country is null");
        }

    }
}

And the output would be:

INDIA

Some more examples of Optional class methods:

  • ifPresent()
    public static void main(String[] args) {

        Optional<String> country = Optional.of("India");
        country.ifPresent( value -> System.out.println(value.toUpperCase()));

    }

The outcome is:

INDIA

The value is present, and it's going to be printed in uppercase. If the value is not present, nothing would be printed.

  • ifPresentOrElse()
    public static void main(String[] args) {

        Optional<String> country = Optional.of("India");

        country.ifPresentOrElse(value -> System.out.println("The country is: " + value),
                () -> System.out.println("The country is not present"));

    }

The outcome is:

The country is: India
  • This method takes in Consumer and Runnables functional interfaces.

  • The Consumer will be triggered only if the value is present. Runnable if the value is absent. In this case, the value is present, and therefore the Consumer will be called.

  • map()

    public static void main(String[] args) {

        Optional<String> country = Optional.of("America");

        Optional<Integer> lengthOfCountry = country.map(String::length);

        System.out.println("The length of America is: " + lengthOfCountry.get());

    }

The outcome is:

The length of America is: 7

The map() method transforms a value into another value. In our case, you're taking an Optional String and converting it into an Integer that is equal to its length.

  • flatMap()
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

public class Country {

    private Optional<String> capital;


    public Country(Optional<String> capital) {
        this.capital = capital;
    }

    public Optional<String> getCapital() {
        return capital;
    }

    public static void main(String[] args) {

        Optional<Country> africanCapital = Optional.of(new Country(Optional.of("Johannersburg")));
        Optional<Country> europeanCapital = Optional.of(new Country(Optional.of("Rome")));
        Optional<Country> asianCapital = Optional.of(new Country(Optional.of("Seoul")));

        List<Optional<Country>> capitalList = Arrays.asList(africanCapital, europeanCapital, asianCapital);

        System.out.println(capitalList
                .stream()
                .map(x -> x.map(Country::getCapital)).collect(Collectors.toList()));

        System.out.println(capitalList
                .stream()
                .map(x -> x.flatMap(Country::getCapital)).collect(Collectors.toList()));

    }
}

The outcome is:

[Optional[Johannersburg], Optional[Rome], Optional[Seoul]]
  • In this example, you have wrapped three Optional Country objects into a list.

  • Then, we print the list item using the flatMap() method. What it does is give back a list with no additional Optional.

If we were going to use the map() method to perform the same, the code would look like this:

import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

public class Country {

    private Optional<String> capital;


    public Country(Optional<String> capital) {
        this.capital = capital;
    }

    public Optional<String> getCapital() {
        return capital;
    }

    public static void main(String[] args) {

        Optional<Country> africanCapital = Optional.of(new Country(Optional.of("Johannersburg")));
        Optional<Country> europeanCapital = Optional.of(new Country(Optional.of("Rome")));
        Optional<Country> asianCapital = Optional.of(new Country(Optional.of("Seoul")));

        List<Optional<Country>> capitalList = Arrays.asList(africanCapital, europeanCapital, asianCapital);

        System.out.println(capitalList
                .stream()
                .map(x -> x.map(Country::getCapital)).collect(Collectors.toList()));

    }
}

And the outcome would be:

[Optional[Optional[Johannersburg]], Optional[Optional[Rome]], Optional[Optional[Seoul]]]

This is because the map() method returns the Optional and its value. Flatmap() the method gives back a smoother result.

When Is It Best to Use the Optional Class?

  • At return type.

  • Combined with streams.

Related: Java 8 Streams

When Should We Avoid Using the Optional Class?

  • You shouldn't use it as a private field because it's not serializable (meaning that you wouldn't be able to transform that object into a format we can store in the memory).

  • You would make the code more complicated to read in method parameters or constructors.

It's critical to highlight that Optional is not a way to avoid all types of null checks. For example, you'll still need to consider the inputs taken in constructors and methods.

Conclusion

I hope that you've found this helpful. Optional is an exciting addition, but be careful not to overuse it as it may potentially kill off its benefits! ๐Ÿ˜Š

Until next time!

๐Ÿ™‹๐Ÿพโ€โ™€๏ธ

Did you find this article valuable?

Support Maddy by becoming a sponsor. Any amount is appreciated!

ย