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.