The Builder pattern vs Java syntax

The Builder pattern is one of the most popular patterns in Java.

It is simple, helps to keep objects immutable, and can be generated with tools like Project Lombok’s @Builder or Immutables, to name a few.

Example of a fluent variant of the pattern:

public class User {

  private final String firstName;

  private final String lastName;

  User(String firstName, String lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

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

  public static class Builder {

    String firstName;
    String lastName;

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

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

    public User build() {
        return new User(firstName, lastName);
    }
  }
}
User.Builder builder = User.builder().firstName("Sergey").lastName("Egorov");

if (newRules) {
    builder.firstName("Sergei");
}

User user = builder.build();

What we have here:

  1. User class is immutable and cannot be changed once we instantiate it.
  2. Its constructor has package-private visibility, one has to use the builder to instantiate an instance of User.
  3. Builder’s fields are not immutable and can be changed multiple times before building an instance of User.
  4. The setters are fluent and return this (of type Builder) and can be chained.

So… What’s the deal?

Inheritance problem

Imagine we want to extend User class:

public class RussianUser extends User {
    final String patronymic;

    RussianUser(String firstName, String lastName, String patronymic) {
        super(firstName, lastName);
        this.patronymic = patronymic;
    }

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

    public static class Builder extends User.Builder {

        String patronymic;

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

        public RussianUser build() {
            return new RussianUser(firstName, lastName, patronymic);
        }
    }
}
RussianUser me = RussianUser.builder()
    .firstName("Sergei") // returns User.Builder :(
    .patronymic("Valeryevich") // // Cannot resolve method!
    .lastName("Egorov")
    .build();

The problem here is because firstName has the following definition:

    User.Builder firstName(String value) {
        this.value = value;
        return this;
    }

And Java’s compiler has no chance to detect that this here means RussianUser.Builder and not User.Builder!

We can’t even change the order:

RussianUser me = RussianUser.builder()
    .patronymic("Valeryevich")
    .firstName("Sergei")
    .lastName("Egorov")
    .build() // compilation error! User is not assignable to RussianUser
    ;

Possible solution: Self typing

One way to solve it is to add a generic parameter to User.Builder, indicating what type to return:

  public static class Builder<SELF extends Builder<SELF>> {

    SELF firstName(String value) {
        this.firstName = value;
        return (SELF) this;
    }

and set it in RussianUser.Builder:

    public static class Builder extends User.Builder<RussianUser.Builder> {

Now it works:

RussianUser.builder()
    .firstName("Sergei") // returns RussianUser.Builder :)
    .patronymic("Valeryevich") // RussianUser.Builder
    .lastName("Egorov") // RussianUser.Builder
    .build(); // RussianUser

It also works with multiple levels of inheritance:

class A<SELF extends A<SELF>> {

    SELF self() {
        return (SELF) this;
    }
}

class B<SELF extends B<SELF>> extends A<SELF> {}

class C extends B<C> {}

So, problem solved? Well, not really… Base type cannot be easily instantiated!

Since it uses a recursive generic definition, we have a recursion problem!

new A<A<A<A<A<A<A<...>>>>>>>()

AAAA

However, it can be solved (unless you use Kotlin):

A a = new A<>();

Here we rely on Java’s raw types and the diamond operator.

But, as mentioned, it doesn’t work with other languages like Kotlin or Scala, and is, generally speaking, a hack.

Ideal solution: Self typing in Java

⚠️ Before you continue reading, I should warn you: this solution does not exist, at least not yet.
It would be nice to have it, but currently I am not aware of any JEP for it.
P.S. anyone knows how to submit a JEP? ;)

Self typing as a language feature exists in languages like Swift.

Imagine the following fictional Java example:

class A {

    @Self
    void withSomething() {
        System.out.println("something");
    }
}

class B extends A {
    @Self
    void withSomethingElse() {
        System.out.println("something else");
    }
}
new B()
    .withSomething() // replaced with the receiver instead of void
    .withSomethingElse();

As you can see, the problem can be solved at the compiler level.
In fact, there are javac compiler plugins like Manifold’s @Self for it.

Real solution: Think different

But what if instead of trying to solve the return type problem, we… remove the type?

public class User {

  // ...

    public static class Builder {

        String firstName;
        String lastName;

        void firstName(String value) {
            this.firstName = value;
        }

        void lastName(String value) {
            this.lastName = value;
        }

        public User build() {
            return new User(firstName, lastName);
        }
    }
}
public class RussianUser extends User {

    // ...

    public static class Builder extends User.Builder {

        String patronymic;

        public void patronymic(String patronymic) {
            this.patronymic = patronymic;
        }

        public RussianUser build() {
            return new RussianUser(firstName, lastName, patronymic);
        }
    }
}
RussianUser.Builder b = RussianUser.builder();
b.firstName("Sergei");
b.patronymic("Valeryevich");
b.lastName("Egorov");
RussianUser user = b.build(); // RussianUser

“This is not handy and verbose, at least in Java” - you might say.
And I agree, but… Is it the problem of the builder?

Remember I said that the builder can be mutable? So, why not to make use of it!

Let’s add the following to our base builder:

public class User {

  // ...

    public static class Builder {
        public Builder() {
            this.configure();
        }

        protected void configure() {}

And use our builder as an anonymous object:

RussianUser user = new RussianUser.Builder() {
    @Override
    protected void configure() {
        firstName("Sergei"); // from User.Builder
        patronymic("Valeryevich"); // From RussianUser.Builder
        lastName("Egorov"); // from User.Builder
    }
}.build();

The inheritance is no longer an issue, but it is still a bit verbose.

Here is where another “feature” of Java come in handy: the Double brace initialization.

RussianUser user = new RussianUser.Builder() {{
    firstName("Sergei");
    patronymic("Valeryevich");
    lastName("Egorov");
}}.build();

Here we use an initializer block to set the fields. Swing/Vaadin folks may recognize this pattern ;)

Some don’t like it (feel free to comment why, btw).
I do. I would not use it in the performance critical parts of the app, but if it is, let’s say, testing, then this approach seems to mark all the checks:

  1. Can be used with any Java version starting from the Mammoths Age.
  2. Friendly to other JVM languages.
  3. Concise.
  4. A native feature of the language, not a hack.

Conclusion

We’ve seen that while Java doesn’t provide a self-typing syntax, we can solve the problem by using another feature of Java without ruining the experience for alternative JVM languages.

Although some developers seem to think of the double brace initialization as an anti-pattern, it actually seems to have its value for certain use cases.
After all, that’s just a sugar for constructor definition inside an anonymous class.

I’m interested in how others approach this problem and what you think about the tradeoffs of the different implementations!

P.S.

Thanks a lot to Richard North and Kevin Wittek for reviewing it! 👍

comments powered by Disqus