Question 26 · Section 13

How to implement Builder pattern for an immutable class?

Builder is justified for 4+ fields. For 1-2 fields use a constructor — Builder adds an extra class and complicates the code.

Language versions: English Russian Ukrainian

Junior Level

Builder is a pattern that helps create objects with many fields, especially when there are optional fields.

The problem: too many constructors

// "Telescoping constructors" — bad
new User("Ivan");
new User("Ivan", 25);
new User("Ivan", 25, "ivan@mail.com");
new User("Ivan", 25, "ivan@mail.com", "admin");

Solution: Builder

User user = new User.Builder("Ivan")
    .age(25)
    .email("ivan@mail.com")
    .role("admin")
    .build();

When Builder is excessive

Builder is justified for 4+ fields. For 1-2 fields use a constructor — Builder adds an extra class and complicates the code.


Middle Level

Classic implementation

public final class UserProfile {
    private final String username;  // required
    private final String email;     // optional
    private final int age;          // optional

    private UserProfile(Builder builder) {
        this.username = builder.username;
        this.email = builder.email;
        this.age = builder.age;
    }

    public String getUsername() { return username; }
    public String getEmail() { return email; }
    public int getAge() { return age; }

    public static class Builder {
        private final String username;
        private String email;
        private int age;

        public Builder(String username) {
            this.username = username; // required field
        }

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

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

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

Lombok (@Builder)

@Value      // final class, private final fields, equals/hashCode/toString
@Builder    // @Builder generates a static Builder class with methods for each field.
public class UserProfile {
    String username;
    String email;
    int age;
}

toBuilder() — for “modifying” an immutable object

UserProfile updated = user.toBuilder().age(30).build();

Senior Level

Validation in build()

The build() method is the ideal place for cross-field validation:

public UserProfile build() {
    if (email != null && !email.contains("@")) {
        throw new IllegalArgumentException("Invalid email");
    }
    return new UserProfile(this);
}

Lombok @Singular for collections

@Value @Builder
public class Order {
    String id;
    @Singular List<String> items; // automatic defensive copying
}

@Singular automatically calls List.copyOf() in build().

Why this matters for Senior

  1. Validation — object is not published until fully assembled and validated
  2. Atomicity — object either exists in a complete state or doesn’t exist at all
  3. API Design — builder is self-documenting, adding a field doesn’t break calls
  4. Immutability contract — builder is the only mutable place, the main class is fully immutable

Summary for Senior

  • Builder for objects with 4+ fields
  • Combine Builder with final fields of the main class
  • Lombok @Builder + @Singular — minimal boilerplate with defensive copying
  • Always add toBuilder() for convenient modification
  • build() — place for final validation

Interview Cheat Sheet

Must know:

  • Builder solves “telescoping constructors” — convenient parameter names via fluent API
  • Classic implementation: static inner Builder class with chaining, build() creates immutable object
  • Private constructor of immutable class accepts Builder — the only mutable place
  • Lombok @Builder + @Value — minimal boilerplate; @Singular automatically calls List.copyOf()
  • toBuilder() — for convenient “modification” of immutable object
  • build() — place for cross-field validation

Frequent follow-up questions:

  • When is Builder justified? — 4+ fields, especially with optional ones
  • What does @Singular do? — Automatic defensive copying of collections (List.copyOf())
  • Builder vs Record? — Record for simple DTOs; Builder for complex objects with validation
  • Why private constructor? — So the object is only created through Builder with validation

Red flags (do NOT say):

  • “Builder makes the class mutable” — Builder is mutable, the main class is final with final fields
  • “Validation in Record constructor is better” — Builder allows validation BEFORE object creation
  • “@Builder without @Singular is safe” — without @Singular collections are not copied
  • “Builder is always needed” — for 1-2 fields a constructor is simpler

Related topics:

  • [[3. How to create an immutable class in Java]]
  • [[20. What is Record and how does it help create immutable classes]]
  • [[25. Are there disadvantages to immutable objects]]
  • [[29. How to properly work with collections in immutable classes]]