← Back to Advanced Java Course | Chapter 6: Design Patterns | Lesson 5 of 14

Java Builder Pattern

The builder pattern makes a complicated object step by step, like assembling a burger with only the toppings you want. It keeps the code readable when there are many options.
Syntax
java
Product product = new Product.Builder()
    .setPart1(value1)
    .setPart2(value2)
    .build();

Why Use Builder Pattern?

The Builder pattern constructs complex objects step by step instead of all at once, and it directly solves the 'telescoping constructor' problem, where a class ends up with a long chain of overloaded constructors just to support every combination of optional parameters.

Example: Why Use Builder Pattern?

java
public class Main {
	static class Pizza {
		String size; boolean cheese;
		Pizza(String size, boolean cheese) { this.size = size; this.cheese = cheese; }
	}
	public static void main(String[] args) {
		// Avoids a telescoping constructor for every combination of optional toppings
		Pizza p = new Pizza("large", true);
		System.out.println(p.size + " " + p.cheese);
	}
}

Inner Static Builder Class

A common Java implementation nests a static Builder class inside the class it constructs. The Builder accumulates configuration values through chained setter-style calls, and a final build() method uses those accumulated values to construct and return the actual outer-class instance.

Example: Inner Static Builder Class

java
// Define the class Main
public class Main {
	// Define the class Pizza
	static class Pizza {
		String size; boolean cheese;
		private Pizza(Builder b) { this.size = b.size; this.cheese = b.cheese; }
		static class Builder {
			String size; boolean cheese;
			Builder size(String size) { this.size = size; return this; }
			Builder cheese(boolean cheese) { this.cheese = cheese; return this; }
			Pizza build() { return new Pizza(this); }
		}
	}
	// Program entry point: the JVM starts running here
	public static void main(String[] args) {
		Pizza p = new Pizza.Builder().size("medium").cheese(true).build();
		// Print a line to the console
		System.out.println(p.size + " " + p.cheese);
	}
}

Handling Mandatory and Optional Fields

To enforce that certain fields are mandatory, require them as parameters directly in the Builder's own constructor so an object can't be built without them. Optional fields, in contrast, can be set afterward through separate chainable methods that the caller is free to skip.

Example: Handling Mandatory and Optional Fields

java
public class Main {
	static class Order {
		final String item; String note;
		private Order(Builder b) { this.item = b.item; this.note = b.note; }
		static class Builder {
			final String item; // mandatory -- required by the Builder's own constructor
			String note; // optional
			Builder(String item) { this.item = item; }
			Builder note(String note) { this.note = note; return this; }
			Order build() { return new Order(this); }
		}
	}
	public static void main(String[] args) {
		Order o = new Order.Builder("Book").note("Gift wrap").build();
		System.out.println(o.item + " " + o.note);
	}
}

Thread Safety and Immutability

By omitting setter methods on the outer, built class entirely, the objects the Builder produces stay immutable and inherently thread-safe once constructed. You can also centralize validation of cross-field constraints inside the build() method itself, catching invalid combinations before the object ever comes into existence.

Example: Thread Safety and Immutability

java
public class Main {
	static final class Point {
		final int x, y; // no setters -- immutable once built
		private Point(Builder b) {
			if (b.x < 0 || b.y < 0) throw new IllegalArgumentException("negative coordinates");
			this.x = b.x; this.y = b.y;
		}
		static class Builder {
			int x, y;
			Builder x(int x) { this.x = x; return this; }
			Builder y(int y) { this.y = y; return this; }
			Point build() { return new Point(this); }
		}
	}
	public static void main(String[] args) {
		Point p = new Point.Builder().x(3).y(4).build();
		System.out.println(p.x + "," + p.y);
	}
}

Real-World Scenarios

Builders are heavily used in real-world code to configure HTTP requests with many optional headers and parameters, to construct complex SQL queries piece by piece, and to assemble application configuration objects that have dozens of independently optional settings.

Example: Real-World Scenarios

java
// Define the class Main
public class Main {
	// Define the class HttpRequestBuilder
	static class HttpRequestBuilder {
		String url; String method = "GET"; String header;
		HttpRequestBuilder url(String url) { this.url = url; return this; }
		HttpRequestBuilder method(String method) { this.method = method; return this; }
		HttpRequestBuilder header(String header) { this.header = header; return this; }
		String build() { return method + " " + url + " [" + header + "]"; }
	}
	// Program entry point: the JVM starts running here
	public static void main(String[] args) {
		// Create a new HttpRequestBuilder object and store it in request
		String request = new HttpRequestBuilder().url("/api/users").method("POST").header("Auth").build();
		// Print a line to the console
		System.out.println(request);
	}
}
Related Topics
{# common_mistakes/chapter_summary/browser_support: on Hindi pages the view already swaps in the hi_ translation fields (or blanks these out if untranslated), so this renders correctly for both languages without a lang_code check here. #}
Common Mistakes
  1. Writing builder methods that return void instead of this, which breaks the chained calls.
  2. Not validating mandatory fields in build(), so an incomplete object is created.
  3. Adding setters to the built class, which removes the immutability the Builder was supposed to give.

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.