Java Code Smells
Purpose
A smell is evidence, not a verdict. This skill runs a detection pass whose output is a
short, prioritised list of findings — each with the code it names, the evidence, a
severity argument, and the java-refactoring technique that addresses it. The failure
modes it prevents: reporting everything pattern-matching a smell (noise the team
ignores), and rewriting code during what was supposed to be a diagnosis.
Workflow
Use Java 21 without preview as the example baseline. Inspect Maven/Gradle release settings,
toolchains and CI/runtime versions before suggesting records, sealed types or pattern switches;
do not upgrade the project or enable preview features as part of detection. The catalogue
also applies to older Java, with recommendations conditional on its supported language level.
- Scan for signals, not smells. Size outliers (methods, classes, parameter lists),
change history (
git log --follow on files that appear in every PR), duplication,
and dependency fan-in/fan-out. Signals say where to look; the catalogue says what
you found.
- Classify against the catalogue — read
references/catalogue-within.md for
findings inside one class, references/catalogue-between.md for findings about
coupling between classes. Check the smell's false positives before recording it.
- Weigh severity from risk, not size. Combine change frequency and blast radius with
business/security criticality, defect evidence, test/observability confidence, ownership,
reversibility and migration cost. A rarely edited authorisation or money-movement path may
outrank a frequently edited formatter. Repository history is evidence, not a veto.
- Record findings, do not fix them. Each finding: smell name, location, evidence,
severity argument, the named java-refactoring technique — routed through
references/smell-to-refactoring.md, which also says what decides between competing
techniques and when the honest recommendation is no refactoring. Fixing happens in a
separate pass under that skill's safety workflow.
If history is shallow or unavailable, say which change-pressure claims cannot be established.
Use source-level evidence for current coupling, but do not invent recurring co-change or defect
history. Name what additional evidence would distinguish a finding from a monitor-only lead.
Rules
- No finding without evidence a reviewer can check: a metric, a diff that fanned out, a
duplicated block's two locations. "This looks wrong" is not a finding.
- Stable code gets a lower change-pressure score, not immunity. Dormant compatibility,
security, concurrency and data-integrity defects still warrant findings when their impact and
evidence are strong; absence of tickets is weak evidence when failures are silent.
- One structural cause often shows as several smells (a God Object produces Feature
Envy in its neighbours and Shotgun Surgery in its callers). Report the cause once,
not each symptom separately.
- An exhaustive
switch over a sealed type with no default is not the Switch
Statements smell — it is one of its fixes. Read references/modern-java.md before
flagging any switch, record, or Optional usage.
- A comment apologising for code ("hack", "careful here") is a search lead, not a finding.
Preserve comments that encode an invariant, upstream defect, compatibility constraint or
measured workaround; report the underlying structure only when independently evidenced.
- Never bundle a fix into the detection pass. Detection changes no code.
Severity and decision record
For each candidate, record this compact tuple:
location + structural signal
evidence and counter-evidence
change pressure + impact if wrong
test/observability confidence
plausible refactoring and migration surface
decision: finding | monitor | no action
Do not multiply ordinal scores and pretend the result is quantitative risk. Use the dimensions
to expose the argument, then rank findings relative to this repository:
| Priority |
Typical evidence |
| Critical/high |
Security, money or data-integrity failure; repeated incidents; unsafe concurrent state; change is already blocked by the structure |
| Medium |
Recurring co-change/defects with a bounded refactoring seam and adequate characterization tests |
| Low/monitor |
Readability cost with little change pressure, speculative future benefit, or migration cost larger than demonstrated harm |
| No action |
Intentional boundary/representation, generated code, framework contract, or candidate falsified by ownership/change evidence |
Before recommending a move, identify externally observed contracts: serialized fields, database
mapping, reflection, dependency injection, native-image configuration and module exports. A
smell inside such a boundary may be real while the safe recommendation is staged migration,
not immediate cleanup.
References
- Catalogue: within a class — Long Method, Large
Class, Primitive Obsession, Data Clumps, Temporary Field, Duplicate Code, Dead Code,
comments-as-deodorant, boolean blindness, Speculative Generality, Mysterious Name,
Long Parameter List, Mutable Data, Loops, Lazy Element.
- Catalogue: between classes — Feature Envy,
Shotgun Surgery, Divergent Change, Message Chains, Middle Man, Refused Bequest,
Inappropriate Intimacy, Switch Statements, null-heavy APIs, leaky abstraction, Global
Data, Alternative Classes with Different Interfaces, Data Class.
- Smell → refactoring — the routing table from a
recorded finding to the java-refactoring techniques that address it, what decides
between competing techniques, the sequences that must run in order, and when the
correct output is no refactoring at all. Read at step 4, when turning findings into
recommendations.
- Modern Java: dissolved and created smells — what
records, sealed types and Optional removed from the classic catalogue and what they
added. Read before flagging switches, records, or Optional chains.
- A worked smell pass — one realistic service audited end
to end: signals, findings, severity weighing, and the false positive that was
deliberately not reported. Read when unsure how to weigh or phrase findings.
- Primitive confusion check — run with a POSIX shell
and JDK 21+ when assessing whether distinct identifier types prevent a real argument swap.
It compiles with
--release 21, runs the before/after cases and checks the intentional
compile failure. This demonstrates type safety, not application behavior or performance.
1---2name: java-code-smells3description: The detection catalogue for Java code smells: Long Method, God Object, Feature Envy, Primitive Obsession, Data Clumps, Shotgun Surgery, Divergent Change, Mysterious Name, Mutable and Global Data, Data Class, Loops, Lazy Element, Refused Bequest, boolean blindness, null-heavy APIs and leaky abstraction, plus how modern Java changes the list and the routing table from a finding to the refactoring that fixes it. Use when auditing code for structural problems, before planning a refactoring, when one change keeps fanning out across many files, when several refactorings could address one finding, when a switch over a sealed type carries a default branch, or when deciding whether a suspect pattern is actually a problem. Detection and severity only — refactoring mechanics are java-refactoring, navigation-chain depth is java-law-of-demeter, and the economics of duplication and premature abstraction are java-dry-kiss-yagni.4---56# Java Code Smells78## Purpose910A smell is evidence, not a verdict. This skill runs a detection pass whose output is a11short, prioritised list of findings — each with the code it names, the evidence, a12severity argument, and the java-refactoring technique that addresses it. The failure13modes it prevents: reporting everything pattern-matching a smell (noise the team14ignores), and rewriting code during what was supposed to be a diagnosis.1516## Workflow1718Use Java 21 without preview as the example baseline. Inspect Maven/Gradle release settings,19toolchains and CI/runtime versions before suggesting records, sealed types or pattern switches;20do not upgrade the project or enable preview features as part of detection. The catalogue21also applies to older Java, with recommendations conditional on its supported language level.22231. **Scan for signals, not smells.** Size outliers (methods, classes, parameter lists),24 change history (`git log --follow` on files that appear in every PR), duplication,25 and dependency fan-in/fan-out. Signals say where to look; the catalogue says what26 you found.272. **Classify against the catalogue** — read `references/catalogue-within.md` for28 findings inside one class, `references/catalogue-between.md` for findings about29 coupling between classes. Check the smell's false positives before recording it.303. **Weigh severity from risk, not size.** Combine change frequency and blast radius with31 business/security criticality, defect evidence, test/observability confidence, ownership,32 reversibility and migration cost. A rarely edited authorisation or money-movement path may33 outrank a frequently edited formatter. Repository history is evidence, not a veto.344. **Record findings, do not fix them.** Each finding: smell name, location, evidence,35 severity argument, the named java-refactoring technique — routed through36 `references/smell-to-refactoring.md`, which also says what decides between competing37 techniques and when the honest recommendation is no refactoring. Fixing happens in a38 separate pass under that skill's safety workflow.3940If history is shallow or unavailable, say which change-pressure claims cannot be established.41Use source-level evidence for current coupling, but do not invent recurring co-change or defect42history. Name what additional evidence would distinguish a finding from a monitor-only lead.4344## Rules4546- No finding without evidence a reviewer can check: a metric, a diff that fanned out, a47 duplicated block's two locations. "This looks wrong" is not a finding.48- Stable code gets a lower change-pressure score, not immunity. Dormant compatibility,49 security, concurrency and data-integrity defects still warrant findings when their impact and50 evidence are strong; absence of tickets is weak evidence when failures are silent.51- One structural cause often shows as several smells (a God Object produces Feature52 Envy in its neighbours and Shotgun Surgery in its callers). Report the cause once,53 not each symptom separately.54- An exhaustive `switch` over a sealed type with no `default` is not the Switch55 Statements smell — it is one of its fixes. Read `references/modern-java.md` before56 flagging any switch, record, or Optional usage.57- A comment apologising for code ("hack", "careful here") is a search lead, not a finding.58 Preserve comments that encode an invariant, upstream defect, compatibility constraint or59 measured workaround; report the underlying structure only when independently evidenced.60- Never bundle a fix into the detection pass. Detection changes no code.6162## Severity and decision record6364For each candidate, record this compact tuple:6566```text67location + structural signal68evidence and counter-evidence69change pressure + impact if wrong70test/observability confidence71plausible refactoring and migration surface72decision: finding | monitor | no action73```7475Do not multiply ordinal scores and pretend the result is quantitative risk. Use the dimensions76to expose the argument, then rank findings relative to this repository:7778| Priority | Typical evidence |79| ------------- | ---------------------------------------------------------------------------------------------------------------------------------- |80| Critical/high | Security, money or data-integrity failure; repeated incidents; unsafe concurrent state; change is already blocked by the structure |81| Medium | Recurring co-change/defects with a bounded refactoring seam and adequate characterization tests |82| Low/monitor | Readability cost with little change pressure, speculative future benefit, or migration cost larger than demonstrated harm |83| No action | Intentional boundary/representation, generated code, framework contract, or candidate falsified by ownership/change evidence |8485Before recommending a move, identify externally observed contracts: serialized fields, database86mapping, reflection, dependency injection, native-image configuration and module exports. A87smell inside such a boundary may be real while the safe recommendation is staged migration,88not immediate cleanup.8990## References9192- [Catalogue: within a class](references/catalogue-within.md) — Long Method, Large93 Class, Primitive Obsession, Data Clumps, Temporary Field, Duplicate Code, Dead Code,94 comments-as-deodorant, boolean blindness, Speculative Generality, Mysterious Name,95 Long Parameter List, Mutable Data, Loops, Lazy Element.96- [Catalogue: between classes](references/catalogue-between.md) — Feature Envy,97 Shotgun Surgery, Divergent Change, Message Chains, Middle Man, Refused Bequest,98 Inappropriate Intimacy, Switch Statements, null-heavy APIs, leaky abstraction, Global99 Data, Alternative Classes with Different Interfaces, Data Class.100- [Smell → refactoring](references/smell-to-refactoring.md) — the routing table from a101 recorded finding to the java-refactoring techniques that address it, what decides102 between competing techniques, the sequences that must run in order, and when the103 correct output is no refactoring at all. Read at step 4, when turning findings into104 recommendations.105- [Modern Java: dissolved and created smells](references/modern-java.md) — what106 records, sealed types and Optional removed from the classic catalogue and what they107 added. Read before flagging switches, records, or Optional chains.108- [A worked smell pass](references/worked-pass.md) — one realistic service audited end109 to end: signals, findings, severity weighing, and the false positive that was110 deliberately not reported. Read when unsure how to weigh or phrase findings.111- [Primitive confusion check](scripts/primitive-obsession/verify.sh) — run with a POSIX shell112 and JDK 21+ when assessing whether distinct identifier types prevent a real argument swap.113 It compiles with `--release 21`, runs the before/after cases and checks the intentional114 compile failure. This demonstrates type safety, not application behavior or performance.