Builder Design Pattern

Builder pattern is a creational design pattern that builds a complex object step by step.
It separates the construction of a complex object from its representation, so that the same construction process can create different representations.

Confusing right ??

Let’s understand this with the help on an example.
Suppose you have an object to create a user profile which contains user’s first name and last name.

You can easily achieve the same as shown below.

class UserProfile {
  private String firstName;
  private String lastName;
  
  public UserProfile(String firstName, String lastName){
    this.firstName = firstName;
    this.lastName = lastName;
  }
}

class CreateUserProfiles {
  public static void main(String[] args) {
    UserProfile userProfile = new UserProfile("Java", "Guy");
  }
}

So far so good …

But now your clients want to have all the below as fields for your user profile.

  • firstName
  • lastName
  • email
  • phone
  • address
  • preferences
  • settings
  • newsletter subscription

with firstName and lastName as mandatory filelds and remaining as optional fields.

Now one way to achieve this is:

class UserProfile {

  // Mandatory
  private String firstName;
  private String lastName;
  
  // Optional
  private String email;
  private String phone;
  private String address;
  private List<String> preferences;
  private List<String> settings;
  private boolean requireNewsletter;
  
  public UserProfile( String firstName, String lastName,
                     String email, String phone, String address,
                     List<String> preferences, List<String> settings,
                     boolean requireNewsletter ) {
                     
    this.firstName = firstName;
    this.lastName = lastName;
    this.email = email;
    this.phone = phone;
    this.address = address;
    this.preferences = preferences;
    this.settings = settings;
    this.requireNewsletter = requireNewsletter;
  }
}

class CreateUserProfiles {
  public static void main(String[] args) {
    UserProfile userProfile = new UserProfile("Java", "Guy", null, null, null, null, null, false);
  }
}

By now, you would have understood how inconvenient it is for the CreateUserProfiles class to create a user profile with all the forceful nulls in the object creation.

But what can we instead 🤔.

Let’s define a separate constructor for each of the optional fields.

class UserProfile {

    // Mandatory
    private String firstName;
    private String lastName;

    // Optional
    private String email;
    private String phone;
    private String address;
    private List<String> preferences;
    private List<String> settings;
    private boolean requireNewsletter;

    // Constructor 1: Only mandatory
    public UserProfile(String firstName, String lastName) {
        this(firstName, lastName, null);
    }

    // Constructor 2: + email
    public UserProfile(String firstName, String lastName, String email) {
        this(firstName, lastName, email, null);
    }

    // Constructor 3: + phone
    public UserProfile(String firstName, String lastName, String email, String phone) {
        this(firstName, lastName, email, phone, null);
    }

    // Constructor 4: + address
    public UserProfile(String firstName, String lastName, String email, 
                        String phone, String address) {
                        
        this(firstName, lastName, email, phone, address, null);
    }

    // Constructor 5: + preferences
    public UserProfile(String firstName, String lastName, String email, 
                        String phone, String address,
                       List<String> preferences) {
                       
        this(firstName, lastName, email, phone, address, preferences, null);
    }

    // Constructor 6: + settings
    public UserProfile(String firstName, String lastName, String email, 
                       String phone, String address,
                       List<String> preferences, List<String> settings) {
                       
        this(firstName, lastName, email, phone, address, preferences, settings, false);
    }

    // Constructor 7: Full version
    public UserProfile(String firstName, String lastName, String email, 
                       String phone, String address,
                       List<String> preferences, List<String> settings, 
                       boolean requireNewsletter) {

        this.firstName = firstName;
        this.lastName = lastName;
        this.email = email;
        this.phone = phone;
        this.address = address;
        this.preferences = preferences;
        this.settings = settings;
        this.requireNewsletter = requireNewsletter;
    }
}

Do you start to notice a problem ??

This is known as the Telescoping Constructor Anti-Pattern. It results in a chain of constructors that:

  • Are hard to read and write
  • Become error-prone because the parameter order is easy to mix up
  • Are difficult to maintain as more fields are added
  • Are inflexible, forcing users to supply arguments in a strict sequence

Now just imagine that your clients want you to add 20 new optional fields in the user profile class 🤯.

It’s alright… don’t panic… stay cool, and breathe …

Builder Pattern to the rescue …

To fix the constructor issues we saw earlier, we use the Builder Pattern. It separates how an object is built from what it represents, letting us create it step-by-step while keeping the result clean, readable, and immutable.

class UserProfile {

    // Mandatory
    private final String firstName;
    private final String lastName;

    // Optional
    private final String email;
    private final String phone;
    private final String address;
    private final List<String> preferences;
    private final List<String> settings;
    private final boolean requireNewsletter;
    
    private UserProfile( UserProfileBuilder builder ){
      this.firstName = builder.firstName;
      this.lastName = builder.lastName;
      this.email = builder.email;
      this.phone = builder.phone;
      this.address = builder.address;
      this.preferences = builder.preferences;
      this.settings = builder.settings;
      this.requireNewsletter = builder.requireNewsletter;
    } 
    
    public static class UserProfileBuilder {
          // Mandatory
          private final String firstName;
          private final String lastName;
      
          // Optional
          private String email;
          private String phone;
          private String address;
          private List<String> preferences;
          private List<String> settings;
          private boolean requireNewsletter;
          
          public UserProfileBuilder(String firstName, String lastName) {
            this.firstName = firstName;
            this.lastName = lastName;
          }
          
          public UserProfileBuilder withEmail(String email) {
            this.email = email;
            return this;
          }
          
          public UserProfileBuilder withPhone( String phone ) {
            this.phone = phone;
            return this;
          }
          
          public UserProfileBuilder withAddress( String address ) {
            this.address = address;
            return this;
          }
          
          public UserProfileBuilder withPreferences( List<String> preferences ) {
            this.preferences = preferences;
            return this;
          }
          
          public UserProfileBuilder withSettings( List<String> settings ) {
            this.settings = settings;
            return this;
          }
          
          public UserProfileBuilder withRequireNewsletter( boolean requireNewsletter ) {
            this.requireNewsletter = requireNewsletter;
            return this;
          }
  
          public UserProfile build() {
            return new UserProfile(this);
          }
    }
}

Now let’s understand what we did in the above code:

  • The UserProfile constructor is private, so objects can only be created through the builder.
  • This builder class contains the same fields as UserProfile (with the slight exception that here the optional fields are not marked final), maintaining immutability and controlling object creation.
  • Each method (like withEmail, withPhone) returns the builder itself, allowing readable method chaining.
  • Mandatory fields (firstName, lastName) are passed to the builder’s constructor, while all other fields are optional and set via withFieldName() methods.
  • After setting all desired fields, calling .build() completes object creation and returns the fully constructed UserProfile instance.

OK… But how do we now create UserProfile objects using this builder design pattern??

That’s the easy part…

class CreateUserProfiles {
  public static void main(String[] args) {
     UserProfile userProfile = new UserProfile.UserProfileBuilder("Java", "Guy")
                                    .withEmail("justanotherjavaguy@gmail.com") // optional
                                    .withRequireNewsletter(true) // optional
                                    .build();
  }
}

  • Mandatory fields (firstName, lastName) are passed to the builder constructor.
  • Optional fields are set fluently using withEmail() and withRequireNewsletter().
  • .build() finalizes the object and returns an immutable UserProfile instance.

If you’re using Spring Boot or similar frameworks, you can leverage Lombok’s @Builder annotation to generate a builder automatically, instead of writing the builder class from scratch like this:

@Builder
class UserProfile {

    private String firstName;
    private String lastName;
    private String email;
    private String phone;
    private String address;
    private List<String> preferences;
    private List<String> settings;
    private boolean requireNewsletter;
    
}

And with that, we’ve wrapped up the Builder Design Pattern — you’re now fully equipped with everything you need to know on Builder 🥳.