# The Builder Pattern in Java: A Tasty Example

The Builder pattern is a creational design pattern used to **construct complex objects step-by-step**. It’s useful when a class has many optional parameters or an object construction needs to be made more readable and flexible.

In this post, we’ll explore the Builder pattern through a fun and relatable example: building a custom sandwich.

---

## 1\. What Is the Builder Pattern and When to Use It?

The Builder Pattern lets you **separate object construction from its representation**. It’s helpful when:

* You have a class with many parameters
    
* Some parameters are optional
    
* You want to make object creation readable and safe
    
* You want to avoid telescoping constructors (constructors with many overloads)
    

### 🍞 The Basic Recipe for the Builder Pattern

Before jumping into the full example, here’s a quick overview of how the Builder pattern is typically implemented:

**Prerequisite**: You have a class with many fields or complex configuration logic.

### 🔧 Steps:

1. In the enclosing class (the one with many fields), define a **static nested** `Builder` class.
    
2. In the `Builder` class, declare **the same fields** as in the enclosing class.
    
3. In the `Builder` constructor, set any **required fields**.
    
4. In the `Builder`, provide setter methods (e.g., `setX(value)`) for **optional fields**.
    
5. Ensure each setter **returns** `this` (the builder instance) so you can **chain calls fluently**.
    
6. In the `Builder`, implement a `build()` method that returns the final object.
    
7. In the enclosing class, create a **private constructor** that initializes its fields using the builder’s fields.
    

## 2\. A Sandwich Builder Example

Let’s say you want to create a customizable sandwich with required and optional ingredients.

```java
package design_patterns;

import java.util.List;

public class Sandwich {
    //required fields
    private final String breadType;
    private final String mainFilling;

    //optional fields
    private final String cheese;
    private final List<String> veggies;
    private final List<String> sauces;
    private final boolean toasted;

    private Sandwich(Builder builder) {
       this.breadType = builder.breadType;
       this.mainFilling = builder.mainFilling;
       this.cheese = builder.cheese;
       this.veggies = builder.veggies;
       this.sauces = builder.sauces;
       this.toasted = builder.toasted;
    }

    public static Builder builder(String breadType, String mainFilling) {
        return new Builder(breadType, mainFilling);
    }

    @Override
    public String toString() {
        return String.format(
            "Sandwich: \n- Bread: %s\n- Main Filling: %s\n- Cheese: %s\n- Veggies: %s\n- Sauces: %s\n- Toasted: %s",
            breadType,
            mainFilling,
            cheese != null ? cheese : "None",
            veggies != null ? veggies : "None",
            sauces != null ? sauces : "None",
            toasted ? "Yes" : "No"
        );
    }

    public static class Builder {
        private final String breadType;
        private final String mainFilling;
        private String cheese;
        private List<String> veggies;
        private List<String> sauces;
        private boolean toasted;

        public Builder(String breadType, String mainFilling) {
            this.breadType = breadType;
            this.mainFilling = mainFilling;
            this.veggies = List.of();
            this.sauces = List.of();
        }

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

        public Builder setVeggies(List<String> veggies) {
            this.veggies = veggies.stream().toList();
            return this;
        }

        public Builder setSauces(List<String> sauces) {
            this.sauces = sauces.stream().toList();
            return this;
        }

        public Builder setToasted(boolean toasted) {
            this.toasted = toasted;
            return this;
        }

        public Sandwich build() {
            return new Sandwich(this);
        }
    }
}
```

And here’s how you would use it:

```java
package design_patterns;

import java.util.List;

public class BuilderPatternExample {
    public static void main(String[] args) {
        Sandwich mySandwich = Sandwich.builder("Whole Wheat", "Chicken")
            .setCheese("Cheddar")
            .setToasted(true)
            .setSauces(List.of("Honey Mustard"))
            .setVeggies(List.of("Green Pepper", "Red Pepper", "Lettuce"))
            .build();
        System.out.println(mySandwich);
    }
}
```

---

## 3\. Essentials That Make the Builder Pattern Work

* **Private constructor:** The `Sandwich` constructor is private so clients are forced to use the builder.
    
* **Immutable fields:** All fields in `Sandwich` are `final`, making the object immutable after construction.
    
* **Required vs Optional Fields:** Required fields are set in the builder constructor, while optional fields have setters.
    
* **Static Builder class:** Declaring `Builder` as a static nested class avoids needing a reference to the outer `Sandwich`.
    
* **Fluent API:** Each setter returns `this` to support method chaining.
    
* **Extensibility:** Easy to add more optional ingredients without changing the constructor signatures.
    

---

## 4\. Pro-Level Improvements for a Great Builder API

* **Static** `builder()` method: Makes usage cleaner than `new Sandwich.Builder(...)`.
    
* **Defensive copying:** Lists are copied using `stream().toList()` to prevent external mutation.
    
* **Default values:** Optional fields like `veggies` and `sauces` default to empty lists.
    
* **Readable** `toString()`: Custom `toString()` improves debuggability and demo value.
    

## 5\. Real-World Examples of the Builder Pattern in Frameworks

The Builder pattern is not just a design exercise — it’s widely used in real frameworks and libraries to improve code readability and reduce constructor complexity.

### 🔧 Lombok (`@Builder`)

In Java, [Project Lombok](https://projectlombok.org/) offers a `@Builder` annotation that auto-generates builder code for you. It’s great for data classes where you want clean, fluent APIs without boilerplate.

```java
@Builder
public class User {
    private String name;
    private int age;
}
```

Usage:

```java
User user = User.builder()
    .name("Alice")
    .age(30)
    .build();
```

---

### 🌱 Spring Framework

While Spring is best known for dependency injection, many Spring projects (like Spring Security, Spring Boot, and Spring Data) use builder-style APIs to configure components:

```java
HttpSecurity http = ...;

http
  .authorizeRequests()
    .antMatchers("/admin/**").hasRole("ADMIN")
    .anyRequest().authenticated()
  .and()
  .formLogin()
    .loginPage("/login")
    .permitAll();
```

This fluent configuration style is inspired by the Builder pattern, providing clarity and flexibility when building complex object graphs.

---

### ☁️ AWS SDK (Java)

The AWS SDK uses the Builder pattern extensively to construct requests:

```java
PutObjectRequest request = PutObjectRequest.builder()
    .bucket("my-bucket")
    .key("my-file.txt")
    .contentType("text/plain")
    .build();
```

This avoids long constructors and makes API usage expressive and maintainable.

---

## 6\. Trade-Offs of the Builder Pattern

While the Builder pattern improves clarity and flexibility, it comes with a few trade-offs:

* **Extra boilerplate**: Writing a builder adds more code compared to using constructors or setters directly.
    
* **Adds minor indirection:** The object creation is split between the builder and the target class
    
* **Not ideal for simple objects**: For classes with only a few fields, the pattern may feel unnecessary.
    

Despite these, the Builder pattern remains a clean and scalable solution for constructing complex or immutable objects.

## Conclusion

The Builder pattern brings **fluency, clarity, and structure** to object creation. It’s widely adopted in frameworks and libraries to improve the readability and flexibility of configuration.

This is one of those patterns that feels sweet and mellow — the trade-offs are minor compared to the flexibility you gain, especially when working with complex objects.

I hope that the next time you enjoy a good sandwich, you’ll think of the Builder pattern. Enjoy! 🥪!
