Builder Design Pattern in Java Core Java Design Design Patterns java by devs5003 - September 14, 2025September 22, 20250 Last Updated on September 22nd, 2025 Builder Design Pattern in Java: Full Guide with Examples Constructing complex objects with numerous optional parameters often leads to a mess of telescoping constructors or error-prone setter methods. The Builder Pattern solves this by providing a clear, step-by-step process for creating objects, resulting in code that is more readable, maintainable, and thread-safe. This article explores the pattern through a Custom Pizza Order analogy, demonstrating both the classic approach and the modern, fluent style using modern Java 21 compatible codes. Table of Contents Toggle What is the Builder Pattern?Participants in General Builder Pattern: UMLWhy Use It? The Problem of Object ConstructionThe Simple Analogy: Custom Pizza OrderFlavour 1: The Classic Builder (Separate Classes)Implementation with Modern JavaOutputFlavour 2: The Fluent Builder (Static Inner Class)ImplementationOutputFlavour 3: The Lombok @Builder (Practical Alternative)OutputComparison of FlavoursReal-World Software Examples Of Builder Design Pattern in Java1. Java Native APIs2. Lombok Library (@Builder)3. Spring Framework (RestTemplateBuilder, BeanDefinitionBuilder)4. Jackson Library (ObjectMapper for JsonNode)5. Testing with Mockito (MockitoJUnitRunnerBuilder)6. Building HTTP Requests (HttpRequest from Java 11+)How to Handle Inheritance with Builders?Common Pitfalls and Best PracticesCommon PitfallsBest PracticesHow It Relates to Other PatternsFrequently Asked Questions (FAQs)ConclusionRelated What is the Builder Pattern? The Builder is a creational design pattern that lets you construct complex objects step by step. It separates the construction of a complex object from its representation, and allows the same construction process to create different representations. Participants in General Builder Pattern: UML This diagram shows the classic Gang of Four representation, which is important for understanding the pattern’s theoretical foundation. Here is the Explanation of the participants: Director: Knows the construction process (construct()). It uses the Builder interface to build the product step-by-step. (This is an optional component and is often omitted in simpler Java implementations). Builder Interface: Declares the steps required to build the product (buildPartA(), buildPartB()). ConcreteBuilder: Provides the implementation for the construction steps. It also holds and assembles the Product. Product: The complex object being built. The ConcreteBuilder returns it via getResult(). Why Use It? The Problem of Object Construction Imagine a Pizza class with 10 possible attributes, many of which are optional (olives, extra cheese, spicy sauce, etc.). Telescoping Constructors: Creating a constructor for every possible combination of parameters is unmaintainable. new Pizza(size, cheese, true, false, true, null, onions, peppers); // What does ‘true’ mean? Setters: Using a no-arg constructor and setters leads to an object that can be in an inconsistent state partway through its construction and is not immutable. The Builder Pattern solves this by: Providing clear, readable code for object creation. Enforcing the creation of immutable, thread-safe objects. Allowing the enforcement of business rules during the build process. The Simple Analogy: Custom Pizza Order You’re ordering a pizza over the phone. You don’t just say “I want a pizza”; you specify your choices step-by-step: Size: “I’d like a large pizza…” Crust: “…with a thin crust…” Sauce: “…and extra tomato sauce…” Toppings: “…add pepperoni, mushrooms, and extra cheese…” The pizza builder (the person taking your order) assembles your pizza based on your precise instructions. The same process can create a completely different pizza for the next customer. Flavour 1: The Classic Builder (Separate Classes) This approach uses a separate Builder class that is responsible for constructing the product. Implementation with Modern Java // 1. The Product (Immutable Record) public record Pizza(String size, String crust, String sauce, List<String> toppings, boolean extraCheese) { // The Classic Builder as a separate, static class public static final class ClassicBuilder { // Required parameters (could be made final and required in constructor) private String size; private String crust; // Optional parameters - initialized to defaults private String sauce = "Tomato"; private List<String> toppings = new ArrayList<>(); private boolean extraCheese = false; public ClassicBuilder(String size, String crust) { this.size = size; this.crust = crust; } public ClassicBuilder sauce(String sauce) { this.sauce = sauce; return this; } public ClassicBuilder topping(String topping) { this.toppings.add(topping); return this; } public ClassicBuilder extraCheese(boolean extraCheese) { this.extraCheese = extraCheese; return this; } public Pizza build() { // Validation can be done here if (size == null || crust == null) { throw new IllegalStateException("Size and crust are required"); } return new Pizza(size, crust, sauce, List.copyOf(toppings), extraCheese); } } } Usage: public class BuilderTest{   public static void main(String[] args){     Pizza myPizza = new Pizza.ClassicBuilder("Large", "Thin") .sauce("Garlic") .topping("Pepperoni") .topping("Mushrooms") .extraCheese(true) .build(); System.out.println(myPizza); } } Output Pizza[size=Large, crust=Thin, sauce=Garlic, toppings=[Pepperoni, Mushrooms], extraCheese=true] Flavour 2: The Fluent Builder (Static Inner Class) This is the most common and idiomatic Java implementation. It uses a static inner class that has access to the private constructor of the outer class, allowing for truly immutable objects. Implementation We’ll use a static inner class to guide the construction process, making it even more intuitive. // 1. The Product (Immutable Class) public final class Pizza { // All fields are final for immutability private final String size; private final String crust; private final String sauce; private final List<String> toppings; private final boolean extraCheese; // Private constructor only the Builder can use private Pizza(Builder builder) { this.size = builder.size; this.crust = builder.crust; this.sauce = builder.sauce; this.toppings = List.copyOf(builder.toppings); // Defensive copy for immutability this.extraCheese = builder.extraCheese; } // 2. The Builder (Static Inner Class) public static Builder builder(String size, String crust) { return new Builder(size, crust); } public static final class Builder { // Required parameters (final) private final String size; private final String crust; // Optional parameters - initialized to defaults private String sauce = "Tomato"; private final List<String> toppings = new ArrayList<>(); private boolean extraCheese = false; private Builder(String size, String crust) { this.size = size; this.crust = crust; } public Builder sauce(String sauce) { this.sauce = sauce; return this; } public Builder topping(String topping) { this.toppings.add(topping); return this; } public Builder extraCheese(boolean extraCheese) { this.extraCheese = extraCheese; return this; } public Pizza build() { // Validation if (size == null || crust == null) { throw new IllegalStateException("Size and crust are required"); } return new Pizza(this); } } // Getters... public String size() { return size; } public String crust() { return crust; } public String sauce() { return sauce; } public List<String> toppings() { return toppings; } public boolean extraCheese() { return extraCheese; } // toString()... for a meaningful output } Usage (Fluent and Readable): public class BuilderTest{   public static void main(String[] args){ Pizza myPizza = Pizza.builder("Large", "Thin") .sauce("Garlic") .topping("Pepperoni") .topping("Mushrooms") .extraCheese(true) .build(); // Returns the immutable Pizza object System.out.println(myPizza); } } Output Pizza[size=Large, crust=Thin, sauce=Garlic, toppings=[Pepperoni, Mushrooms], extraCheese=true] Flavour 3: The Lombok @Builder (Practical Alternative) For real-world productivity, Project Lombok’s @Builder annotation generates the above Fluent Builder automatically. import lombok.Builder; import lombok.Singular; import java.util.List; @Builder public record PizzaRecord( String size, String crust, @Builder.Default String sauce = "Tomato", // Default value @Singular List<String> toppings, // Lombok creates `topping()` method boolean extraCheese ) { // Lombok generates the entire builder automatically! // Validation can be added via custom method public static class PizzaRecordBuilder { public PizzaRecord build() { if (size == null || crust == null) { throw new IllegalStateException("Size and crust are required"); } return new PizzaRecord(size, crust, sauce, toppings, extraCheese); } } } Lombok Usage (Identical to Fluent Builder): public class BuilderTest{   public static void main(String[] args){ PizzaRecord myPizza = PizzaRecord.builder() .size("Large") .crust("Thin") .sauce("Garlic") .topping("Pepperoni") // Singular method .topping("Mushrooms") // Singular method .extraCheese(true) .build(); System.out.println(myPizza); } } Output Pizza[size=Large, crust=Thin, sauce=Garlic, toppings=[Pepperoni, Mushrooms], extraCheese=true] Comparison of Flavours Feature Classic Builder Fluent Builder (Inner Class) Lombok @Builder Immutability Yes Yes (Enforced) Yes Boilerplate High High None Readability Good Excellent Excellent Validation Custom Custom Custom Default Values Manual Manual Automatic (@Builder.Default) Learning Curve Low Medium Low (after setup) Real-World Software Examples Of Builder Design Pattern in Java The Builder pattern is common in Java development, especially in libraries and frameworks designed for clarity and configuration. Here are some concrete examples you’ve likely seen already in multiple places: 1. Java Native APIs StringBuilder & StringBuffer We are highly use the Builder pattern in StringBuilder & StringBuffer classes. They are used to construct a String step-by-step to avoid the performance cost of creating multiple immutable String objects. // A familiar example of fluid construction String message = new StringBuilder() .append("Hello, ") .append(userName) .append("! You have ") .append(count) .append(" new messages.") .toString(); // The 'build()' method is effectively 'toString()' 2. Lombok Library (@Builder) This is the most common real-world implementation. Lombok’s annotation generates a full-featured, fluent Builder for your classes automatically, drastically reducing boilerplate code. import lombok.Builder; import lombok.Singular; import java.util.List; @Builder public class Customer { private Long id; private String name; private String email; @Singular // Generates a `order` method and adds to a list private List<String> orders; } // Usage Customer customer = Customer.builder() .id(1L) .name("Jane Doe") .email("jane.doe@example.com") .order("Laptop") // Lombok-generated singular method .order("Mouse") // Lombok-generated singular method .build(); 3. Spring Framework (RestTemplateBuilder, BeanDefinitionBuilder) Spring uses the Builder pattern extensively to configure complex objects in a readable way. RestTemplateBuilder: Used to build a RestTemplate client with custom settings like interceptors, message converters, and URI templates. RestTemplate restTemplate = new RestTemplateBuilder() .rootUri("https://api.example.com") .additionalInterceptors(new LoggingInterceptor()) .basicAuthentication("user", "password") .build(); BeanDefinitionBuilder: Used internally by Spring to programmatically define beans for the application context. BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(MyService.class); builder.addPropertyValue("timeout", 5000); builder.setScope(BeanDefinition.SCOPE_SINGLETON); BeanDefinition beanDefinition = builder.getBeanDefinition(); 4. Jackson Library (ObjectMapper for JsonNode) Jackson provides a Builder-style API for constructing JSON trees programmatically. ObjectMapper mapper = new ObjectMapper(); ObjectNode userJson = mapper.createObjectNode() .put("name", "John Doe") .put("age", 30) .set("address", mapper.createObjectNode() .put("street", "123 Main St") .put("city", "Anytown") ); // userJson: {"name":"John Doe","age":30,"address":{"street":"123 Main St","city":"Anytown"}} 5. Testing with Mockito (MockitoJUnitRunnerBuilder) Testing frameworks use Builders for configuration. // Mockito's fluent API for stubbing when(mockList.get(anyInt())) .thenReturn("first") .thenReturn("second") .thenThrow(new RuntimeException()); // Building a argument captor ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class); 6. Building HTTP Requests (HttpRequest from Java 11+) The Java HTTP Client API uses the Builder pattern. import java.net.http.HttpRequest; import java.net.URI; HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://httpbin.org/post")) .header("Content-Type", "application/json") .header("Authorization", "Bearer mytoken") .POST(HttpRequest.BodyPublishers.ofString("{\"data\": \"value\"}")) .build(); How to Handle Inheritance with Builders? This is an advanced but common scenario. The solution is to use recursive generics, also known as the “self” type. // Base class public class Animal { private final String species; // Base Builder class public static abstract class Builder<T extends Builder<T>> { private String species; public T species(String species) { this.species = species; return self(); } protected abstract T self(); // Key to making it fluent for subclasses public Animal build() { return new Animal(this); } } protected Animal(Builder<?> builder) { this.species = builder.species; } } // Subclass public class Dog extends Animal { private final String breed; // Subclass Builder public static class Builder extends Animal.Builder<Builder> { private String breed; public Builder breed(String breed) { this.breed = breed; return this; } @Override protected Builder self() { return this; // Returns 'this' instance of Dog.Builder } public Dog build() { return new Dog(this); } } private Dog(Builder builder) { super(builder); this.breed = builder.breed; } } // Usage: Fluent method chaining works for both parent and child methods Dog dog = new Dog.Builder() .species("Canine") // Method from Animal.Builder .breed("Husky") // Method from Dog.Builder .build(); The above code demonstrates how to extend the Builder pattern to support inheritance through a technique called recursive generics. The key is in the abstract base Animal.Builder class, which is defined with a generic type parameter T that must extend itself (Builder<T>). This construct allows the base builder’s methods, like species(), to return the specific builder type of the subclass (e.g., Dog.Builder) instead of the base Animal.Builder, thus preserving the fluent API. The crucial self() method, which each concrete subclass must override to return its own this reference, enables this “covariant return type” behavior. When Dog.Builder extends Animal.Builder<Dog.Builder>, it effectively tells the parent that T is Dog.Builder, ensuring that methods called from the parent return the dog builder, allowing subsequent calls to subclass-specific methods like breed(). This creates a type-safe, fluent inheritance hierarchy where both parent and child builder methods can be chained together seamlessly. Common Pitfalls and Best Practices Although the Builder pattern is powerful, it’s not without its refinement. Here’s what to watch out for and how to use it effectively. Common Pitfalls Over-Engineering for Simple Objects: The biggest pitfall is using a Builder for classes with only a few, required parameters. A simple constructor is often cleaner and more straightforward. Pitfall: Creating a UserBuilder for a User class that only has firstName and lastName. Solution: Reserve the Builder for objects with many parameters, especially optional ones. Pitfall: Using the same builder instance to build multiple objects without resetting its state. Mutable Builder State: If the Builder itself is not built correctly, it can lead to shared state or concurrency issues, especially if a builder instance is reused. Pizza.Builder builder = Pizza.builder("Large", "Thin").topping("Pepperoni"); Pizza pizza1 = builder.build(); // Has pepperoni Pizza pizza2 = builder.build(); // Also has pepperoni (maybe unintended) Solution: Design the builder for single-use. After calling build(), the builder should be discarded. This is the expected behavior and is how Lombok’s builder works. Inconsistent State After build(): The builder’s build() method should always return a fully constructed and validated object. Failing to validate can leave the object in an inconsistent state. Pitfall: Not checking for required parameters or invalid combinations (e.g., size is null, or topping(“Seafood”) and sauce(“Chocolate”) are incompatible). Solution: Perform all validation in the build() method and throw a clear, informative exception (like IllegalStateException) if the state is invalid. Boilerplate Overhead: The manual Fluent Builder requires a lot of repetitive code, which is error-prone to write and maintain. Pitfall: Adding a new field to the product requires updating the builder class in multiple places (field declaration, setter method, build method assignment). Solution: Use Project Lombok’s @Builder annotation to generate the boilerplate automatically. Now-a-days this becomes the industry-standard solution for this pitfall. Best Practices Enforce Immutability: This is the pattern’s superpower. Make all fields in the product final and ensure the builder provides all necessary values to the product’s constructor. For collections, use immutable collections (e.g., List.copyOf()) to prevent modification after construction. Make the Builder a Static Inner Class: This is the most idiomatic Java approach. It allows the product’s constructor to be private, making the builder the only way to instantiate the object, which enforces the use of the pattern. Provide a Clear, Fluent API: Name setter methods intuitively (e.g., topping(), sauce()) and omit the set prefix. Always return this from these methods to enable method chaining. Consider Default Values: For optional parameters, initialize them to sensible defaults within the builder. This simplifies client code for the most common use cases. public Builder(String size, String crust) { this.size = size; this.crust = crust; this.sauce = "Tomato"; // Sensible default this.extraCheese = false; // Sensible default } Use a Static Factory Method: Instead of forcing the client to write new Pizza.Builder(), provide a static factory method like Pizza.builder(). This is more concise and discoverable. // Client code is cleaner with a static factory method Pizza.builder("Large", "Thin")... vs. new Pizza.Builder("Large", "Thin")... Lombok for the Win: In any non-trivial project, seriously consider using Project Lombok. The @Builder annotation eliminates all the boilerplate pitfalls while providing all the benefits. You can still add a custom build() method for validation. Java 16+ Records: Consider using records for the product if it’s a simple data carrier. How It Relates to Other Patterns The Builder pattern doesn’t exist in a vacuum. Understanding its relationship to other patterns clarifies its purpose and helps you choose the right tool for the job. vs. Factory Patterns (Factory Method & Abstract Factory): Purpose: Factories are about polymorphism (what object is created). The Builder is about construction (how a complex object is assembled). Focus: A Factory returns a new instance of a product on each call, often from a hierarchy. A Builder strictly assembles a single product through multiple steps and returns it only when build() is called. Analogy: An Abstract Factory is a showroom that gives you a matching chair, sofa, and table. A Builder is a custom workshop that builds you one specific, highly customized table step-by-step. vs. Prototype Pattern: Builder constructs a new object from scratch by specifying all its parts. Prototype creates a new object by copying an existing one (a prototype) and then possibly modifying it. It’s useful when object creation is more expensive than copying. vs. Composite Pattern: These patterns can be complementary. A Builder can be an excellent way to construct the complex tree structure of a Composite. The steps of the builder can nicely mirror the recursive structure of the composite. Example: A DocumentBuilder could have methods like addParagraph(), addTable(), and addList(), which internally assemble a Document composite object. vs. Telescoping Constructor Anti-Pattern: The Builder is the solution to this anti-pattern. It replaces a confusing multitude of constructors with a single, clear, fluent construction process. Can be used with other Patterns: Singleton: The Builder itself is usually stateless after build() and can be created new each time. However, a Director (an advanced builder concept) that defines common construction sequences could be a Singleton. Strategy: The construction algorithm within a builder’s build() method could be designed to use different strategies for validation or assembly. Frequently Asked Questions (FAQs) Q#1: Should I make the Builder a separate class or a static inner class? A: Almost always use a static inner class. This is the Java idiom for the Builder pattern. It has several key advantages: Strong Coupling: The Builder is intimately tied to the product it creates. Access to Private Constructor: The inner class can access the private constructor of the outer class, allowing you to make the product’s constructor private. This forces clients to use the Builder, which is often the desired behavior. Namespacing: The Builder is neatly namespaced within the product (e.g., Pizza.Builder), making it clear what it builds. Q#2: When should I not use the Builder pattern? A: Avoid the Builder pattern when: The object has very few parameters (use a constructor). The object has only mandatory parameters (use a constructor or a factory method with a descriptive name). The object is not complex to construct. The pattern adds boilerplate, so the complexity of construction should justify it. Immutability is not a goal. If you want a mutable JavaBean with setters, a Builder is overkill. Q#3: What’s the difference between Builder pattern and a Factory pattern? A: This is a fundamental distinction: Factory Patterns (Factory Method, Abstract Factory) are about what object is created. They are a mechanism for achieving polymorphism in object creation. Builder Pattern is about how a single, complex object is assembled. It’s a solution to the “telescoping constructor” problem and focuses on the step-by-step construction process, often with many optional parameters. A Factory is like a restaurant kitchen: you order a “Margherita Pizza” and you get a ready-made pizza. You don’t control the steps. A Builder is like a subway counter: you specify each ingredient step-by-step (“whole wheat bread, ham, cheese, lettuce, no tomatoes”) to assemble your custom sandwich. Q#4: Is the Builder pattern thread-safe? A: The Builder instance itself is typically not thread-safe. It’s designed to be used by a single thread to configure and then build an object. However, the final product, if built with immutable fields, is thread-safe. The pattern is a great way to construct immutable objects in a mutable way during the construction phase. Conclusion The Builder Pattern is an essential tool for creating complex objects in a clear, readable, and safe manner. The Fluent Builder with a Static Inner Class is the gold standard for Java, that enables the creation of immutable objects with a beautiful API. For maximum productivity, Lombok’s @Builder is the pragmatic choice in real-world applications. The Builder pattern is the specialist you call for constructing a single complex object. It focuses on the process of assembly, ensuring the final product is immutable and valid. It defers to Factory patterns for creating families of related objects and to the Prototype pattern when cloning is cheaper than building. By choosing the right flavour of the Builder pattern, you can eliminate constructor confusion, ensure object consistency, and make your code a pleasure to read and use. Ready to learn about another creational pattern? Let’s visit, the Abstract Factory Pattern in Java explained with examples. You may also check, the hub page of Java Design Patterns. Related