Design Pattern

Builder Pattern

Clean Java-only production-ready implementation.


When an object has 8+ parameters (many optional), constructors become unreadable and error-prone. new House(4, 2, true, false, true, null, null, "tile") — is that 4 bedrooms or 4 bathrooms? Builder solves this with named, chainable setters.

// ─── EXAMPLE 1 ──────────────────────────────────────────────────────────────
// WHAT WE ARE IMPLEMENTING:
// A complex rich-text Document exporter assembling headers, bodies, and
// footers step-by-step.
//
// WHERE THE PATTERN FITS IN:
// DocumentBuilder acts as the Builder interface, defining individual assembly
// operations. Document is the Product being built.
// ────────────────────────────────────────────────────────────────────────────
// --- Product ---
class HotelBooking {
    private final String city;
    private final String checkIn;
    private final String checkOut;
    private final int guests;
    private final boolean breakfast;
    private final boolean parking;
    private final boolean poolAccess;
    private final String roomType;

    // Private constructor — only called by Builder
    private HotelBooking(String city, String checkIn, String checkOut, int guests,
                         boolean breakfast, boolean parking, boolean poolAccess, String roomType) {
        this.city = city; this.checkIn = checkIn; this.checkOut = checkOut;
        this.guests = guests; this.breakfast = breakfast;
        this.parking = parking; this.poolAccess = poolAccess; this.roomType = roomType;
    }

    public static class Builder {
        // Required params
        private final String city;
        private final String checkIn;
        private final String checkOut;

        // Optional params with defaults
        private int guests = 1;
        private boolean breakfast = false;
        private boolean parking = false;
        private boolean poolAccess = false;
        private String roomType = "STANDARD";

        public Builder(String city, String checkIn, String checkOut) {
            this.city = city;
            this.checkIn = checkIn;
            this.checkOut = checkOut;
        }

        public Builder guests(int guests) { this.guests = guests; return this; }
        public Builder breakfast(boolean v) { this.breakfast = v; return this; }
        public Builder parking(boolean v) { this.parking = v; return this; }
        public Builder poolAccess(boolean v) { this.poolAccess = v; return this; }
        public Builder roomType(String type) { this.roomType = type; return this; }

        public HotelBooking build() {
            if (checkIn.compareTo(checkOut) >= 0) throw new IllegalArgumentException("check-out must be after check-in");
            return new HotelBooking(city, checkIn, checkOut, guests, breakfast, parking, poolAccess, roomType);
        }
    }

    @Override
    public String toString() {
        return "HotelBooking{city=" + city + ", dates=" + checkIn + " to " + checkOut +
               ", guests=" + guests + ", room=" + roomType +
               ", breakfast=" + breakfast + ", parking=" + parking + "}";
    }
}

public class Main {
    public static void main(String[] args) {
        // Readable, intentional, no wrong-order bugs
        HotelBooking booking = new HotelBooking.Builder("Mumbai", "2025-06-01", "2025-06-05")
            .guests(2)
            .breakfast(true)
            .parking(true)
            .roomType("DELUXE")
            .build();

        System.out.println(booking);
    }
}