Java Builder Pattern example

20 March 2014
By Gonçalo Marques
In this tutorial we will see how to implement the Builder design pattern in Java.

Builder Design Pattern

The Builder Design Pattern is a creational pattern that solves the problem of excessive constructor overloading (or telescoping constructor), where the number of required constructors grows exponentially depending on the number of available constructor parameters.

When the number of overloaded constructors grow out of control in a given class, one will naturally have difficulties to understand which constructors initialize what and also wonders if the constructed objects will be built in a consistent state. Less cautious developers may even use a constructor which accepts more parameters than they actually need and pass null into the apparently not needed parameters.

From this point the resulting code will become a complete mess and also hard to use, understand and maintain.

The builder pattern solves this problem elegantly as we will see in the next sections.

The domain class

We will use a class that represents a User. The class definition, suffering already from constructor overloading follows next:

User class definition

public class User {

  private String username;
  private int age;
  private String email;
  private boolean active;

  public User(String username) {
    this.username = username;
  }

  public User(String username, int age) {
    this(username);
    this.age = age;
  }

  public User(String username, int age, String email) {
    this(username, age);
    this.email = email;
  }

  public User(String username, int age, String email, boolean active) {
    this(username, age, email);
    this.active = active;
  }

  public User(String username, String email) {
    this(username);
    this.email = email;
  }

  // ...
  // Constructors would keep growing in order to 
  // combine all available parameters


  public String getUsername() {
    return username;
  }

  public int getAge() {
    return age;
  }

  public String getEmail() {
    return email;
  }

  public boolean isActive() {
    return active;
  }

}

We only defined 5 constructors but we would need more if we needed to combine all the available parameters.

The builder

We will now provide a different class definition containing a builder that will help constructing new instances (this time we are also providing the User class getters):

User class containing a builder

public class User {

  private final String username;
  private final int age;
  private final String email;
  private final boolean active;

  private User(UserBuilder userBuilder) {
    this.username = userBuilder.username;
    this.age = userBuilder.age;
    this.email = userBuilder.email;
    this.active = userBuilder.active;
  }

  public String getUsername() {
    return username;
  }

  public int getAge() {
    return age;
  }

  public String getEmail() {
    return email;
  }

  public boolean isActive() {
    return active;
  }

  public static class UserBuilder {

    private String username;
    private int age;
    private String email;
    private boolean active;

    public UserBuilder setUsername(String username) {
      this.username = username;
      return this;
    }

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

    public UserBuilder setEmail(String email) {
      this.email = email;
      return this;
    }

    public UserBuilder setActive(boolean active) {
      this.active = active;
      return this;
    }

    public User build() {
      return new User(this);
    }

  }

}

We used a static inner class to define the builder so it becomes somewhat coupled to the type it's actually creating (instances of type User).

The builder has the same properties of the instance it's creating. Each setter will define a single property and return the builder itself to the caller, so we may chain the setter methods and finally invoke the build method:


User user = new UserBuilder()
            .setUsername("johndoe")
            .setAge(26)
            .setEmail("[email protected]")
            .build();

We have just constructed a User instance defining only the username, age and email using the builder.

You may note that we are passing the builder instance itself into the User constructor. This way we may define the User properties as final, resulting in an immutable User instance.

Mandatory parameters

If we need the constructed User instance to have mandatory properties we may define our builder with the appropriate constructor:

Builder with mandatory username parameter

public class User {

  private final String username;
  private final int age;
  private final String email;
  private final boolean active;

  private User(UserBuilder userBuilder) {
    this.username = userBuilder.username;
    this.age = userBuilder.age;
    this.email = userBuilder.email;
    this.active = userBuilder.active;
  }

  public String getUsername() {
    return username;
  }

  public int getAge() {
    return age;
  }

  public String getEmail() {
    return email;
  }

  public boolean isActive() {
    return active;
  }

  public static class UserBuilder {

    private final String username;
    private int age;
    private String email;
    private boolean active;

    public UserBuilder(String username) {
      this.username = username;
    }

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

    public UserBuilder setEmail(String email) {
      this.email = email;
      return this;
    }

    public UserBuilder setActive(boolean active) {
      this.active = active;
      return this;
    }

    public User build() {
      return new User(this);
    }

  }

}

Now we guarantee that the username property must be set. Usage would become:


User user = new UserBuilder("johndoe")
            .setAge(26)
            .setEmail("[email protected]")
            .build();

Validation

We may need to validate if the User instance is properly constructed. A suitable place to do it is inside the build method as follows (we are only showing the build method for clarity):

Validating the constructed instance

public User build() {
  User user = new User(this);
  if (user.age < 18) {
    throw new IllegalArgumentException("User age must be over 18");
  }
  return user;
}

Remember that we should always perform validation against the constructed instance. This way we ensure thread safety (if the builder is shared across multiple threads, and we validate against the builder parameters, another thread may change the builder parameters after validation has occurred and we may end up with an inconsistent User instance).

Reference

Builder pattern

Download source code from this article

Related Articles

Comments

About the author
Gonçalo Marques is a Software Engineer with several years of experience in software development and architecture definition. During this period his main focus was delivering software solutions in banking, telecommunications and governmental areas. He created the Bytes Lounge website with one ultimate goal: share his knowledge with the software development community. His main area of expertise is Java and open source.

GitHub profile: https://github.com/gonmarques

He is also the author of the WiFi File Browser Android application: