GoF Patterns vs Modern Java: Strategy vs Lambdas, Singleton vs DI, Factory vs Builder, Observer vs Reactive, Command vs Events
Fast recall
Quick revision
Core answer
Function/lambda, Command is a Runnable or record + handler, Template Method is List, Iterator is built-in. Others shift purpose: Singleton is replaced by DI scopes (Spring's @Singleton/default scope) in 90% of cases, Factory is often just a builder + static method or a Supplier, Observer is replaced by reactive streams (Flux, Flow) or messaging. Some are more relevant than ever: Adapter, Decorator, Facade, Builder, Visitor (with sealed types), Chain of Responsibility (e.g., Spring filters), and State (with sealed + records). Anti-patterns in modern code: Singletons as global mutable state, Factory-of-Factory-of-Factory pyramids, Observer with leaky listener lists, Template Method inheritance trees. The senior takeaway: know the patterns by name so you can recognize them in code, frameworks, and design conversations — but reach for the simplest modern idiom first (function, record, sealed type, DI), and introduce explicit patterns only when complexity justifies the ceremony.Reciting GoF gospel; over-applying patterns to simple code; ignoring that DI+records+sealed fundamentally changed the landscape
Map classical patterns to modern idioms; recognize anti-patterns; know when ceremony helps; reference Spring/Java stdlib examples
Interview-ready guide
Interview answer
Most GoF patterns still matter, but their expression has changed dramatically in modern Java. Patterns are vocabulary — you need to recognize them — but reach for the simplest modern idiom first.
Largely replaced by language features
- Strategy → Function / lambda / Comparator.comparing
- Command → Runnable or record + handler
- Template Method → list of steps or function pipeline
- Iterator → built-in Iterable and Stream
- Observer → Flux, Flow, RxJava, Spring events, Kafka
- Singleton → DI scope, Spring @Component default
- Factory Method → Supplier, static factory, List.of / Map.of, Spring @Bean
Still very relevant and often improved
- Adapter — translating interfaces is a real recurring problem
- Decorator — functional decoration, AOP, filters, interceptors
- Facade — simplifying complex subsystems; good service interfaces are facades
- Builder — 5+ fields, optional fields, validation; records cover simple case
- Chain of Responsibility — Spring Security filter chain, Netty pipelines
- Visitor — sealed types + pattern matching made it elegant and exhaustive
- State — sealed interface + records + pattern matching for transitions
- Composite — trees; sealed types make it safer
- Mediator — at scale: event bus, Kafka; at class scale: ApplicationEventPublisher
Modern Java changes to notice
- Lambdas killed most Strategy/Command boilerplate
- Records made immutable value objects trivial
- Sealed types + pattern matching enabled safer State/Visitor
- CompletableFuture and reactive streams replaced Observer
- DI replaced Singleton/Factory in 90% of cases
- Modules reduced Facade ceremony
Anti-pattern warning signs
- Singleton as global mutable state
- Factory pyramids like AbstractBeanFactoryFactoryProvider
- Observer lists that leak on forgotten unsubscribe
- Template Method inheritance trees 5 levels deep
- Visitor double-dispatch boilerplate where pattern matching would do
- Decorator stacks 10 deep
- Builders for 2-field objects
- Facades that just forward 1:1
- Pattern-named classes instead of domain-named
Decision heuristics
- One place, simple behavior → lambda / record / function ref
- Multiple implementations with discovery → explicit Strategy/Factory with DI
- Cross-cutting concern → AOP, filter chain, decorator
- Complex state machine → State + sealed types
- Closed hierarchy + new operations → Visitor via sealed + pattern match
- Commands need serialization/queueing/audit → Command record + handler
- Complex object construction → Builder
Naming discipline
- Prefer domain names: ShippingPolicy, not ShippingStrategy
- Pattern is implementation detail; domain language is stable
When to refactor away
- Pattern present but variation never materialized → collapse to concrete
- Inheritance tree deeper than 2 levels → often pattern gone wrong
- Builder with one consumer and all required fields → constructor/record
- Observer with one subscriber always installed → direct call
Cost of ceremony
- Each abstraction costs cognitive load, test surface, code to navigate
- YAGNI: add when variation is real, not speculative
- Refactor to pattern when second variation shows up
Teaching and review
- Simplest idiom for current and near-future variation
- Name the pattern when it helps communication
- Don't cargo-cult pattern ceremony on every PR
Language keeps changing the answer
- Records reduced Builder need
- Sealed + pattern matching changed State and Visitor
- Virtual threads may reduce some callback patterns further
- Reassess every 3–5 years; don't cement 1994 answers into 2026 code
Bottom line
- Patterns are a shared vocabulary and recognition tool
- Modern Java has absorbed many of them as features
- Know the classics; apply the simplest idiom; introduce explicit patterns only when complexity justifies the ceremony
GoF Patterns vs Modern Java: Strategy vs Lambdas, Singleton vs DI, Factory vs Builder, Observer vs Reactive, Command vs Events
Direct Answer
Most GoF patterns still matter, but their expression has changed. Modern Java makes many of them one-liners: Strategy is a Function/lambda, Command is a Runnable or record + handler, Template Method is List<Step>, Iterator is built-in. Others shift purpose: Singleton is replaced by DI scopes (Spring's @Singleton/default scope) in 90% of cases, Factory is often just a builder + static method or a Supplier, Observer is replaced by reactive streams (Flux, Flow) or messaging. Some are more relevant than ever: Adapter, Decorator, Facade, Builder, Visitor (with sealed types), Chain of Responsibility (e.g., Spring filters), and State (with sealed + records). Anti-patterns in modern code: Singletons as global mutable state, Factory-of-Factory-of-Factory pyramids, Observer with leaky listener lists, Template Method inheritance trees. The senior takeaway: know the patterns by name so you can recognize them in code, frameworks, and design conversations — but reach for the simplest modern idiom first (function, record, sealed type, DI), and introduce explicit patterns only when complexity justifies the ceremony.
Common Mistakes & Pitfalls
Reciting GoF gospel; over-applying patterns to simple code; ignoring that DI+records+sealed fundamentally changed the landscape
Check your understanding
Answer these without looking back at the article.
- When would you choose an explicit Strategy class over a lambda?
- How do sealed types change how you implement a state machine?
- What's wrong with a 5-level Template Method inheritance tree, and how do you refactor it?
- When is Builder overkill and when is it the right choice?
Related interview prep categories
2Want a guided curriculum order?
These modules are also sequenced in our flagship backend interview track — open it when you prefer an ordered path instead of browsing by topic.