Design patterns: Singleton, Factory and Builder
Singleton: exactly one instance. The safest Java versions are an enum or a lazily initialised holder class. In Spring, every bean is a singleton by default, so you rarely write one by hand.
Factory: a method decides which class to create, so callers don't use new and don't know the concrete type. List.of(), Executors.newFixedThreadPool() and Path.of() are factories.
Builder: build a complex object step by step with readable, named methods, then call build() to get an immutable result. It helps when a constructor would need many parameters, some optional. HttpRequest.newBuilder() and Lombok's @Builder are builders.
Use each for a reason: a factory hides a choice, a builder tames many parameters, and a singleton shares one expensive resource.
Example
// Singleton via enum: thread-safe and serialization-safe
public enum AppClock {
INSTANCE;
private final Clock clock = Clock.systemUTC();
public Instant now() { return clock.instant(); }
}
// Factory: choose the implementation from the input
public interface Notifier { void send(String to, String message); }
public final class Notifiers {
private Notifiers() {}
public static Notifier forChannel(String channel) {
return switch (channel) {
case "email" -> (to, msg) -> System.out.println("Email to " + to + ": " + msg);
case "sms" -> (to, msg) -> System.out.println("SMS to " + to + ": " + msg);
default -> throw new IllegalArgumentException("Unknown channel " + channel);
};
}
}
// Builder: many optional fields, immutable result
public record Email(String to, String subject, String body, List<String> cc) {
public static Builder to(String to) { return new Builder(to); }
public static final class Builder {
private final String to;
private String subject = "";
private String body = "";
private final List<String> cc = new ArrayList<>();
private Builder(String to) { this.to = to; }
public Builder subject(String s) { subject = s; return this; }
public Builder body(String b) { body = b; return this; }
public Builder cc(String c) { cc.add(c); return this; }
public Email build() { return new Email(to, subject, body, List.copyOf(cc)); }
}
}
Email mail = Email.to("asha@example.com").subject("Welcome").body("Hi Asha").build();
Notifiers.forChannel("sms").send("+919876543210", "Your OTP is 4821");Common mistake
Writing a lazy singleton with a plain null check. Two threads can both see null and create two instances.
Under the hood
Hand-written singletons with static state make testing hard, because every test shares the same instance; dependency injection gives the same single-instance benefit with swappable implementations. Double-checked locking is only correct with a volatile field; the enum and holder idioms avoid the subtlety entirely.
Check yourself
Which JDK method is an example of the factory pattern?
How this connects
Know these first
Where this leads
You've reached the end of this thread. Try a learning path for what's next.
Part of Crack the Java interview.
Was this lesson helpful?
Finished reading? Mark it complete to track your progress.