Maddy
Tech with Maddy

Tech with Maddy

How to handle NullPointerException using Optional class

How to handle NullPointerException using Optional class

Maddy's photo
Maddy

Published on Oct 4, 2021

5 min read

Subscribe to my newsletter and never miss my upcoming articles

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, and it's referring to nothing (or null).

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.

There are some alternative ways that we can use 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 that corresponds 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, we'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 to say that "The country is null."

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

The country is null

Otherwise, we 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 is going to be triggered only if the value is present. Runnable if the value is absent. In our case, the value is present and therefore the Consumer is going to 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 is used to transform a value into another value. In our case, we're taking an Optional String and convert 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, we have created three Optional Country objects and wrapped them into a list.
  • Then, we print the item of the list using the flatMap() method. What it does it gives 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 along with its value. Flatmap() method gives back a smoother result.

When is it best to use the Optional class?

✅ At return type

✅ Combined with Streams

When should we avoid using the Optional class?

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

❌ In method parameters or constructors: we would make the code more complicated to read.

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 think about the inputs taken in constructors and methods.

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! 😊

 
Share this