Skip to content
  • Design Patterns: Singleton, Builder & Factory · Java

Design Patterns: Singleton, Builder & Factory

LearnViaNotes @learnvianotes· 5 min read

Creational patterns control how objects get made: exactly one (Singleton), step by step (Builder), or without the caller naming the concrete class (Factory).

Singleton

VariantLazy?Thread-safe?Notes
Eager static final fieldNo — created at class initYes (class init is thread-safe)Simplest; fine when creation is cheap
synchronized getInstance()YesYesLock on every call — slow
Double-checked lockingYesYes only with volatileVerbose; easy to get wrong
Holder idiom (static nested class)YesYesJVM class-loading guarantees; no locks
enum with one constantAt enum initYesRecommended (Effective Java): also safe against reflection and serialization
Java
public class Main {
    // 1. Eager
    static final class Eager {
        private static final Eager INSTANCE = new Eager();
        private Eager() {}
        static Eager getInstance() { return INSTANCE; }
    }

    // 2. Double-checked locking — volatile prevents seeing a half-constructed object
    static final class Dcl {
        private static volatile Dcl instance;
        private Dcl() {}
        static Dcl getInstance() {
            Dcl local = instance;
            if (local == null) {                       // 1st check, no lock
                synchronized (Dcl.class) {
                    local = instance;
                    if (local == null) {               // 2nd check, with lock
                        instance = local = new Dcl();
                    }
                }
            }
            return local;
        }
    }

    // 3. Holder idiom — Holder is initialised on first getInstance() call
    static final class Lazy {
        private Lazy() { System.out.println("Lazy created"); }
        private static final class Holder { static final Lazy INSTANCE = new Lazy(); }
        static Lazy getInstance() { return Holder.INSTANCE; }
    }

    // 4. Enum
    enum Config {
        INSTANCE;
        private int timeout = 30;
        int timeout() { return timeout; }
    }

    public static void main(String[] args) {
        System.out.println(Eager.getInstance() == Eager.getInstance());
        System.out.println(Dcl.getInstance() == Dcl.getInstance());
        System.out.println("before first use");
        System.out.println(Lazy.getInstance() == Lazy.getInstance());
        System.out.println(Config.INSTANCE.timeout());
    }
}
Output
true
true
before first use
Lazy created
true
30

Breaking a singleton — and the enum defence

Java
import java.lang.reflect.Constructor;

public class Main {
    static final class Classic {
        static final Classic INSTANCE = new Classic();
        private Classic() {}
    }

    enum Single { INSTANCE }

    public static void main(String[] args) throws Exception {
        Constructor<Classic> c = Classic.class.getDeclaredConstructor();
        c.setAccessible(true);                       // bypass `private`
        Classic second = c.newInstance();
        System.out.println("classic still single? " + (second == Classic.INSTANCE));

        Constructor<Single> e = Single.class.getDeclaredConstructor(String.class, int.class);
        e.setAccessible(true);
        try {
            e.newInstance("INSTANCE", 0);
        } catch (IllegalArgumentException ex) {
            System.out.println(ex.getMessage());
        }
    }
}
Output
classic still single? false
Cannot reflectively create enum objects
Java
import java.io.*;

public class Main {
    static final class Settings implements Serializable {
        private static final long serialVersionUID = 1L;
        static final Settings INSTANCE = new Settings();
        private Settings() {}

        private Object readResolve() {       // deserialization returns this instead
            return INSTANCE;
        }
    }

    public static void main(String[] args) throws Exception {
        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
        try (ObjectOutputStream out = new ObjectOutputStream(bytes)) {
            out.writeObject(Settings.INSTANCE);
        }
        try (ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes.toByteArray()))) {
            Object copy = in.readObject();
            System.out.println(copy == Settings.INSTANCE);   // false without readResolve
        }
    }
}
Output
true

Builder

For classes with many parameters, especially optional ones. Replaces telescoping constructors and avoids the half-built objects of the JavaBeans setter style.

Java
import java.util.ArrayList;
import java.util.List;

public class Main {
    static final class Pizza {
        private final String size;              // required
        private final boolean extraCheese;      // optional
        private final List<String> toppings;    // optional

        private Pizza(Builder b) {
            this.size = b.size;
            this.extraCheese = b.extraCheese;
            this.toppings = List.copyOf(b.toppings);
        }

        static Builder builder(String size) { return new Builder(size); }

        @Override
        public String toString() {
            return size + " pizza, cheese=" + extraCheese + ", toppings=" + toppings;
        }

        static final class Builder {
            private final String size;
            private boolean extraCheese;
            private final List<String> toppings = new ArrayList<>();

            private Builder(String size) { this.size = size; }

            Builder extraCheese() { this.extraCheese = true; return this; }
            Builder topping(String t) { toppings.add(t); return this; }

            Pizza build() {
                if (toppings.size() > 5) throw new IllegalStateException("too many toppings");
                return new Pizza(this);
            }
        }
    }

    public static void main(String[] args) {
        Pizza p = Pizza.builder("large")
                .topping("olives")
                .topping("basil")
                .extraCheese()
                .build();
        System.out.println(p);
    }
}
Output
large pizza, cheese=true, toppings=[olives, basil]

StringBuilder · HttpRequest.newBuilder() · Stream.builder() · Locale.Builder · Lombok @Builder

Factories

Java
public class Main {
    sealed interface Shape permits Circle, Square {
        double area();

        static Shape of(String kind, double size) {        // simple / static factory
            return switch (kind) {
                case "circle" -> new Circle(size);
                case "square" -> new Square(size);
                default -> throw new IllegalArgumentException("unknown shape: " + kind);
            };
        }
    }

    record Circle(double r) implements Shape {
        public double area() { return Math.PI * r * r; }
    }

    record Square(double side) implements Shape {
        public double area() { return side * side; }
    }

    public static void main(String[] args) {
        Shape s = Shape.of("square", 3);
        System.out.println(s + " area=" + s.area());
        System.out.printf("%.2f%n", Shape.of("circle", 1).area());
        System.out.println(Integer.valueOf(100) == Integer.valueOf(100));   // valueOf caches
    }
}
Output
Square[side=3.0] area=9.0
3.14
true

Which singleton implementation would you use and why?

Why must the instance field be volatile in double-checked locking?

When would you choose a builder over constructors?

Free for students and teachers

Write notes like this one

Cards for every idea, quizzes and flashcards, read aloud — on the web and your phone.

Something wrong with this note? Open it to report