1. Overview

In this tutorial, we'll look at the Builder Pattern and how we can implement it in Java.

2. When to Use

Let's say that we have a class with multiple properties. Moreover, it has various construction rules leading to multiple internal representations.

To handle each representation, we can create multiple constructors. However, this can be cumbersome and hard to manage. It also leads to duplicate code.

Telescoping constructors can help us in reducing the code duplication. But nevertheless, creating a different class to handle this construction process is a better approach.

3. How to Implement

The Builder Pattern has different flavors. We'll investigate the builder implementation that is created as an inner static class:

public class Person {

  private final String name;

  private final String lastName;

  private final int age;

  private final String profession;

  private final List<String> hobbies;

  private Person(String name, String lastName, int age, String profession, List<String> hobbies) {
    this.name = name;
    this.lastName = lastName;
    this.age = age;
    this.profession = profession;
    this.hobbies = hobbies == null ? new ArrayList<>() : new ArrayList<>(hobbies);
  }

  // Getters...

  public static Builder builder(String name, String lastName) {
    return new Builder(name, lastName);
  }
  
  public static class Builder {

    private final String name;

    private final String lastName;

    private int age;

    private String profession;

    private List<String> hobbies = new ArrayList<>();

    public Builder(String name, String lastName) {
      this.name = name;
      this.lastName = lastName;
    }

    public Builder age(int age) {
      if (age < 0) {
        throw new IllegalArgumentException("Age cannot be smaller than 0");
      }

      this.age = age;
      return this;
    }

    public Builder profession(String profession) {
      if (profession == null) {
        throw new IllegalArgumentException("Profession cannot be empty");
      }

      this.profession = profession;
      return this;
    }

    public Builder add(String hobby) {
      if (hobby == null || hobby.isEmpty()) {
        throw new IllegalArgumentException("Hobby cannot be empty");
      }

      this.hobbies.add(hobby);
      return this;
    }

    public Person build() {
      return new Person(name, lastName, age, profession, hobbies);
    }
  }
}

Here we have the Person and Builder classes. Since the builder class constructs the target object, it must define the same properties. As we can see Builder has all the properties Person defines.

Immutability is another important point when working with builders. The target class must be immutable. Hence Person has a private constructor and doesn't have any setter methods.

Additionally, the builder class maintains the variants by validating the input. As a result, we can't have a Person object with an inconsistent state.

Lastly, let's look at the client code:

public class ClientMain {

  public static void main(String[] args) {
    Person person =
        new Person.Builder("John", "Doe")
            .age(20)
            .profession("Engineer")
            .add("Fishing")
            .add("Games")
            .add("Football")
            .build();

    Person person2 =
        Person.builder("Jane", "Doe")
            .age(20)
            .profession("Doctor")
            .add("Fishing")
            .add("Games")
            .add("Football")
            .build();
  }
}

4. Summary

In this tutorial, we've looked at the usage of the Builder Pattern.

As always, the source code for all examples is available on Github