Skip to main content

The Builder Pattern

Category: Creational

The Builder pattern is used to construct a complex object step by step. It allows you to produce different types and representations of an object using the exact same construction code.


The Problem: Telescoping Constructors​

Imagine you have a User class. A user must have a firstName and a lastName. But they might optionally have an age, a phone, and an address.

To handle this, developers usually create multiple constructors (known as the Telescoping Constructor anti-pattern):

public User(String firstName, String lastName) { ... }
public User(String firstName, String lastName, int age) { ... }
public User(String firstName, String lastName, int age, String phone) { ... }
// ... and so on

This becomes a nightmare to read and maintain. Imagine reading this in code: new User("John", "Doe", 0, null, "123 Main St");. What does the 0 mean? What does the null mean?


The Solution: The Builder​

The Builder pattern extracts the object construction code out of its own class and moves it to separate objects called builders.

1. The Target Class​

The target class is made immutable (no setter methods). Its constructor is private, and it only accepts the Builder object as an argument.

public class User {
// Required fields
private final String firstName;
private final String lastName;

// Optional fields
private final int age;
private final String phone;
private final String address;

// Private constructor! Only the Builder can call this.
private User(UserBuilder builder) {
this.firstName = builder.firstName;
this.lastName = builder.lastName;
this.age = builder.age;
this.phone = builder.phone;
this.address = builder.address;
}

// Getters only (no setters, making it immutable)
public String getFirstName() { return firstName; }
public String getLastName() { return lastName; }
// ... other getters ...

@Override
public String toString() {
return firstName + " " + lastName + " (Age: " + age + ")";
}

2. The Builder Class​

The Builder class is usually implemented as a public static nested class inside the target class. It has the same fields as the target class.

// Nested static Builder class
public static class UserBuilder {
// Required fields
private final String firstName;
private final String lastName;

// Optional fields (initialized to default values)
private int age = 0;
private String phone = "";
private String address = "";

// Constructor for REQUIRED fields
public UserBuilder(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}

// Methods for OPTIONAL fields (Notice they return 'this' for chaining)
public UserBuilder age(int age) {
this.age = age;
return this;
}

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

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

// The final build method that creates the actual User object
public User build() {
return new User(this);
}
}
} // End of User class

3. Client Code​

Now look at how incredibly readable the object construction becomes! This is called Method Chaining or a Fluent Interface.

public class LabBuilder1 {

public static void main(String[] args) {
// Constructing a complex User object step by step
User user = new User.UserBuilder("John", "Doe")
.age(30)
.phone("555-1234")
.address("123 Main St")
.build(); // Finally, create the User

System.out.println(user);
}
}

[!TIP] If you are using Lombok in your Java project, you don't need to write any of this boilerplate code manually! You can simply add the @Builder annotation to your class, and Lombok will automatically generate the entire Builder class for you at compile time.