Radical Java

The Safe Builder Pattern

As you may have read in a previous post called Abuse of the Builder Pattern, there are some problems with the widespread adoption of the Builder pattern as the most popular way to create large or complex objects which may have optional fields. The one I highlighted most was that it doesn’t actually give you any guarantees at compile time that your object is fully specified, which could lead to - at best - runtime errors on creation, or - at worst - silent unexpected non-initialized values or NullPointerExceptions.

Here I present a possible solution for getting round this problem in situation where (a) you are convinced that a Builder is the nicest API for what you want to do (b) you want a compile-time guarantee that the object is fully-specified and (c) you don’t mind putting in a little extra work and code to make it happen. It’s inspired partially by the safeBuild idiom in Twitter’s Finagle project, but with extra use of Java 8 Lambdas to reduce the complexity for the user. The final result looks a bit like this:

new Person(build -> build.setFirstName("David").setLastName("Rave").setAge(28));

Note that this article is less dogmatic than some of my previous ones, as I will not attempt to convince you that doing this is a good tradeoff of effort vs. reward. I’m exploring it for its academic value, not for its practical usefulness. (edit: I had to put this in bold because people just didn’t read this paragraph)

The concept behind this approach is that you exploit the Java compiler’s handling of type arguments, but instead of using those type arguments elsewhere in the functioning part of your code, you instead use them as flags to denote whether each required part of your Builder has been specified yet.

Let’s take the example from the previous post, but split the required field into two because it helps with the demonstration.

public class Person {
    public final String firstName; 
    public final String lastName;  
    public final int age;
    public final boolean vip;

    private Person(String firstName, String lastName, int age, boolean vip) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
        this.vip = vip;
    }

    public static class Builder {
        private String firstName;    // required
        private String lastName;     // required
        private int age = 21;        // assume user is 21 if we don't know
        private boolean vip = false; // most people aren't VIPs

        public Builder setFirstName(String firstName) {
            this.firstName = firstName;
            return this;
        }

        public Builder setLastName(String lastName) {
            this.lastName = lastName;
            return this;
        }

        public Builder setAge(int age) {
            this.age = age;
            return this;
        }

        public Builder vip() {
            this.vip = true;
            return this;
        }

        public Person build() {
            return new Person(firstName, lastName, age, vip);
        }
    }
}

Next we need some types to use as flags. We could use any types here, but for the sake of exposing an acceptable API to the user, let’s create some new ones:

interface IsSpecified {}
interface N extends IsSpecified {}
interface Y extends IsSpecified {}

Then we need to parameterise the Builder with as many type arguments as required fields so that we can flip the correct type argument from N to Y when the field is specified. Here we have 2 required fields: “firstName” and “lastName”, so we need two type arguments for the builder.

public static class Builder<FirstName extends IsSpecified, LastName extends IsSpecified> { ... }

Then we can alter the setFirstName and setLastName methods to return altered versions of the type signature (remember we aren’t actually using these type arguments so the cast here is safe).

@SuppressWarnings("unchecked")
public Builder<Y, LastName> setFirstName(String firstName) {
    this.firstName = firstName;
    return (Builder<Y, LastName>)this;
}

@SuppressWarnings("unchecked")
public Builder<FirstName, Y> setLastName(String lastName) {
    this.lastName = lastName;
    return (Builder<FirstName, Y>)this;
}

Now we know that if we start with a Builder<N, N>, it will be safe to build the object when we have a Builder<Y, Y>, because that means that both fields are specified.

However, we have no mechanism to declare a method on the builder that can only be used when it has certain type parameters, so we must declare it outside the Builder itself. Also, we need somewhere to get our Builder<N, N> from without having to explain this whole setup.

Let’s think about this in a functional way. What we actually need the user to provide is a function for transforming a Builder<N, N> to a Builder<Y, Y>. If they give us that function then we can use it to construct that object with the correct fields specified. That way, the user doesn’t have to worry about creating a builder then building from it, but instead just transforming the one they are given until it is valid. Instead of having a “build” method on the Builder, we can either put a static method or an extra constructor on the Person class:

public Person(Function<Builder<N, N>, Builder<Y, Y>> buildFn) {
    this(buildFn.apply(new Builder<>()));
}

private Person(Builder<Y, Y> completeBuilder) {
    this(completeBuilder.firstName, completeBuilder.lastName, completeBuilder.age, completeBuilder.vip);
}

Let’s put all this together:

public class Person {
    public final String firstName;
    public final String lastName;
    public final int age;
    public final boolean vip;

    private Person(String firstName, String lastName, int age, boolean vip) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
        this.vip = vip;
    }

    public Person(Function<Builder<N, N>, Builder<Y, Y>> buildFn) {
        this(buildFn.apply(new Builder<>()));
    }

    private Person(Builder<Y, Y> completeBuilder) {
        this(completeBuilder.firstName, completeBuilder.lastName, completeBuilder.age, completeBuilder.vip);
    }

    interface IsSpecified {}
    interface N extends IsSpecified {}
    interface Y extends IsSpecified {}

    public static class Builder<FirstName extends IsSpecified, LastName extends IsSpecified> {
        private Builder() {}
        private String firstName;
        private String lastName;
        private int age = 21;        // assume user is 21 if we don't know
        private boolean vip = false; // most people aren't VIPs

        @SuppressWarnings("unchecked")
        public Builder<Y, LastName> setFirstName(String firstName) {
            this.firstName = firstName;
            return (Builder<Y, LastName>)this;
        }

        @SuppressWarnings("unchecked")
        public Builder<FirstName, Y> setLastName(String lastName) {
            this.lastName = lastName;
            return (Builder<FirstName, Y>)this;
        }

        public Builder<FirstName, LastName> setAge(int age) {
            this.age = age;
            return this;
        }

        public Builder<FirstName, LastName> vip() {
            this.vip = true;
            return this;
        }
    }

    public static void exampleUsage() {
        // These work because they specify first and last name
        new Person(build -> build.vip().setFirstName("David").setLastName("Rave"));
        new Person(build -> build.setFirstName("David").setLastName("Rave").setAge(28));

        // This one doesn't compile because lastName is not set and therefore the type is Builder<Y, N> still
        new Person(build -> build.vip().setFirstName("David"));
    }
}

Witchcraft, right?

As I said before I haven’t made up my mind whether this is something that really warrants a real-world use case, but it’s fun nonetheless. If you do see it being used well out in the wild somewhere, let me know and maybe it’ll help me make up my mind. As always, my address for correspondence is i.am@thewit.ch