Radical Java

Abuse of the Builder pattern

The Builder pattern is a well-known construct in the Java world, and doesn’t show any sign of diminishing in popularity. It’s used in countless popular libraries, and most IDEs have an automated tool for creating a Builder for your type. It’s a convenient way of handling optional parameters in the creation of an object without having to write a cartesian product of alternative constructors, factory methods, or sacrificing immutability of the created object. It has deviated a little over time from original “Gang of Four” definitions, so in case you’re not familiar with the modern case, the usual example looks a little like this:

public class Person {
    public final String name;
    public final int age;
    public final boolean vip;

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

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

        public Builder setName(String name) {
            this.name = name;
            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(name, age, vip);
        }
    }
}

As you can see, it is somewhat verbose on the implementation side, but when the number of optional or defaulted parameters gets high, then it starts to look rather sensible at the usage site:

Person a = new Person.Builder().setName("David").build(); // use default age
Person b = new Person.Builder() 
                     .setName("Erik")  
                     .setAge(45)
                     .setVip()
                     .build();        // all fields are specified

This makes them ideal for things like configurations, where there is usually a default configuration and what you want to specify any deviations from it without specifying the whole thing again.

So what’s the problem?

Builders are great for configuring things, but recently I’ve seen them used a whole lot more in the context of data records. If you take a look around the data object representation and serialization space, there’s a whole lot of Builders being generated as part of the implementation code. Avro generates Builders to create its record classes, Protobuf won’t let you create records using any other method than a builder, and the default implementation in Immutables is to create the immutable types with a builder.

In every one of these cases, using the Builder is required to specify the *required fields as well as the optional or defaulted ones.*

If required fields are specified using a Builder, you are transferring the burden of knowing which fields are required from the compiler on to the developer. This is very bad, because we all know that relatively speaking compilers are smart and developers are stupid. If you forget to specify a field it becomes a runtime error instead of a compile-time error, and we lose some precious safety that we had for free in the land of the constructor.

In the Person example above, the following code would compile, but result in a Person with a null name, which is probably not what you want:

Person nullPerson = new Person.Builder().build();

What’s the alternative?

1: Split your Builder into parts

Using this method you put your required fields into the constructor or factory method of the Builder, then have all optional mutations as methods on the Builder. This makes it impossible to create an invalid object, while still giving you the flexibility to specify a subset of the optional functionality. That makes the usage look a bit more like this:

Person david = new Person.Builder("David").setAge(61).build();

It is now impossible to create a Person without a name.

2: Use Optional<> fields

The concept of nullable fields has been around for a while, but null is problematic for many reasons. Luckily, Java 8 introduced a standardized Optional type which is a safer alternative. Using this you make it explicit by the type definition which fields are Optional, making it safe to implement Person like this:

public class Person {
    public final String name;
    public final int age;
    public final boolean vip;

    public Person(String name, Optional<Integer> age, Optional<Boolean> vip) {
        this.name = name;
        this.age = age.orElse(21);
        this.vip = vip.orElse(false);
    }
}

/* ... */

Person david = new Person("David", Optional.empty(), Optional.empty());
Person erik = new Person("Erik", Optional.of(21), Optional.of(true));

It’s much uglier in usage as the Builder variant, but the implementation is much simpler and shorter and removes the need for a whole extra class to be created.

3: Immutable alterations

If the number of optional fields is low and you’re not allocating huge numbers of these objects, then you could start with an minimum object specified using just the required fields and make immutable edits to it to return variations with the content you need.

public class Person {
    public final String name;
    public final int age;
    public final boolean vip;

    
    public Person(String name) { // required-only constructor
        this.name = name;
        this.age = 21
        this.vip = false;
    }

    private Person(String name, int age, boolean vip) { // fully specified constructor
        this.name = name;
        this.age = age;
        this.vip = vip;
    }

    public Person withAge(int newAge) {
        return new Person(name, newAge, vip);
    }

    public Person withVipStatus() {
        return new Person(name, age, true);
    }
}

/* ... */

Person david = new Person("David");
Person erik = new Person("Erik").withAge(21).withVipStatus();

I think this is my favourite of the alternatives, but it comes at a cost. Each mutation will create a new object, which will have to be cleaned up later. This is fine for application objects, but if you’re doing data processing this will soon become a bottleneck and then you should probably consider option 1 instead.

4: Factory methods

If you’re trying to replicate just a few different concrete ways to construct an object, you might consider using factory methods instead:

public class Person {
    ...
    public static Person createNormal(String name, int age) {
        return new Person(name, age, false);
    }

    public static Person createVip(String name, int age) {
        return new Person(name, age, true);
    }
}
/* ... */

Person david = createNormal("David", 28);
Person erik = createVip("Erik", 41);

Summary

The Builder pattern is useful in a range of situations, but you shouldn’t think of it as a naive drop-in replacement for the named parameter feature that Java lacks. By doing so with required fields you move the burden of checking correctness from the compiler to the developer, and open the door to possible runtime errors. There are many good alternatives for this use case so you should consider them before reaching for the all-args Builder every time, and library authors who deal with data records should take the same advice.

Update 2016-01-14

I’ve had some correspondence on the subject that I think is worthy of note here. @whitingjp pointed out this rather sensible fifth alternative:

5: Accessible default values

A simple solution to this is to provide a single all-args constructor and have default values publicly and statically available to insert into it. This works similarly to (2), but with less developer and execution overhead. It relies on the convention of people knowing where to find the default values, but if you’re happy to make it a standard for your project or team it could be a good option for fields with default values.

public class Person {
    public final String name;
    public final int age;
    public final boolean vip;

    private Person(String name, int age, boolean vip)
        this.name = name;
        this.age = age;
        this.vip = vip;
    }

    public static class Default {
        public static final int AGE = 21;
        public static final bool VIP = false;
    }
}

/* ... */

Person david = new Person("David", Person.Default.AGE, Person.Default.VIP);

Finally, Mārtiņš pointed out one benefit of the Builder that I overlooked, and that’s that it protects you against reordering of constructor args of the same type; meaning that if you had a new Person(firstName, lastName) and switched the signature to new Person(lastName, firstName), then you wouldn’t even get a runtime error, just bad data. I don’t think that kind of sneaky behaviour should ever be encouraged, so in balance I still don’t think that’s worth having a Builder for. I did, however, knock up this solution which is very silly but fixes that problem too. I do not recommend that you do this in real life.