How to read this page
A pattern is a name for a shape you'll write anyway. Learning the name lets you spot the shape in your teammate's code, argue about it in a design review, and skip re-inventing it in your own. Nothing here is magic — most patterns are ten to thirty lines of Java that you'll find yourself typing whether or not you know the label.
Three groups, one page: creational (how objects come into being), structural (how they fit together), and behavioral (how they talk). Jump around:
Creational — how objects are born
These five answer the question: "who decides which concrete class to instantiate?" The answer is almost never new scattered across your code.
Singleton
One instance, one global handle. The "there can be only one" pattern.
How it works
The class hides its constructor and hands out one shared instance through a static accessor. Every caller in the process gets the same object. Use for things that genuinely have one — a config loader, a connection pool, a logger.
Java
public final class Config {
private static final Config INSTANCE = new Config();
private Config() {}
public static Config get() { return INSTANCE; }
public String db() { return "postgres://..."; }
}
// Config.get().db()
Use when
Truly one-per-process resource. Not when you just want a global variable — that's how singletons become the most-hated pattern.
Factory Method
A method whose job is to make the right subclass, so callers stop caring which one.
How it works
Instead of calling new EmailNotifier() everywhere, callers ask a factory method for a Notifier. The factory picks the concrete class from a config, a channel string, or the user's preference — one place to change when you add a new channel.
Java
interface Notifier { void send(String to, String msg); }
class EmailNotifier implements Notifier { public void send(String t,String m){ /*...*/ } }
class SmsNotifier implements Notifier { public void send(String t,String m){ /*...*/ } }
class NotifierFactory {
static Notifier of(String channel) {
return switch (channel) {
case "email" -> new EmailNotifier();
case "sms" -> new SmsNotifier();
default -> throw new IllegalArgumentException(channel);
};
}
}
Use when
You have one caller and many possible concrete types keyed by something at runtime (channel, region, plan tier).
new.Abstract Factory
A factory that makes families of related things — pick the family once, get compatible parts.
How it works
An abstract factory has multiple factory methods that all return matching objects. MacFactory makes MacButton and MacMenu; WinFactory makes WinButton and WinMenu. Pick the factory once, and everything you produce is from the same family.
Java
interface Button { void render(); }
interface Menu { void render(); }
interface UiFactory { Button button(); Menu menu(); }
class MacFactory implements UiFactory {
public Button button() { return new MacButton(); }
public Menu menu() { return new MacMenu(); }
}
class WinFactory implements UiFactory { /* similarly */ }
UiFactory ui = System.getProperty("os").equals("mac") ? new MacFactory() : new WinFactory();
ui.button().render();
ui.menu().render();
Use when
Several products must vary together and stay compatible (cross-platform UI, SQL vs NoSQL DAO bundles).
Builder
Configure an object step by step, then .build(). The fix for constructors with fifteen arguments.
How it works
Each setter returns the builder itself, so calls chain. The final build() validates and returns an immutable object. Beats a constructor with ten parameters where three are booleans no one can remember the order of.
Java
public class Book {
private final String title, author;
private final int pages;
private Book(Builder b) { this.title=b.title; this.author=b.author; this.pages=b.pages; }
public static class Builder {
private String title, author; private int pages;
public Builder title(String t) { this.title = t; return this; }
public Builder author(String a) { this.author = a; return this; }
public Builder pages(int p) { this.pages = p; return this; }
public Book build() {
if (title == null) throw new IllegalStateException("title required");
return new Book(this);
}
}
}
// new Book.Builder().title("SDI").author("Alex").pages(300).build();
Use when
Object has many optional fields or the construction has order/validation rules.
Prototype
Make a new object by copying an existing one, instead of running expensive setup again.
How it works
Keep one fully-configured object as a template, then clone() it whenever you need a new one. Cheaper than re-running heavy setup — parsing a schema, loading defaults from disk, warming a cache.
Java
public class Report implements Cloneable {
private String title;
private List<String> sections = new ArrayList<>();
public Report clone() {
Report r = new Report();
r.title = this.title;
r.sections = new ArrayList<>(this.sections); // deep enough for strings
return r;
}
}
Report template = expensiveDefault();
Report a = template.clone(); a.title = "Q1";
Report b = template.clone(); b.title = "Q2";
Use when
Creation is expensive and most instances only differ in a handful of fields.
Structural — how objects fit together
These seven answer: "how do I compose these classes so I can change one without breaking the others?"
Adapter
Translate one interface into another — the plug converter for code.
How it works
Wrap the incompatible thing in a class that exposes the interface you actually wanted. Your code talks to the adapter; the adapter translates to the underlying API. Classic use: switching payment gateways, upgrading a library, isolating a third-party SDK behind your own boundary.
Java
interface PaymentGateway { void charge(long cents, String card); }
class StripeAdapter implements PaymentGateway {
private final com.stripe.StripeClient stripe;
StripeAdapter(com.stripe.StripeClient s) { this.stripe = s; }
public void charge(long cents, String card) {
stripe.paymentIntents().create(Map.of("amount", cents, "source", card));
}
}
// PaymentGateway pg = new StripeAdapter(stripeClient);
// pg.charge(1999, "tok_visa");
Use when
Two things must talk but their interfaces don't match, and you don't own one of them.
Bridge
Split "what" from "how" so both sides can vary without a class explosion.
How it works
You have two dimensions of variation — say shape × renderer — and you don't want RedSvgShape, RedCanvasShape, BlueSvgShape… (2 × 2, then 3 × 3, then M × N). Bridge holds the renderer as a field on the shape. Add a color: one class. Add a renderer: one class. Not M × N.
Java
interface Renderer { void drawCircle(int x, int y, int r); }
class SvgRenderer implements Renderer { public void drawCircle(int x,int y,int r){ /* svg */ } }
class CanvasRenderer implements Renderer { public void drawCircle(int x,int y,int r){ /* canvas */ } }
abstract class Shape {
protected final Renderer renderer;
Shape(Renderer r) { this.renderer = r; }
abstract void draw();
}
class Circle extends Shape {
private final int x,y,r;
Circle(Renderer rend, int x,int y,int r){ super(rend); this.x=x; this.y=y; this.r=r; }
void draw() { renderer.drawCircle(x,y,r); }
}
Use when
Two orthogonal axes of change (message type × transport, format × storage, shape × renderer).
Composite
Treat a group of things the same way you treat one thing. Trees, in one word.
How it works
Leaves and containers share an interface, so calling size() on a file just returns its bytes; calling it on a folder recursively sums its children. The caller doesn't need to know which is which.
Java
interface Node { long size(); }
record File(String name, long bytes) implements Node {
public long size() { return bytes; }
}
class Folder implements Node {
private final List<Node> children = new ArrayList<>();
public void add(Node n) { children.add(n); }
public long size() { return children.stream().mapToLong(Node::size).sum(); }
}
Use when
Tree-shaped data: filesystems, UI widgets, org charts, expression ASTs.
add()?) or the interface is minimal and callers need casts.Decorator
Wrap an object to add behaviour without changing its class. Middleware, essentially.
How it works
Both wrapper and wrapped implement the same interface. The wrapper forwards to the inner object, doing extra work before or after. Stack as many wrappers as you like — logging → auth → rate-limit → handler is exactly this pattern.
Java
interface Coffee { int cost(); }
class Espresso implements Coffee { public int cost() { return 3; } }
abstract class CoffeeDecorator implements Coffee {
protected final Coffee inner;
CoffeeDecorator(Coffee c) { this.inner = c; }
}
class Milk extends CoffeeDecorator { Milk(Coffee c){super(c);} public int cost(){ return inner.cost() + 1; } }
class Sugar extends CoffeeDecorator { Sugar(Coffee c){super(c);} public int cost(){ return inner.cost() + 1; } }
Coffee order = new Sugar(new Milk(new Espresso())); // cost = 5
Use when
You want to add optional, stackable behaviour without touching the original class or writing 2ⁿ subclasses.
Facade
Hide a messy subsystem behind one simple method. The "just do it" pattern.
How it works
Bundle a common workflow across many classes into one method with an obvious name. Callers stop coordinating a dozen collaborators; the facade does. Every SDK's client.doThing() is a facade over dozens of moving parts.
Java
class OrderFacade {
private final Inventory inv;
private final Payment pay;
private final Shipping ship;
OrderFacade(Inventory i, Payment p, Shipping s) { inv=i; pay=p; ship=s; }
public String placeOrder(String userId, String sku, String card) {
inv.reserve(sku);
String txn = pay.charge(userId, card, inv.price(sku));
return ship.dispatch(userId, sku, txn);
}
}
// new OrderFacade(inv, pay, ship).placeOrder("u1","widget","tok_visa");
Use when
The 80% workflow through a subsystem is always the same and callers shouldn't have to memorise it.
Flyweight
Share the parts that don't change, so a million objects fit in the memory of ten.
How it works
Split state into intrinsic (shared, immutable — the font glyph) and extrinsic (per-instance — where it sits on the page). Cache the intrinsic part and pass the extrinsic part in at use time. Renders a million-character document with 26 glyph objects instead of a million.
Java
class Glyph { final char c; Glyph(char c){ this.c = c; } void draw(int x, int y){ /*...*/ } }
class GlyphPool {
private final Map<Character,Glyph> pool = new HashMap<>();
Glyph of(char c) { return pool.computeIfAbsent(c, Glyph::new); }
}
// GlyphPool p = new GlyphPool();
// for (Char ch : doc) p.of(ch.value).draw(ch.x, ch.y); // 26 glyphs, N draws
Use when
Millions of objects that mostly repeat a small alphabet of underlying values (glyphs, tree species in a forest sim, particle sprites).
Proxy
A stand-in that looks like the real object but adds a check, a cache, or a lazy load.
How it works
Proxy implements the same interface as the real object. Callers can't tell them apart. The proxy decides when — or whether — to touch the real thing. Four common flavours: virtual (lazy-load heavy resources), protection (auth check), remote (network stub), caching (memoise expensive calls).
Java
interface Image { void render(); }
class RealImage implements Image {
private final byte[] pixels;
RealImage(String path) { this.pixels = loadFromDisk(path); /* heavy */ }
public void render() { /* paint */ }
}
class ImageProxy implements Image {
private final String path;
private RealImage real;
ImageProxy(String path) { this.path = path; }
public void render() {
if (real == null) real = new RealImage(path); // lazy
real.render();
}
}
Use when
Real object is expensive, restricted, or remote — and you want the substitution to be transparent.
Behavioral — how objects talk
These eleven answer: "who calls whom, in what order, and how do they stay decoupled?"
Chain of Responsibility
Pass a request down a line of handlers until one of them handles it.
How it works
Each handler either processes the request or passes it to the next. Order the chain to your policy: auth first, cache last, handler at the end. Adding a step doesn't touch the others.
Java
abstract class Handler {
protected Handler next;
public Handler chain(Handler n) { this.next = n; return n; }
public void handle(Request r) {
if (canHandle(r)) process(r);
else if (next != null) next.handle(r);
}
abstract boolean canHandle(Request r);
abstract void process(Request r);
}
// new AuthHandler().chain(new RateLimitHandler()).chain(new CacheHandler()).chain(new AppHandler());
// entry.handle(request);
Use when
Preprocessing pipelines: web middleware, logging filters, event routing where any handler may claim the event.
Command
Wrap an action as an object so you can queue it, log it, undo it.
How it works
Each user action becomes an object with execute() and (optionally) undo(). Push executed commands onto a stack — Ctrl+Z pops and undoes. Serialise them and you have a job queue.
Java
interface Command { void execute(); void undo(); }
class DeleteText implements Command {
private final Document doc; private final int from, to;
private String removed;
DeleteText(Document d, int f, int t){ doc=d; from=f; to=t; }
public void execute() { removed = doc.cut(from, to); }
public void undo() { doc.insert(from, removed); }
}
// history.push(cmd); cmd.execute();
// on Ctrl+Z: history.pop().undo();
Use when
Undo/redo, task queues, macro recording, transactional workflows.
Interpreter
Build a tiny language: parse expressions into a tree, then evaluate the tree.
How it works
Every grammar rule is a class. Numbers, variables, operators — each implements eval(). Parse the input into a tree of these nodes, then call eval() on the root. This is how the tiny DSL for feature flags, search filters, or spreadsheet formulas gets built.
Java
interface Expr { int eval(Map<String,Integer> ctx); }
record Num(int v) implements Expr { public int eval(Map<String,Integer> c){ return v; } }
record Var(String name) implements Expr { public int eval(Map<String,Integer> c){ return c.get(name); } }
record Plus(Expr a, Expr b) implements Expr { public int eval(Map<String,Integer> c){ return a.eval(c) + b.eval(c); } }
record Times(Expr a, Expr b) implements Expr { public int eval(Map<String,Integer> c){ return a.eval(c) * b.eval(c); } }
// 3 + 4 * 5 → new Plus(new Num(3), new Times(new Num(4), new Num(5))).eval(Map.of()) == 23
Use when
Small, stable grammar you own — search predicates, targeting rules, calculator engines.
Iterator
Walk a collection without exposing how it's stored.
How it works
The collection hands out an iterator object that knows how to walk itself — array, linked list, tree, database cursor, all the same to the caller. for (x : xs) in Java desugars to exactly this.
Java
class Range implements Iterable<Integer> {
private final int from, to;
Range(int f, int t) { from=f; to=t; }
public Iterator<Integer> iterator() {
return new Iterator<>() {
int i = from;
public boolean hasNext() { return i < to; }
public Integer next() { return i++; }
};
}
}
for (int n : new Range(0, 5)) System.out.println(n); // 0..4
Use when
Custom collection or lazy sequence (paged results, DB rows, infinite streams).
Iterable already exist — reach for a hand-rolled iterator only for lazy or paged sequences.Mediator
Objects talk to a central hub instead of each other. N² wires become N.
How it works
Each participant knows only the mediator. To broadcast, send to the mediator; it fans out. Removes the mesh of direct references — add a new participant without editing the others.
Java
class ChatRoom {
private final List<User> users = new ArrayList<>();
public void join(User u) { users.add(u); u.room = this; }
public void send(User from, String msg) {
for (User u : users) if (u != from) u.receive(from.name, msg);
}
}
class User {
final String name; ChatRoom room;
User(String n) { this.name = n; }
void say(String m) { room.send(this, m); }
void receive(String from, String m) { System.out.println(from + ": " + m); }
}
Use when
Many-to-many communication (UI form fields validating each other, chat, event bus in a small scope).
Memento
Snapshot an object's state, tuck it away, restore it later. Undo without exposing internals.
How it works
The object emits an opaque memento (its state, no methods). A caretaker stores mementos. Later, hand one back to the object to restore. The caretaker never peeks inside — encapsulation preserved.
Java
class Editor {
private String text = "";
public record Memento(String snapshot) {}
public Memento save() { return new Memento(text); }
public void restore(Memento m) { this.text = m.snapshot; }
public void type(String s) { text += s; }
}
Editor e = new Editor();
e.type("hello ");
Editor.Memento snap = e.save();
e.type("world!");
e.restore(snap); // back to "hello "
Use when
Undo/redo with rich state; checkpoint/rollback semantics.
Observer
Publish a change once, and everyone who cares gets notified. The pub/sub of OOP.
How it works
Observers register with a subject. When the subject's state changes, it calls each observer's update(). Loose coupling — the subject has no idea who listens or what they'll do.
Java
interface Observer<T> { void update(T event); }
class OrderEvents {
private final List<Observer<String>> subs = new ArrayList<>();
public void subscribe(Observer<String> o) { subs.add(o); }
public void publish(String event) { for (Observer<String> o : subs) o.update(event); }
}
OrderEvents bus = new OrderEvents();
bus.subscribe(e -> log.info(e));
bus.subscribe(e -> email.send("ops@co", e));
bus.publish("order.placed:1234");
Use when
One event, many reactions; UI models notifying views; in-process event bus.
State
The object's behaviour changes with its state — because state is its own class.
How it works
Instead of a fat if (state == ...) ladder in every method, each state is a class implementing the same interface. The context delegates to its current state object; transitions swap the object out.
Java
interface State { State submit(Post p); State approve(Post p); }
class Draft implements State { public State submit(Post p){ return new Review(); } public State approve(Post p){ throw new IllegalStateException(); } }
class Review implements State { public State submit(Post p){ throw new IllegalStateException(); } public State approve(Post p){ return new Published(); } }
class Published implements State { public State submit(Post p){ throw new IllegalStateException(); } public State approve(Post p){ throw new IllegalStateException(); } }
class Post {
private State state = new Draft();
public void submit() { state = state.submit(this); }
public void approve() { state = state.approve(this); }
}
Use when
An object has a clear lifecycle with rules on which transitions are allowed (order, subscription, document workflow).
Strategy
Pluggable algorithm. Swap the "how" without changing the "what".
How it works
Family of algorithms, all behind one interface. The context holds a reference to one and calls it; the caller can swap. This is the pattern once you learn it — validators, pricing rules, ranking, compression, retry policies all fit.
Java
interface PricingStrategy { int price(int cents, User u); }
class FullPrice implements PricingStrategy { public int price(int c, User u){ return c; } }
class MemberPrice implements PricingStrategy { public int price(int c, User u){ return c * 90 / 100; } }
class BlackFriday implements PricingStrategy { public int price(int c, User u){ return c / 2; } }
class Checkout {
private PricingStrategy strategy;
public void setStrategy(PricingStrategy s) { this.strategy = s; }
public int total(int c, User u) { return strategy.price(c, u); }
}
Use when
Multiple interchangeable ways to do the same thing, chosen at runtime.
Template Method
A base class fixes the skeleton; subclasses fill in the interesting bits.
How it works
Abstract class implements the algorithm as a final method calling smaller steps. Steps that vary are abstract; subclasses override them. Framework code, essentially — Spring's JdbcTemplate, HttpServlet.service(), JUnit's setUp/tearDown.
Java
abstract class Report {
public final String render() {
return header() + body() + footer();
}
protected String header() { return "==== Report ====\n"; }
protected String footer() { return "\n==== End ====\n"; }
protected abstract String body();
}
class SalesReport extends Report {
protected String body() { return "Sales: $1,234"; }
}
new SalesReport().render();
Use when
Fixed workflow with a few hot spots that vary. Inversion of control — base calls sub, not the other way around.
Visitor
Add a new operation across a class hierarchy without editing the classes.
How it works
Each element in the hierarchy has an accept(Visitor v) method that calls back v.visit(this). Add a new operation = one new visitor class, no edits to the elements. The trick: double-dispatch picks the right visit() based on the concrete element type.
Java
interface Shape { double accept(ShapeVisitor v); }
record Circle(double r) implements Shape { public double accept(ShapeVisitor v){ return v.visit(this); } }
record Square(double s) implements Shape { public double accept(ShapeVisitor v){ return v.visit(this); } }
interface ShapeVisitor { double visit(Circle c); double visit(Square s); }
class Area implements ShapeVisitor {
public double visit(Circle c) { return Math.PI * c.r() * c.r(); }
public double visit(Square s) { return s.s() * s.s(); }
}
// new Circle(3).accept(new Area()); // 28.27
// Add PerimeterVisitor: no touching Circle/Square.
Use when
Stable set of element classes, growing set of operations (ASTs, IR passes in a compiler).