Skip to content

Design Pattern: Builder (Creational)

Objects with many optional parts

Builds on Constructors and Fluent API and annotations

Stage 3core1 min read

The "Anti-Pattern"

The current code uses a Telescoping Constructor: one constructor that takes every part. As BurgerMeal gains cheese, sauce, or isTakeout, the constructor gets longer and longer, and the code breaks easily.

1. Readability & Parameter Confusion

  • Issue: new BurgerMeal("wheat", "non-veg", "none", null) is hard to read. You cannot tell what each value means.
  • Risk: Many parameters are String, so it is easy to swap "wheat" and "non-veg" by mistake. The compiler will not catch this error.

2. The "Forced Argument" Tax

  • Issue: The client must give a value for every parameter, even for parts they do not want.
  • Code Smell (a sign of weak design): Passing null or "none" clutters the main method. It also risks a NullPointerException (a crash when code uses a null value).

3. Maintenance Rigidity

  • Inflexibility: To allow a burger with just bread, you would need a new overloaded constructor (same name, different parameters). This leads to "Constructor Bloat."
  • Breaking Changes: Adding one new field to the constructor breaks every existing instantiation of BurgerMeal in the entire project.

4. Technical Bug (Type Mismatch)

  • Error (if you make this mistake): The constructor BurgerMeal(String, String, String, String) is undefined.
  • Reason: The 4th argument must be a List<String>. Passing "null" (text in quotes) instead of null (no object) gives a type mismatch. The repo's BurgerMealWithoutBuilder.java passes real null, so it compiles, but the burger prints Drinks=null.

In this topic

Before code1 file

Before/BurgerMealWithoutBuilder.java67 linesGitHub
package DesignPatterns.CreationalPatterns.BuilderPattern.Before;import java.util.List;/** * LEGACY APPROACH: Using a "Telescoping Constructor" * This file demonstrates the problems that arise before implementing the Builder Pattern. */class BurgerMeal {    private String bread;    private String patty;    private String sides;    private List<String> drinks;    // PROBLEM 1: Rigidity    // If we add 'cheese' or 'sauce', every single call to this constructor across    // the entire project will break and must be updated.    public BurgerMeal(        String bread,        String patty,        String sides,        List<String> drinks    ) {        this.bread = bread;        this.patty = patty;        this.sides = sides;        this.drinks = drinks;    }    @Override    public String toString() {        return (            "Burger [Bread=" +            bread +            ", Patty=" +            patty +            ", Sides=" +            sides +            ", Drinks=" +            drinks +            "]"        );    }}public class BurgerMealWithoutBuilder {    public static void main(String[] args) {        // PROBLEM 2: Readability        // Looking at the line below, it's hard to tell which String is 'bread' and which is 'sides'        // without looking at the class definition.        // PROBLEM 3: Forced Arguments        // Even if I don't want sides or drinks, I am forced to pass "none" or null.        BurgerMeal burgerMeal = new BurgerMeal(            "wheat",            "non-veg",            "none",            null        );        System.out.println(burgerMeal);    }}

Done with this topic?

How well do you know it?

PreviousAbstract Factory Pattern Guide