Extending the builder pattern with null safety

As we learned in the previous blog Achieve thread-safety with immutability the builder pattern is a very good design to follow to make your code safer. Another common, perhaps the most common, error that occurs in applications is the NullPointerException. So in this article, we are going to look at how we can use design to eliminate null.

I call it my billion-dollar mistake… At that time, I was designing the first comprehensive type system for references in an object-oriented language. My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn’t resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.
– Tony Hoare, inventor of ALGOL W.What is an Optional?

Java 8 comes with some truly amazing new features that you definitely should check out and start using already if you aren’t. One of these features is java.util.Optional[1].

An optional wrap an object and provides a couple of really useful methods for dealing with that object in a safe way. Optional’s are by design very declarative which makes the code that uses it very easy to read, and it also works very well with java.util.stream[2], another great Java 8 feature. But the main benefit of using it is that you do not have to check for null as long as you use it properly. If you start adopting this throughout your application then you shouldn’t need to check for null anymore, doesn’t that sound great?

We can use two different methods to create an Optional.


Optional optName = Optional.ofNullable("Viktor");
Optional optCity = Optional.of("Stockholm");

The first method creates an Optional containing the object and if the object was null it creates an empty Optional. The second option throws a NullPointerException if the object is null. I recommend mostly using the first option as the whole point of Optional is to eliminate NullPointerExceptions. But if you wanna make sure that your Optional always contains an object then, of course, feel free to use the second version.

When using an Optional we have a couple of different methods that we can use.

if (optName.isPresent()) { 
    System.out.println(optName.get());
}

optName.ifPresent(System.out::println);
System.out.println(optName.orElse("Unknown"));
System.out.println(optName.orElseThrow(RuntimException::new));

Before we continue with more examples, let’s integrate this into our builder pattern. We extend our ImmutablePerson so that a person can have a website. We also realize that a person doesn’t necessarily need to have a list of favorite dishes. So we end up with the following fields.


// Mandatory fields
private final String name;
private final String city;

// Optional fields
private final List favoriteDishes; // is empty list otherwise
private final Website website;

The full class looks like the following.


package org.thecuriousdev.demo.nullsafe;

import com.google.common.base.Preconditions;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

public class ImmutablePerson {

    private static final String PARAM_NAME = "name";
    private static final String PARAM_CITY = "city";

    private final String name;
    private final String city;
    private final List favoriteDishes;
    private final Website website;

    private ImmutablePerson(Builder builder) {
        Preconditions.checkArgument(builder.name != null, PARAM_NAME);
        Preconditions.checkArgument(builder.city != null, PARAM_CITY);
        this.name = builder.name;
        this.city = builder.city;
        this.favoriteDishes = builder.favoriteDishes != null ? builder.favoriteDishes : Collections.emptyList();
        this.website = builder.website;
    }

    public String getName() {
        return name;
    }

    public String getCity() {
        return city;
    }

    public List getFavoriteDishes() {
        return new ArrayList<>(favoriteDishes);
    }

    public Optional getWebsite() {
        return Optional.ofNullable(website);
    }

    public Builder toBuilder() {
        return new Builder(this);
    }

    public static Builder builder() {
        return new Builder();
    }

    public static final class Builder {

        private String name;
        private String city;
        private List favoriteDishes;
        private Website website;

        public Builder() {
            // Empty constructor
        }

        public Builder(ImmutablePerson person) {
            this.name = person.name;
            this.city = person.city;
            this.favoriteDishes = new ArrayList<>(person.favoriteDishes);
        }

        public Builder withName(String name) {
            this.name = name;
            return this;
        }

        public Builder withCity(String city) {
            this.city = city;
            return this;
        }

        public Builder withFavoriteDishes(List dishes) {
            this.favoriteDishes = dishes != null ? new ArrayList<>(dishes) : null;
            return this;
        }

        public Builder withWebsite(Website website) {
            this.website = website;
            return this;
        }

        public ImmutablePerson build() {
            Preconditions.checkState(name != null, PARAM_NAME);
            Preconditions.checkState(city != null, PARAM_CITY);
            return new ImmutablePerson(this);
        }
    }
}

Many advocators of Optional believes that Optional’s should only be used as a return value, and we follow that rule with our ImmutablePerson class. The getters for our class will never have to be checked for null because if the value is not mandatory, we always wrap it in an Optional. Because the object is immutable, an update to the object have to go through the builder pattern with calling build(). In our build method we use Preconditions[3] from Guava to always verify the state. If the mandatory fields would be null, we would throw an IllegalStateException. After that, it goes through the private constructor for the ImmutablePerson class where we verify the arguments again (beacuse they could have been concurrently updated after our checks in build() and throw an IllegalArgumentException if they would be null.

Our website class looks like the following.


package org.thecuriousdev.demo.nullsafe;

import com.google.common.base.Preconditions;

import java.util.Optional;

public class Website {

    private static final String PARAM_URL = "url";

    private final String url;
    private final String rssFeedUrl;

    private Website(Builder builder) {
        Preconditions.checkArgument(builder.url != null, PARAM_URL);
        this.url = builder.url;
        this.rssFeedUrl = builder.rssFeedUrl;
    }

    public String getUrl() {
        return url;
    }

    public Optional getRssFeedUrl() {
        return Optional.ofNullable(rssFeedUrl);
    }

    public Builder toBuilder() {
        return new Builder(this);
    }

    public static Builder builder() {
        return new Builder();
    }

    public static final class Builder {

        private String url;
        private String rssFeedUrl;

        public Builder() {
            // Empty constructor
        }

        public Builder(Website website) {
            this.url = website.url;
            this.rssFeedUrl = website.rssFeedUrl;
        }

        public Builder withUrl(String url) {
            this.url = url;
            return this;
        }

        public Builder withRssFeedUrl(String rssFeedUrl) {
            this.rssFeedUrl = rssFeedUrl;
            return this;
        }

        public Website build() {
            Preconditions.checkState(url != null, PARAM_URL);
            return new Website(this);
        }
    }
}

Optional during use

So we have our thread and null safe object. Let’s put them to use by writing a test verifying that they really are safe from null.


@Test(expected = IllegalStateException.class)
public void cannotBuildWithNull() {
    ImmutablePerson.builder()
            .withName(null)
            .withCity("Stockholm")
            .build();
}

@Test
public void buildAndVerifyOptionals() {
    String url = "thecuriousdev.org";
    String rssFeedUrl = "thecuriousdev.org/rss";

    ImmutablePerson p1 = ImmutablePerson.builder()
            .withName("Viktor")
            .withCity("Stockholm")
            .build();

    assertNotNull(p1.getName());
    assertNotNull(p1.getCity());
    assertTrue(p1.getFavoriteDishes().isEmpty());
    assertFalse(p1.getWebsite().isPresent());

    Website w1 = Website.builder()
            .withUrl(url)
            .build();

    ImmutablePerson p2 = p1.toBuilder()
            .withWebsite(w1)
            .build();

    assertTrue(p2.getWebsite().isPresent());
    assertEquals(url, p2.getWebsite().map(Website::getUrl).orElse(null));

    Website w2 = w1.toBuilder()
            .withRssFeedUrl(rssFeedUrl)
            .build();

    ImmutablePerson p3 = p2.toBuilder()
            .withWebsite(w2)
            .build();

    assertEquals(rssFeedUrl, p3.getWebsite().flatMap(Website::getRssFeedUrl).orElse(null));
}

It works! We are not allowed to create objects where mandatory fields are null. And as shown in the second test we can easily perform tests with the declarative methods of Optional.

My favorite features of the Optional is the map and flatMap methods which takes a function that maps it to another value. It really makes it easy to use them.

Final words

So as you can see (and hopefully agree), Optional is not only good for adding safety against null. Personally, it has completely changed the way I write code. I would love to hear your opinions on it.

The code is available on GitHub.

If you enjoyed the post, please help me out by giving it a like and sharing it on social media.

Sources

https://docs.oracle.com/javase/8/docs/api/java/util/Optional.htmlhttps://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.htmlhttps://github.com/google/guava/blob/master/guava/src/com/google/common/base/Preconditions.java

Leave a Reply