Avoiding the Null Pointer Exception With Optional in Java

Brilian Firdaus
Brilian Firdaus
Avoiding the Null Pointer Exception With Optional in Java
Table of Contents
Table of Contents

In 1964, British computer scientist Tony Hoare invented the Null Pointer References.

The Null Pointer Exception has contributed the most bugs in production exceptions. It was implemented in many programming languages, including C, C++, C#, JavaScript, Java, and more.

The loss of financial resources, time, and human resources to fix it prompted Hoare to call it a “billion-dollar mistake.”

Java is one of the programming languages that implement Null Pointer References. If you’ve been developing with Java, I’m sure that you’ve seen them a lot. It doesn’t matter if you are new to Java or have ten years of experience. There is always a chance that you’ll encounter a Null Pointer Exception bug.


Optional in Java

Optional is an API that was introduced in Java 8. If used right, it can solve the problem of the Null Pointer Exception.

Optional API implements functional programming and uses Functional Interface.

If you want to know more about Functional Programming in Java you can read my other article, Functional Programming in Java, Explained.

Before we proceed any further, please note that I’m using Java 11 for the examples in this article. If you’re using a different version of Java, some methods might not exist or behave exactly the same.


Empty Optional

An empty optional is the main way to avoid the Null Pointer Exception when using the Optional API.

In Optional’s flow, a null will be transformed into an empty Optional. The empty Optional won’t be processed any further. This is how we can avoid a NullPointerException when using Optional.

We will learn further about how an empty Optional behaves later in this article.


Creating Optional object

There are three ways to initiate an Optional object:

  • Optional.of(T)
  • Optional.ofNullable(T)
  • Optional.empty()

Optional.of

Optional.of accepts any type with a non-nullable value in its parameter. To create an Optional object with Optional.of, we just have to pass a value in its parameter.

    @Test
    public void initializeOptional_optionalOf() {
        Optional<String> helloWorldOptional = Optional.of("Hello, world");
        assert helloWorldOptional.isPresent();
        assert "Hello, world".equals(helloWorldOptional.get());
    }

Be very careful when you are passing a value to the Optional.of. Remember that Optional.of doesn’t accept null values in its parameter. If you try to pass a null value, it will produce a NullPointerException.

     @Test
    public void initializeOptional_optionalOf_null() {
        try {
            Optional.of(null);
        } catch (Exception e) {
            assert e instanceof NullPointerException;
        }
    }

Optional.ofNullable

Optional.ofNullable is similar to Optional.of. It accepts any type. The difference is, with Optional.ofNullable, you can pass a null value to its parameter.

    @Test
    public void initializeOptional_optionalOfNullable() {
        Optional<String> helloWorldOptional = Optional.ofNullable("Hello, world");
        assert helloWorldOptional.isPresent();
        assert "Hello, world".equals(helloWorldOptional.get());
    }

When Optional.ofNullable is initialized using a null object, it will return an empty Optional.

    @Test
    public void initializeOptional_optionalOfNullable_null() {
        Optional<String> helloWorldOptional = Optional.ofNullable(null);
        assert !helloWorldOptional.isPresent();
        try {
            helloWorldOptional.get();
        } catch (Exception e) {
            assert e instanceof NoSuchElementException;
        }
    }

Optional.empty

An empty Optional can be initialized by using Optional.empty().

    @Test
    public void initializeOptional_optionalEmpty() {
        Optional<String> helloWorldOptional = Optional.empty();
        assert !helloWorldOptional.isPresent();
    }

Accessing Optional

There are some ways to get the value of an Optional.

get

A pretty straightforward method. The get method will return the value of Optional if it is present and throw a NoSuchElementException if the value doesn’t exist.

    @Test
    public void get_test() {
        Optional<String> helloWorldOptional = Optional.of("Hello, World");
        assert "Hello, World".equals(helloWorldOptional.get());
    }

    @Test
    public void get_null_test() {
        Optional<String> helloWorldOptional = Optional.empty();
        try {
            helloWorldOptional.get();
        } catch (Exception e) {
            assert e instanceof NoSuchElementException;
        }
    }

orElse

If you want to use a default value if the Optional is empty, you can use the orElse method.

    @Test
    public void orElse_test() {
        Optional<String> helloWorldOptional = Optional.of("Hello, World");
        assert "Hello, World".equals(helloWorldOptional.orElse("default"));
    }

    @Test
    public void orELseNull_test() {
        Optional<String> helloWorldOptional = Optional.empty();
        assert "default".equals(helloWorldOptional.orElse("default"));
    }

orElseGet

orElseGet is very similar to the orElse method. It’s just that orElseGet accepts Supplier<T> as its parameter.

    @Test
    public void orElseGet_test() {
        Optional<String> helloWorldOptional = Optional.of("Hello, World");
        assert "Hello, World".equals(helloWorldOptional.orElseGet(() ->"default"));
    }

    @Test
    public void orELseGet_Null_test() {
        Optional<String> helloWorldOptional = Optional.empty();
        assert "default".equals(helloWorldOptional.orElseGet(() ->"default"));
    }

orElseThrow

orElseThrow will return the value of the Optional or throw an exception if the value of the Optional is empty.

    @Test
    public void orElseThrow_test() {
        Optional<String> helloWorldOptional = Optional.of("Hello, World");
        assert "Hello, World".equals(helloWorldOptional.orElseThrow(NullPointerException::new));
    }

    @Test
    public void orELseThrow_Null_test() {
        Optional<String> helloWorldOptional = Optional.empty();
        try {
            helloWorldOptional.orElseThrow(NullPointerException::new);
        } catch (Exception e) {
            assert e instanceof NullPointerException;
        }
    }

Processing an Optional

There are many ways to process and transform an Optional. In this section, we will learn the common methods that are used.

As I wrote at the beginning of the article, an empty Optional won’t be processed in the flow. We can see that from the examples in this section.

Map

map is the most used method when processing an Optional object. It accepts Function<? super T, ? extends U> as its parameter and returns an Optional<U>. This means you can use a Function with any type of parameter and the return value will be wrapped to Optional in the map method.

    @Test
    public void processingOptional_map_test() {
        Optional<String> stringOptional = Optional.of("Hello, World")
                .map(a -> a + ", Hello");

        assert stringOptional.isPresent();
        assert "Hello, World, Hello".equals(stringOptional.get());
    }

If you try to return a null value in Function<? super T, ? extends U>, the map method will return an empty Optional.

    @Test
    public void processingOptional_map_empty_test() {
        Optional<String> stringOptional = Optional.of("Hello, World")
                .map(a -> null);

        assert !stringOptional.isPresent();
    }

An empty optional won’t be processed by map. We can confirm this with the following test:

    @Test
    public void processingOptional_map_empty_notProcessed_test() {
        AtomicBoolean atomicBoolean = new AtomicBoolean(false);
        Optional<String> stringOptional = Optional.of("Hello, World")
                .map(a -> null)
                .map(a -> {
                    atomicBoolean.set(true);
                    return "won't be processed";
                });

        assert !stringOptional.isPresent();
        assert atomicBoolean.get() == false;
    }

FlatMap

This is similar to map, but flatMap won’t wrap the return value of the Function to Optional. The flatMap method accepts Function<? super T, ? extends Optional<? extends U>> as its parameter. This means that you’ll need to define a Function that accepts any type and returns an Optional.

You will usually use the flatMap method when your code calls another method that returns an Optional object.


    @Test
    public void processingOptional_flatmap_test() {
        Optional<String> stringOptional = Optional.of("Hello, World")
                .flatMap(this::getString);

        assert "Hello, World, Hello".equals(stringOptional.get());
    }

    @Test
    public void processingOptional_flatmap_randomString_test() {
        Optional<String> stringOptional = Optional.of(UUID.randomUUID().toString())
                .flatMap(this::getString);

        assert !stringOptional.isPresent();
    }

    public Optional<String> getString(String s) {
        if ("Hello, World".equals(s)) {
            return Optional.of("Hello, World, Hello");
        }
        return Optional.empty();
    }

Filter

In the previous example of flatMap, we used a declarative style to differentiate the return value of the getString method. But we can actually use a functional style for that with the filter method.

   @Test
    public void processingOptional_filter_test() {
        Optional<String> stringOptional = Optional.of("Hello, World")
                .filter(helloWorldString -> "Hello, World".equals(helloWorldString))
                .map(helloWorldString -> helloWorldString + ", Hello");

        assert "Hello, World, Hello".equals(stringOptional.get());
    }

    @Test
    public void processingOptional_filter_randomString_test() {
        Optional<String> stringOptional = Optional.of(UUID.randomUUID().toString())
                .filter(helloWorldString -> "Hello, World".equals(helloWorldString))
                .map(helloWorldString -> helloWorldString + ", Hello");

        assert !stringOptional.isPresent();
    }
view rawProcessingOptionalT

If Present

The ifPresent method accepts a Consumer that will only be executed if the Optional is not empty.

    @Test
    public void processingOptional_ifPresent_test() {
        AtomicBoolean atomicBoolean = new AtomicBoolean(false);
        Optional.of("Hello, World")
            .ifPresent(helloWorldString -> atomicBoolean.set(true));
        assert atomicBoolean.get();
    }

    @Test
    public void processingOptional_ifPresent_empty_test() {
        AtomicBoolean atomicBoolean = new AtomicBoolean(false);
        Optional.empty()
                .ifPresent(helloWorldString -> atomicBoolean.set(true));
        assert !atomicBoolean.get();
    }

Things to avoid

There are some critical things that you need to avoid if you want to use Optional in your code.

Don’t create a method that accepts Optional

Creating a method that accepts Optional as a parameter might introduce a problem it wants to solve, NullPointerException.

If a person using the method with the Optional parameter is not aware of it, they might pass a null to the method instead of Optional.empty(). Processing a null will produce a NullPointerException.

    @Test
    public void optionalAsParameter_test() {
        try {
            isPhoneNumberPresent(null);
        } catch (Exception e) {
            assert e instanceof NullPointerException;
        }
    }

    public boolean isPhoneNumberPresent(Optional<String> phoneNumber) {
        return phoneNumber.isPresent();
    }

Getting value without checking

If you’re using Optional, then you should avoid using the get method if you can. If you still want to use it for some reason, make sure that you check it with the isPresent method first because if you use get on an empty Optional, it will produce a NoSuchMethodException.

    @Test
    public void getWithIsPresent_test() {
        Optional<String> helloWorldOptional = Optional.ofNullable(null);
        if (helloWorldOptional.isPresent()) {
            System.out.println(helloWorldOptional.get());
        }
    }

    @Test
    public void getWithoutIsPresent_error_test() {
        Optional<String> helloWorldOptional = Optional.ofNullable(null);
        try {
            System.out.println(helloWorldOptional.get());
        } catch (Exception e) {
            assert e instanceof NoSuchElementException;
        }
    }

Conclusion

Thank you for reading until the end! Optional is a powerful feature that every Java developer should know about. If you use optional features from end to end correctly, then I’m sure that you won’t meet the NullPointerException anymore.

Optional is also used as a base for other big libraries like Reactor and RXJava, so knowing how Optional works will help you understand them too.

You can find the repository with the examples in this article below:

https://github.com/brilianfird/java-optional

References

  1. Null Pointer References: The Billion Dollar Mistake
  2. https://en.wikipedia.org/wiki/Tony_Hoare#Apologies_and_retractions
  3. https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html



Great! Next, complete checkout for full access to Code Curated
Welcome back! You've successfully signed in
You've successfully subscribed to Code Curated
Success! Your account is fully activated, you now have access to all content
Success! Your billing info has been updated
Your billing was not updated