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

Java Builder Pattern

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
public class Main {
	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); }
		}
	}
	public static void main(String[] args) {
		Pizza p = new Pizza.Builder().size("medium").cheese(true).build();
		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
public class Main {
	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 + "]"; }
	}
	public static void main(String[] args) {
		String request = new HttpRequestBuilder().url("/api/users").method("POST").header("Auth").build();
		System.out.println(request);
	}
}

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.