Java Patterns
A pattern is not a rule. A rule says always; a pattern says when. That's why this is
a skill and not rules/ — it only costs context when invoked.
Entry rule: no pattern without an observed symptom in the code. Applying a pattern
preemptively is overengineering — a Factory for two lines, a Strategy for a single if,
an interface with one implementation that will never have a second. If you can't point
to the file and line that hurts, the answer is "apply no pattern at all".
Procedure
- Name the symptom in the actual code (
file:line), not in the abstract.
- Look up the symptom in the table. No matching row, stop — write simple code.
- Confirm the pattern doesn't collide with the active blueprint's
packages.map.
- Generate from the exemplar in
templates/, with the names from
@.claude/rules/naming.md. Size limits: @.claude/rules/code-quality.md.
- Record it in an ADR if the pattern changes the shape of an entire module.
Catalog
| Symptom in code |
Pattern |
When not |
switch/if-else over a type or enum, repeated in ≥ 2 places, growing with each new rule |
Strategy |
Two stable branches that have never grown |
| Construction with invariants, many steps, or possible invalid combinations |
Static factory (of, from) |
A record with validation in the compact constructor is already enough |
| ≥ 4 optional constructor parameters |
Builder |
Record + with*; Lombok @Builder on a domain object hides invariants |
| Cross-cutting behavior (cache, retry, audit) repeated across implementations |
Decorator |
A Spring aspect already exists (@Transactional, @Cacheable) that does the same thing |
| Business predicate duplicated between service and query |
Specification |
Predicate used only once |
| Output adapter with multiple technologies (JPA + external REST) behind the same port |
Adapter (already the blueprint) |
A single technology — the port is enough |
| Aggregate that emits ≥ 2 events consumed by several modules |
Domain Events |
A single consumer in the same module — direct call |
This table is the enforceable subset: seven rows, each with a symptom threshold and a
"when not" column. For a symptom that doesn't match any row above,
references/pattern-catalog.md covers the full GoF catalog — every pattern on
https://refactoring.guru/design-patterns/java, each with a shape reference in
templates/ and this repo's verdict on the ones that don't (forbidden, or already
covered by the language/framework). It's triage, not a second menu: the entry rule below
still gates every row in it the same way it gates the seven here — an exemplar existing
is not permission to apply it without a real symptom.
Forbidden in this repository
- Singleton — the Spring container already gives singleton scope; the manual version
with
static prevents testing and hides global state.
- Service Locator /
ApplicationContext.getBean — violates the composition set in
@.claude/rules/architecture-ddd.md.
Impl suffix, always — @.claude/rules/naming.md: the name says the role, not
that it's an implementation of something.
- Pattern name standing in for a role name that exists (
OrderStrategy instead of
DiscountPolicy, OrderDecorator instead of naming what it actually adds) — same
rule, and the two curated exemplars (Strategy.java.example, Decorator.java.example)
deliberately avoid the pattern name for exactly this reason. Not forbidden when the
pattern name is the established Java role vocabulary with no better alternative —
Adapter, Repository, Builder, Factory are accepted the same way
@.claude/rules/naming.md's own convention table already accepts
<Resource>RepositoryAdapter. The test: is there a business-meaningful name this
class could have instead? If yes, use it. If the honest answer is "no, this is just
the JDK-idiomatic word for this role," the pattern name stays.
Contract
Reads .claude/rules/code-quality.md, .claude/rules/naming.md,
.claude/rules/architecture-ddd.md, the active blueprint's packages.map, and
references/pattern-catalog.md when the symptom doesn't match any row of the curated
table above.
Writes domain and application code in the generated project. Doesn't touch the
inbound REST adapter — the package the blueprint's packages.map assigns to that role,
whether adapter.in.rest or infrastructure.rest — which is rest-api-architect's
territory.
Two invocation modes, one writer per mode. Standalone — a human runs
/java-patterns against real code with an observed symptom; this skill is the sole
writer for that turn, as above. Preloaded — java-spring-boot-developer carries this
skill's catalog in full via its skills: frontmatter field and applies a matching row
directly while generating a new feature, never as a separate invocation
(disable-model-invocation wouldn't allow that anyway). In that mode this skill writes
nothing itself: the executor remains the sole owner of src/** (invariant 2), and the
catalog is reference material, the same role templates/*.example plays for the other
four pipeline skills. @.claude/decisions/0025-java-patterns-preloaded-in-executor.md.
Doesn't collide with domain-modeling, which also deals with domain and
application. The split is by moment, not by folder: that one produces the
10-dominio.md spec before code exists; this one refactors code that already
exists and already hurts, from a concrete file:line. Without a symptom in the code,
this isn't the right skill — it's the other one, or none.
Does not create rules. If a new limit has to always hold, it goes to
rules/code-quality.md, not here.
1---2name: java-patterns3description: Chooses and implements a design pattern in Java/Spring from the symptom that justifies it. Use when the request involves a design pattern, Strategy, Factory, Decorator, Builder, Specification, "how to structure this class", a growing if/switch chain, or refactoring a class with too many responsibilities.4---56# Java Patterns78A pattern is not a rule. A rule says *always*; a pattern says *when*. That's why this is9a skill and not `rules/` — it only costs context when invoked.1011**Entry rule: no pattern without an observed symptom in the code.** Applying a pattern12preemptively is overengineering — a Factory for two lines, a Strategy for a single `if`,13an interface with one implementation that will never have a second. If you can't point14to the file and line that hurts, the answer is "apply no pattern at all".1516## Procedure17181. Name the symptom in the actual code (`file:line`), not in the abstract.192. Look up the symptom in the table. No matching row, stop — write simple code.203. Confirm the pattern doesn't collide with the active blueprint's `packages.map`.214. Generate from the exemplar in `templates/`, with the names from22 `@.claude/rules/naming.md`. Size limits: `@.claude/rules/code-quality.md`.235. Record it in an ADR if the pattern changes the shape of an entire module.2425## Catalog2627| Symptom in code | Pattern | When **not** |28|---|---|---|29| `switch`/`if-else` over a type or enum, repeated in ≥ 2 places, growing with each new rule | Strategy | Two stable branches that have never grown |30| Construction with invariants, many steps, or possible invalid combinations | Static factory (`of`, `from`) | A record with validation in the compact constructor is already enough |31| ≥ 4 optional constructor parameters | Builder | Record + `with*`; Lombok `@Builder` on a domain object hides invariants |32| Cross-cutting behavior (cache, retry, audit) repeated across implementations | Decorator | A Spring aspect already exists (`@Transactional`, `@Cacheable`) that does the same thing |33| Business predicate duplicated between service and query | Specification | Predicate used only once |34| Output adapter with multiple technologies (JPA + external REST) behind the same port | Adapter (already the blueprint) | A single technology — the port is enough |35| Aggregate that emits ≥ 2 events consumed by several modules | Domain Events | A single consumer in the same module — direct call |3637This table is the enforceable subset: seven rows, each with a symptom threshold and a38"when not" column. For a symptom that doesn't match any row above,39`references/pattern-catalog.md` covers the full GoF catalog — every pattern on40<https://refactoring.guru/design-patterns/java>, each with a shape reference in41`templates/` and this repo's verdict on the ones that don't (forbidden, or already42covered by the language/framework). It's triage, not a second menu: the entry rule below43still gates every row in it the same way it gates the seven here — an exemplar existing44is not permission to apply it without a real symptom.4546## Forbidden in this repository4748- **Singleton** — the Spring container already gives singleton scope; the manual version49 with `static` prevents testing and hides global state.50- **Service Locator / `ApplicationContext.getBean`** — violates the composition set in51 `@.claude/rules/architecture-ddd.md`.52- **`Impl` suffix**, always — `@.claude/rules/naming.md`: the name says the role, not53 that it's an implementation of something.54- **Pattern name standing in for a role name that exists** (`OrderStrategy` instead of55 `DiscountPolicy`, `OrderDecorator` instead of naming what it actually adds) — same56 rule, and the two curated exemplars (`Strategy.java.example`, `Decorator.java.example`)57 deliberately avoid the pattern name for exactly this reason. Not forbidden when the58 pattern name **is** the established Java role vocabulary with no better alternative —59 `Adapter`, `Repository`, `Builder`, `Factory` are accepted the same way60 `@.claude/rules/naming.md`'s own convention table already accepts61 `<Resource>RepositoryAdapter`. The test: is there a business-meaningful name this62 class could have instead? If yes, use it. If the honest answer is "no, this is just63 the JDK-idiomatic word for this role," the pattern name stays.6465## Contract6667**Reads** `.claude/rules/code-quality.md`, `.claude/rules/naming.md`,68`.claude/rules/architecture-ddd.md`, the active blueprint's `packages.map`, and69`references/pattern-catalog.md` when the symptom doesn't match any row of the curated70table above.7172**Writes** domain and application code in the generated project. Doesn't touch the73inbound REST adapter — the package the blueprint's `packages.map` assigns to that role,74whether `adapter.in.rest` or `infrastructure.rest` — which is `rest-api-architect`'s75territory.7677**Two invocation modes, one writer per mode.** Standalone — a human runs78`/java-patterns` against real code with an observed symptom; this skill is the sole79writer for that turn, as above. Preloaded — `java-spring-boot-developer` carries this80skill's catalog in full via its `skills:` frontmatter field and applies a matching row81directly while generating a new feature, never as a separate invocation82(`disable-model-invocation` wouldn't allow that anyway). In that mode this skill writes83nothing itself: the executor remains the sole owner of `src/**` (invariant 2), and the84catalog is reference material, the same role `templates/*.example` plays for the other85four pipeline skills. `@.claude/decisions/0025-java-patterns-preloaded-in-executor.md`.8687**Doesn't collide with `domain-modeling`**, which also deals with domain and88application. The split is by moment, not by folder: that one produces the89`10-dominio.md` spec **before** code exists; this one refactors code that already90exists and already hurts, from a concrete `file:line`. Without a symptom in the code,91this isn't the right skill — it's the other one, or none.9293**Does not** create rules. If a new limit has to always hold, it goes to94`rules/code-quality.md`, not here.