Builder Pattern in Java: Simple Guide with Real Example (2026)

Builder Pattern in Java: Simple Guide with Real Example

Builder Pattern is the pattern that fixes one of the ugliest problems in Java — constructors with too many parameters. You have probably seen code like new Pizza("large", true, false, true, false, null, "extra cheese") and had no idea what all those booleans meant. Builder Pattern solves this elegantly. In this guide, I will explain it in plain words with a real burger ordering example.

The problem: too many constructor parameters

Imagine you are writing a class for a pizza. It has many optional toppings — cheese, olives, mushrooms, paneer, jalapeños, extra cheese, and so on. Some customers want all of them, some want none.

How do you write the constructor? You have three bad options.

Option 1: One big constructor

public Pizza(String size, boolean cheese, boolean olives, boolean mushrooms,
             boolean paneer, boolean jalapenos, boolean extraCheese, String note) {
    // ...
}

Now when someone calls this:

new Pizza("large", true, false, true, false, false, true, "no onion");

Quick question: which boolean is paneer? Which is extraCheese? You have to count positions to figure it out. And if you get one wrong, the compiler will not catch it. This is called the telescoping constructor problem.

Option 2: Many overloaded constructors

public Pizza(String size) { ... }
public Pizza(String size, boolean cheese) { ... }
public Pizza(String size, boolean cheese, boolean olives) { ... }
public Pizza(String size, boolean cheese, boolean olives, boolean mushrooms) { ... }
// ... 7 more constructors

Now you have ten constructors. Nobody can remember which one takes what. Adding a new topping means adding more constructors. It becomes a mess.

Option 3: Setters

Pizza pizza = new Pizza("large");
pizza.setCheese(true);
pizza.setOlives(false);
pizza.setMushrooms(true);
// ... 5 more setter calls

This is better, but the object is now mutable after creation. Somebody can accidentally change it halfway through. And it takes many lines just to build one object.

None of these options are good. That is exactly the problem Builder Pattern was invented to solve.

What is Builder Pattern, in simple words?

Builder Pattern is a way to build an object step by step, using small method calls that each describe one part of the object.

Instead of this:

new Pizza("large", true, false, true, false, false, true, "no onion");

You write this:

Pizza pizza = new Pizza.Builder("large")
    .withCheese()
    .withMushrooms()
    .withExtraCheese()
    .withNote("no onion")
    .build();

Read that second version out loud. You can actually understand what it does. You do not need to look up parameter orders. You cannot accidentally mix up positions. The code reads like a sentence.

That is the whole point of Builder Pattern: make object creation readable and safe.

Now here is the interesting part. The final Pizza object is usually immutable. You cannot change it after it is built. That is a bonus — immutable objects are safer, thread-friendly, and easier to reason about.

A real-life example: ordering a burger

Think about how you order a burger at a fast-food counter.

You do not say to the cashier: "One burger, with cheese, no lettuce, extra patty, yes pickles, no tomato, small size, medium spice, and no onion, and I want it in a box."

Instead, you say: "One chicken burger." The cashier says: "Any cheese?" You say yes. "Pickles?" You say no. "Extra patty?" Yes. And so on, one question at a time.

At the end, the burger is made. You did not need to remember all options upfront. You built your order step by step.

That is exactly how Builder Pattern works in code. Each method call is like answering one question. And when you are done, you call build() — which is like saying "That is my final order."

Also notice: you never change your burger after it is made. You cannot say "actually, add cheese now." Once it is wrapped, it is final. That is immutability.

Building it in Java step by step

Let us build a Burger class with Builder Pattern. We will keep it clean and readable.

Step 1: Create the main class with private fields

public class Burger {
    // All fields are final — the object is immutable
    private final String patty;
    private final String size;
    private final boolean cheese;
    private final boolean lettuce;
    private final boolean tomato;
    private final boolean pickles;
    private final boolean extraPatty;
    private final String note;

    // Private constructor — only the Builder can call it
    private Burger(Builder builder) {
        this.patty = builder.patty;
        this.size = builder.size;
        this.cheese = builder.cheese;
        this.lettuce = builder.lettuce;
        this.tomato = builder.tomato;
        this.pickles = builder.pickles;
        this.extraPatty = builder.extraPatty;
        this.note = builder.note;
    }

    // Getters (no setters — immutable)
    public String getPatty() { return patty; }
    public String getSize() { return size; }
    public boolean hasCheese() { return cheese; }
    public boolean hasLettuce() { return lettuce; }
    public boolean hasTomato() { return tomato; }
    public boolean hasPickles() { return pickles; }
    public boolean hasExtraPatty() { return extraPatty; }
    public String getNote() { return note; }

    @Override
    public String toString() {
        return "Burger {" +
               "patty='" + patty + '\'' +
               ", size='" + size + '\'' +
               ", cheese=" + cheese +
               ", lettuce=" + lettuce +
               ", tomato=" + tomato +
               ", pickles=" + pickles +
               ", extraPatty=" + extraPatty +
               ", note='" + note + '\'' +
               '}';
    }
}

Notice a few things:

  • All fields are final — nobody can change them after creation
  • The constructor is private — only the Builder can call it
  • There are no setters — once built, the object is frozen

Step 2: Create the Builder as a static inner class

public class Burger {
    private final String patty;
    private final String size;
    private final boolean cheese;
    private final boolean lettuce;
    private final boolean tomato;
    private final boolean pickles;
    private final boolean extraPatty;
    private final String note;

    private Burger(Builder builder) {
        this.patty = builder.patty;
        this.size = builder.size;
        this.cheese = builder.cheese;
        this.lettuce = builder.lettuce;
        this.tomato = builder.tomato;
        this.pickles = builder.pickles;
        this.extraPatty = builder.extraPatty;
        this.note = builder.note;
    }

    // Static nested Builder class
    public static class Builder {
        private final String patty;
        private final String size;
        private boolean cheese;
        private boolean lettuce;
        private boolean tomato;
        private boolean pickles;
        private boolean extraPatty;
        private String note;

        public Builder(String patty, String size) {
            this.patty = patty;
            this.size = size;
        }

        public Builder withCheese() {
            this.cheese = true;
            return this;
        }

        public Builder withLettuce() {
            this.lettuce = true;
            return this;
        }

        public Builder withTomato() {
            this.tomato = true;
            return this;
        }

        public Builder withPickles() {
            this.pickles = true;
            return this;
        }

        public Builder withExtraPatty() {
            this.extraPatty = true;
            return this;
        }

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

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

Key points about the Builder:

  • It mirrors the fields of the main class
  • Each method sets one field and returns this — so you can chain calls
  • The build() method creates the final object
  • Required fields (patty, size) are set in the constructor
  • Optional fields are set through with...() methods

Step 3: Use it in your code

public class Main {
    public static void main(String[] args) {
        Burger myBurger = new Burger.Builder("chicken", "large")
            .withCheese()
            .withLettuce()
            .withPickles()
            .withExtraPatty()
            .withNote("No onion please")
            .build();

        System.out.println(myBurger);

        // A different burger
        Burger simpleBurger = new Burger.Builder("veg", "small")
            .withCheese()
            .build();

        System.out.println(simpleBurger);
    }
}

Output

Burger {patty='chicken', size='large', cheese=true, lettuce=true, tomato=false, pickles=true, extraPatty=true, note='No onion please'}
Burger {patty='veg', size='small', cheese=true, lettuce=false, tomato=false, pickles=false, extraPatty=false, note='null'}

Look at that. Two completely different burgers, built with clean, readable code. No confusion about parameter order. No nulls passed for unused options. Just clear, chainable method calls.

That is Builder Pattern in about 60 lines of code.

Builder vs Factory vs Constructor

You might be wondering — when do I use which? Let me clear this up.

Approach Best for Example
Constructor Simple objects with 1-3 fields new Point(10, 20)
Factory Pattern When the exact class is decided at runtime PaymentFactory.create("upi")
Builder Pattern Complex objects with many optional fields new Pizza.Builder("large").withCheese().build()

The simple rule

  • 1-3 parameters and all required? Use a constructor.
  • Need to pick a class at runtime? Use a factory.
  • Many optional parameters and want readable code? Use a builder.

Builder vs Factory — the key difference

Factory is about what to create. Builder is about how to create it.

You might even combine them. A factory could return a builder. A builder could internally use a factory to create parts. They are not competing patterns — they solve different problems.

When to use it (and when not to)

Good times to use Builder Pattern

  • Your class has more than 4-5 fields, and many of them are optional
  • You find yourself writing multiple overloaded constructors
  • You want the final object to be immutable
  • You want object construction to read like a sentence
  • You are building configuration objects, HTTP requests, or database queries
  • You need to validate the object before creating it

Bad times to use Builder Pattern

  • Your class only has 2-3 simple fields — just use a constructor
  • The object is simple and immutable — no need for a builder
  • You are building a tiny internal class where a constructor is fine
  • You do not actually need immutability — setters might be simpler

The rule I use: if your constructor has more than 3 parameters, or if you ever see two boolean parameters next to each other, consider Builder.

Booleans next to each other in a constructor are the biggest red flag. They are almost impossible to read correctly.

Where you will see it in real Java code

Builder Pattern is everywhere in modern Java. You have probably used it today without realizing.

  • StringBuilder — the classic example. new StringBuilder().append("a").append("b").toString() is Builder Pattern.
  • Lombok's @Builder — an annotation that generates the whole builder for you. Used in millions of projects.
  • HttpRequest.newBuilder() — from Java's modern HTTP client. Every request is built this way.
  • JPA CriteriaBuilder — used to build database queries dynamically.
  • Protocol Buffers — every generated message class has a builder.
  • OkHttp Request.Builder — for building HTTP requests in Android and backend apps.
  • Retrofit, gRPC, Jackson — many modern libraries use builders for configuration.

Notice the pattern? Every one of these involves building something with many optional parts. That is where Builder shines.

Common mistakes to avoid

1. Making the object mutable anyway

The whole point of Builder is often immutability. If you add setters to the final object, you have lost a key benefit.

Fix: Keep fields final. Remove setters. If someone needs to change a value, they build a new object.

2. Forgetting to validate in build()

If a required field is missing, build() should throw an error. Otherwise, you end up with broken objects.

public Burger build() {
    if (patty == null || patty.isEmpty()) {
        throw new IllegalStateException("Patty is required");
    }
    return new Burger(this);
}

Fix: Always validate in build(). This is the last chance to catch bad data.

3. Reusing the builder

Builders are not meant to be reused. Once you call build(), create a new builder for the next object. Reusing can lead to weird bugs where one object has fields from another.

Fix: Treat the builder as single-use. Create a fresh one each time.

4. Making the builder public

If you need to enforce specific building rules (like "you cannot have paneer and chicken together"), do not expose the builder publicly. Provide a wrapper method instead.

Fix: Keep sensitive logic in a factory method that uses the builder internally.

5. Overusing it

Not every class needs a builder. A simple User class with 2 fields does not need one. A Point(x, y) does not need one. When a constructor is fine, use a constructor.

Fix: Count your fields. If it is less than 4 and none are optional, skip the builder.

Common questions

What is Builder Pattern in simple words?

Builder Pattern is a design pattern that lets you create complex objects step by step. Instead of passing 10 parameters to a constructor, you call small methods one at a time, like withCheese().withOlives().build(). It makes your code much more readable.

Why do we need Builder Pattern in Java?

We need it when an object has many optional fields. Without Builder, you either write many overloaded constructors (called telescoping constructors) or pass nulls for unused parameters. Both are ugly and error-prone. Builder solves this.

What is the difference between Builder and Factory Pattern?

Factory Pattern decides which class to create. Builder Pattern controls how a complex object is created step by step. Factory is about the type, Builder is about the construction process.

Is Builder Pattern thread-safe in Java?

By default, no. Each Builder instance is mutable, so using the same builder across threads causes problems. In practice, builders are almost always created and used in a single thread, so this is rarely an issue. The final object that build() returns is typically immutable.

What is telescoping constructor problem?

Telescoping constructor is when you write many overloaded constructors, each adding one more parameter. For example, a Pizza class might have constructors with 1, 2, 3, 4... up to 10 parameters. It becomes unreadable and unmaintainable. Builder Pattern solves this.

Where is Builder Pattern used in real Java code?

Builder Pattern is used in StringBuilder, Lombok's @Builder annotation, HTTP client builders, JPA CriteriaBuilder, Protocol Buffers, and many modern Java libraries. Any time you see chained methods ending with .build(), that is Builder Pattern at work.

Where to go next

You now understand Builder Pattern better than most working developers. You know the problem it solves, how to write it, when to use it, and the mistakes to avoid.

If you remember one thing, remember this: Builder Pattern replaces messy constructors with readable code. It is the pattern that turns 10-parameter monstrosities into sentences.

But do not overuse it. If your class has 2 or 3 simple fields, a constructor is fine. Builder is for complexity that actually exists.

If this helped, bookmark it. But the real learning happens when you type the code. Take a class you wrote recently with too many constructor parameters, and refactor it with a builder. That exercise alone will teach you more than any tutorial.

No comments: