# Gof Patterns In Modern Java

> Which Gang-of-Four patterns modern Java and Spring already implement, which they only change the expression of, and which still need writing by hand. Covers records, sealed types and pattern matching against Visitor, State, Composite and Interpreter; lambdas and functional interfaces against Strategy, Command, Factory Method and Observer; the container against Singleton, Abstract Factory and Factory Method; framework mechanisms against Decorator and Proxy; and what virtual threads and ScopedValue change about patterns that carry context or defer work. Use when implementing a pattern from an older text, when a hand-rolled mechanism duplicates something the framework provides, or when deciding whether a pattern is obsolete or merely invisible. Does not cover choosing a pattern (gof-pattern-selection), any individual pattern's guidance (the gof-\* skills), enterprise patterns against frameworks (patterns-and-modern-frameworks), or the inheritance decision (java-composition-over-inheritance).

- Skill: `robsonkades/gof-patterns-in-modern-java` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add robsonkades/gof-patterns-in-modern-java`
- Raw SKILL.md: https://api.skillmd.com/api/skills/robsonkades/gof-patterns-in-modern-java/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: robsonkades (https://skillmd.com/u/robsonkades)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/robsonkades/gof-patterns-in-modern-java

---


# Patterns in Modern Java

## Purpose

Separate what a pattern _is_ from how it is _written_. Almost no GoF pattern has become obsolete;
many have stopped needing a class hierarchy, and several are now supplied by the language or the
framework so completely that hand-writing them duplicates working machinery.

The distinction to hold throughout:

```text
Design intent       "this algorithm varies, and callers must not know
                     which one runs"    → still true, still Strategy

Implementation      interface + N classes + a selector
mechanism           → optional; a lambda expresses the same intent
```

Refusing the vocabulary because the mechanism changed is as costly as building the 1994 mechanism.
Say "Strategy, as a function" and the design stays legible.

Inspect compiler release/toolchains, resolved framework versions, CI/runtime and existing extension
contracts before suggesting a replacement. Records/sealed types fit Java 17; record patterns and
pattern switch need Java 21 without preview; Gatherers are final in 24 and ScopedValue in 25.
StructuredTaskScope remains preview in Java 25: its release-specific API needs explicit project
preview authorization and compiler/runtime flags. Do not upgrade or enable preview merely to use
this skill. Missing environment evidence makes a version-sensitive recommendation conditional.

## Three categories

```text
COMMON MECHANISMS PROVIDED — reuse them when their guarantees match
    Iterator          Iterable / Iterator / Spliterator (Stream is a pipeline, not a replacement)
    Singleton-like lifecycle  the container's singleton scope (not global uniqueness)
    Proxy             @Transactional, @Cacheable, JPA lazy loading
    Decorator         servlet filters, interceptors, client builders
    Observer          application events; reactive streams; brokers
    Chain of Resp.    filter chains, interceptor chains

ALTERNATIVE EXPRESSIONS — compare with the existing contract
    Strategy          a lambda or a domain functional interface
    Command           a record; often a Runnable/Callable
    Factory Method    an injected Supplier or a keyed map
    Visitor           sealed interface + exhaustive switch
    State             sealed states + one transition function
    Prototype         a copy factory; usually immutability instead
    Memento           an immutable state behind one reference
    Template Method   a final class taking composed steps
    Builder           compare records/factories with staged construction needs

DOMAIN DESIGN STILL REQUIRED — libraries may supply mechanisms;
the classical questions apply with modern types
    Composite, Bridge, Mediator, Interpreter, Abstract Factory,
    Adapter, Facade, Flyweight
```

## The two changes that matter most

**Sealed types plus pattern matching.** A closed hierarchy with
an exhaustive `switch` can express external operations and state dispatch. Adding an operation is
often one new function; adding a variant requires updating affected exhaustive switches on
recompilation. Catch-all cases and non-sealed branches limit that check. Sealing does not define
valid state transitions or decide which Composite types expose child mutation. Where you own
the relevant variants and compatibility boundary, consider it
(`java-composition-over-inheritance`).

**The container.** DI can own instance lifetimes and assemble a family, but singleton scope is
per bean definition/container, profiles can overlap, and compatible-family invariants need tests
or validation. Supplier injection can replace application-controlled creation hooks when public
extension and lifecycle contracts permit it.

Two smaller but real ones: **records** remove the boilerplate that made Builder, Memento and
Prototype heavy, but records are only shallowly immutable. **Virtual threads** can simplify
blocking I/O orchestration; they do not remove requests represented as Commands, durable work,
admission control, cancellation or downstream capacity limits (`thread-sizing-and-virtual-threads`).

## Decision rules

```text
IF you are implementing a pattern from an older text
THEN check whether the JDK/framework supplies the required semantics before
     hand-writing infrastructure. Do not turn categories into prohibitions.

IF the framework provides the mechanism
THEN compare ordering, lifecycle, failure and observability semantics before reuse.
     Avoid duplicate policy; custom implementations can still integrate correctly.

IF the variant set is closed and you own it
THEN consider sealed + exhaustive switch against Visitor, State and Strategy,
     preserving behavior, compatibility and the actual direction of change.

IF the variant set is open to code you do not compile
THEN preserve an extension interface. Classic Visitor also couples visitors to
     element types; it does not automatically solve independently added variants.

IF a lambda would express the pattern
THEN compare a function with a named implementation for state, metadata,
     checked failures and diagnostics; name the intent whichever form is chosen.

IF diagnosability matters (a hot path, a production stack trace)
THEN inspect actual traces/profiles; named methods/classes can help, but a lambda
     is not automatically undiagnosable (flame-graph-analysis).

IF a pattern exists to defer or offload work
THEN check whether virtual threads remove the need
     (thread-sizing-and-virtual-threads, structured-concurrency).

IF immutable context must flow down a bounded call tree
THEN consider ScopedValue on Java 25+. ThreadLocal remains appropriate for
     mutable/per-thread integration in some libraries but requires lifecycle
     cleanup and does not automatically propagate to arbitrary executor tasks
     (scoped-values).
```

## What has not changed

- **Coupling analysis.** Who knows whom, and what must not know what, is unaffected by syntax.
- **The cost of indirection.** A lambda hides a dispatch site exactly as a class does.
- **Naming.** "Strategy", "Adapter", "Mediator" still tell a reader what to expect, and the
  expectations differ.
- **The high-risk set.** Singleton, Observer, Mediator, Proxy, Flyweight and Prototype are risky
  for reasons the language does not address — global state, unspecified ordering, god objects,
  hidden network calls, shared mutation, and broken copying.
- **"No pattern" as an answer.** Modern features make it easier to reach, not less legitimate.

## References

Return the existing mechanism, proposed replacement or reason to retain it, target compatibility,
preserved contracts and checks executed versus pending. A shorter class list is not validation.

- [Feature to pattern](references/feature-to-pattern.md) — each modern Java feature with the
  patterns it changes and how: records, sealed types, pattern matching, switch expressions,
  lambdas and method references, generics, `Optional`, immutable collections, default methods,
  dependency injection, virtual threads, structured concurrency, `ScopedValue` and Gatherers. Read
  when a language feature suggests a design might be simplified.
- [Pattern by pattern](references/pattern-by-pattern.md) — all twenty-three with the modern verdict,
  the mechanism that replaces or supplies each, and the residual case where the classical form is
  still correct. Read when implementing a specific pattern from an older reference.

