Skip to content

Design Pattern: Builder (Creational)

Objects with many optional parts

Builds on Constructors and Fluent API and annotations

Stage 3core1 min read

1. Core Concepts

The Builder Pattern is a Creational Design Pattern (a pattern about how objects get created). It builds a complex object step by step.

Why use it?

  • Eliminates Telescoping Constructors: You do not need 10 constructors for different combinations of fields.
  • Readable Code: Using .withCheese(true) is much clearer than just passing true in a long list of arguments.
  • Controlled Construction: You can check the data before the object is created.

2. Java Specifics Used

private final Fields

By making the fields in the main class final, we get Immutability (the object cannot change after it is built). Once build() is called, the BurgerMeal is fixed. This is also thread-safe, because several threads can read it and none can change it.

static Nested Class

The Builder is inside the BurgerMeal class and marked as static.

  • Static: Lets us call new BurgerMeal.BurgerMealWithBuilder("wheat", "non-veg") without a BurgerMeal object first.
  • Nested: Keeps the builder next to the class it builds, so the code is easy to find.

private Constructor

The constructor of the main class is private. This forces developers to use the Builder, so no "half-finished" or "invalid" object is created by hand.

return this; (Fluent Interface)

Each optional method returns the builder itself. This allows Method Chaining: calls written one after another, so the code reads like a sentence.

3. Comparison

FeatureTelescoping (Bad)Builder (Good)
Optional ParamsRequires null or multiple constructorsJust skip the method call
ReadabilityHard to tell what each value doesExplicit method names (e.g., withSide)
ImmutabilityHard to achieve with many paramsEasily achieved with final fields

In this topic

After code1 file

After/Main.java103 linesGitHub
package DesignPatterns.CreationalPatterns.BuilderPattern.After;import java.util.List;class BurgerMeal {    // 1. Immutability: Use 'final' so the fields cannot be changed after the object is built.    private final String breadType;    private final String patty;    private final boolean hasCheese;    private final String side;    private final List<String> drinks;    // 2. Private Constructor: Prevents direct instantiation.    // The ONLY way to create a BurgerMeal is through the Builder.    private BurgerMeal(BurgerMealWithBuilder builder) {        this.breadType = builder.breadType;        this.patty = builder.patty;        this.hasCheese = builder.hasCheese;        this.side = builder.side;        this.drinks = builder.drinks;    }    // 3. Static Nested Class: Being 'static' means we can create the Builder    // without needing an existing instance of BurgerMeal.    public static class BurgerMealWithBuilder {        private String breadType;        private String patty;        private boolean hasCheese;        private String side;        private List<String> drinks;        // 4. Required Parameters: The constructor of the builder enforces mandatory fields.        public BurgerMealWithBuilder(String breadType, String patty) {            this.breadType = breadType;            this.patty = patty;        }        // 5. Fluent API: Methods return 'this' (the current builder instance)        // allowing us to chain methods like .withCheese().withSide().        public BurgerMealWithBuilder withCheese(boolean hasCheese) {            this.hasCheese = hasCheese;            return this;        }        public BurgerMealWithBuilder withSide(String side) {            this.side = side;            return this;        }        public BurgerMealWithBuilder withDrinks(List<String> drinks) {            this.drinks = drinks;            return this;        }        // 6. The "Build" method: Finally converts the builder into the real object.        public BurgerMeal build() {            return new BurgerMeal(this);        }    }    @Override    public String toString() {        return (            "BurgerMeal [breadType=" +            breadType +            ", patty=" +            patty +            ", hasCheese=" +            hasCheese +            ", side=" +            side +            ", drinks=" +            drinks +            "]"        );    }}public class Main {    public static void main(String[] args) {        // Look how clean the client code is now! No more "null" values.        BurgerMeal basic = new BurgerMeal.BurgerMealWithBuilder(            "wheat",            "non-veg"        ).build();        BurgerMeal fullMeal = new BurgerMeal.BurgerMealWithBuilder(            "wheat",            "non-veg"        )            .withCheese(true)            .withSide("fries")            .withDrinks(List.of("coke"))            .build();        System.out.println(basic);        System.out.println(fullMeal);    }}

Done with this topic?

How well do you know it?

PreviousAbstract Factory Pattern Guide