@@ -6,36 +6,18 @@ description: Write, review, and refactor Java Optional code using best practices
66# Java Optional Skill
77
88Use this skill before writing Java code that may introduce ` Optional ` , and when reviewing or
9- refactoring existing Optional code. Keep ` Optional ` as a clear present/absent boundary while
10- preserving behavior, exception contracts, public output, laziness, and readability.
11-
12- This skill is based on observed production failures where agents avoided one Optional antipattern by
13- introducing another. Treat it as a practical guardrail, not a broad Java style guide.
9+ refactoring existing Optional code. Preserve behavior, exception contracts, public output, laziness,
10+ and readability.
1411
1512Open [ references/optional-examples.md] ( references/optional-examples.md ) for non-trivial edits,
16- review-only tasks, checked-exception cases, or benchmark-style examples.
13+ review-only tasks, priority selectors, checked-exception cases, or benchmark-style examples.
1714
18- ## Decision Procedure
15+ ## Core Workflow
1916
20- 1 . Classify each Optional shape before editing:
21- - single ` Optional<T> ` ;
22- - real collection stream ending in an Optional terminal operation;
23- - boolean-only presence check;
24- - absence-as-error boundary;
25- - side-effecting or expensive fallback;
26- - checked-exception or prompting fallback;
27- - nullable interop with an API that genuinely requires ` null ` .
28- 2 . When writing new code, choose the Optional boundary before writing branches. Decide whether
29- absence means fallback, error, side effect, prompt/IO, or nullable interop.
30- Don't start with an ` isPresent() ` skeleton and clean it up later; write the boundary directly
31- when the intent is ordinary fallback, transformation, or side-effect dispatch.
32- 3 . Reject ordinary-control-flow workarounds:
33- - ` optional.isPresent() ` or ` optional.isEmpty() ` followed by ` get() ` or ` orElseThrow() ` ;
34- - ` optional.orElse(null) ` followed by local ` value != null ` branching;
35- - ` optional.stream().toList() ` or similar just to loop over one Optional;
36- - replacing a readable real collection stream with nested loops, labels, or sentinel flags only
37- to avoid an Optional terminal result.
38- 4 . Use the Optional API that matches the intent when it stays readable:
17+ 1 . Classify the Optional boundary before writing branches: fallback, error, side effect,
18+ boolean-only check, collection lookup, checked IO/prompt, or nullable API interop. Don't start
19+ ordinary value flow with an ` isPresent() ` skeleton.
20+ 2 . Use the Optional API that matches the intent when it stays readable:
3921 - ` map ` for transforming a present value;
4022 - ` flatMap ` when the transform already returns Optional;
4123 - ` filter ` to keep a value only when a predicate matches;
@@ -44,91 +26,48 @@ review-only tasks, checked-exception cases, or benchmark-style examples.
4426 - ` orElseGet ` for lazy, expensive, or side-effecting fallbacks;
4527 - ` orElseThrow ` when absence is truly an error at that boundary;
4628 - ` ifPresent ` or ` ifPresentOrElse ` for side-effect boundaries.
47- 5 . Extract a named helper when a fluent chain becomes dense. Prefer a clear helper over a clever
48- Optional expression.
49- 6 . Preserve laziness. If fallback work creates state, performs IO, mutates data, calls external
50- services, or is expensive, use ` orElseGet(...) ` or an explicit lazy branch, not ` orElse(...) ` .
51- 7 . For review-only tasks, always return an explicit review decision. If no code change is needed,
52- say that and give the Optional-shape rationale; don't return an empty answer.
53- 8 . For collection streams, choose ` findFirst() ` only when encounter order is part of the behavior.
54- Use ` findAny() ` when any matching value is equivalent. When changing or intentionally keeping
55- either method, include a short rationale unless the target output format is code-only.
56- If a real collection lookup feeds more complex stateful code, keep the lookup as a small helper
57- returning ` Optional<T> ` and consume that result directly. For option matchers, centralize exact
58- matches and ` option=value ` matches in that helper instead of splitting matching logic across
59- separate branches. If the surrounding loop needs a boolean such as "does this argument exactly
60- equal the matched option?", derive it from the Optional value with ` match.map(arg::equals).orElse(false) `
61- or a named helper; don't use ` match.filter(arg::equals).isPresent() ` as a new presence gate.
62- 9 . For multiple independent Optional selectors, boolean-only validation may stay as presence checks
63- when no value is read. Once a branch needs the value, map that Optional to the domain action or
64- bind the value once; don't turn a selector into a list, stream, or null branch.
65- For priority selectors, prefer a shape like:
6629
6730 ``` java
68- return primary
69- .map(value - > selectedFromPrimary(value))
70- .orElseGet(() - > secondary
71- .map(value - > selectedFromSecondary(value))
72- .orElseGet(this :: defaultSelection));
31+ return findCart(cartId). map(this :: toSummary). orElseGet(() - > createSummary(cartId));
7332 ```
7433
75- If the selected domain object stores the chosen value as an ` Optional ` , wrap the mapped value
76- with ` Optional.of(value) ` inside the mapping lambda rather than reopening the original Optional.
77- 10 . For checked-exception or prompting fallbacks, plain branching is acceptable:
34+ 3 . Reject ordinary-control-flow workarounds:
35+ - ` isPresent() ` / ` isEmpty() ` followed by ` get() ` or ` orElseThrow() ` ;
36+ - ` orElse(null) ` plus local null branching;
37+ - ` optional.stream().toList() ` or another fake collection around one Optional;
38+ - loops, labels, or sentinel flags that only avoid an Optional terminal result.
7839
7940 ``` java
80- Optional<String > configured = options. workspaceId();
81- if (configured. isEmpty()) {
82- return promptForWorkspace(terminal);
83- }
84- return configured. orElseThrow();
85- ```
86-
87- Keep this exception narrow. The absent branch must genuinely perform checked IO, prompting, or
88- another checked operation, and the enclosing method should honestly expose that boundary. When
89- using this exception, say why plain branching is clearer than hiding checked exceptions in an
90- unchecked wrapper or local helper unless the target output format is code-only.
91- 11 . For nullable interop, keep ` orElse(null) ` only at an actual API boundary that uses ` null ` for
92- absence. Don't add local null branching around it. If changing that boundary would require
93- altering records, DTOs, serialization, or external APIs, call that out as a separate API/design
94- decision rather than bundling it into an Optional cleanup.
41+ // avoid
42+ if (cart. isPresent()) return summarize(cart. get());
43+ return createSummary(cartId);
9544
96- ## What Not To Do
97-
98- - Don't replace ` isPresent() ` plus ` get() ` with ` orElse(null) ` plus null checks.
99- - Don't force a single ` Optional<T> ` through stream/list syntax to avoid a branch.
100- - Don't ban ` orElseThrow() ` , ` ifPresent() ` , or ` isPresent() ` globally. Classify the shape first.
101- - Don't replace readable collection streams with loops merely because the stream returns Optional.
102- - Don't hide checked exceptions inside unchecked wrappers just to keep fluent Optional syntax.
103- - Don't add Vavr or another functional library for a few Optional call sites. Treat that as a
104- repository-wide style decision requiring the target repository's design process.
105-
106- ## Review Checklist
107-
108- Before finishing an Optional-related Java change, verify:
45+ // prefer
46+ return cart. map(this :: summarize). orElseGet(() - > createSummary(cartId));
47+ ```
10948
110- - you scanned touched code for sibling instances of the same pattern;
111- - ordinary ` isPresent() ` or ` isEmpty() ` plus immediate value reads are removed or justified as a
112- narrow checked-exception boundary;
113- - no ` orElse(null) ` plus local null-control-flow workaround was introduced;
114- - no single Optional was converted to a collection or stream just to branch;
115- - real collection streams remain streams when clearer than manual loop state;
116- - ` findFirst() ` is used only where order matters, otherwise ` findAny() ` is used;
117- - non-obvious ordering, checked-exception, side-effect-boundary, and nullable-interop decisions are
118- briefly explained when the output format allows prose;
119- - review-only no-op findings still include a short rationale;
120- - boolean-only Optional validation stays separate from value-reading branches;
121- - side-effecting or expensive fallbacks remain lazy;
122- - exception types/messages, public output, prompts, generated output, and branch order are
123- preserved;
124- - any attractive rejected approach is documented in the form expected by the target repository.
49+ 4 . Preserve laziness. If fallback work creates state, performs IO, mutates data, calls external
50+ services, or is expensive, use ` orElseGet(...) ` or an explicit lazy branch.
51+ 5 . For collection streams, use ` findFirst() ` only when order matters; otherwise use ` findAny() ` .
52+ Keep real lookups as streams. When stateful code consumes the match, extract an ` Optional<T> `
53+ helper that centralizes exact and ` option=value ` matching.
54+ 6 . For selectors, keep presence checks only for boolean-only validation. Once a value is needed,
55+ map or bind it once. For priority selectors, map the first Optional and use lazy fallback for
56+ later sources. If the domain object stores an Optional, wrap the chosen value inside the mapping
57+ lambda.
58+ 7 . Handle special boundaries directly: use a plain branch for checked IO or prompts; keep
59+ ` orElse(null) ` only at real null-based API boundaries; return an explicit decision for
60+ review-only tasks.
61+ 8 . Verify the result: same return values, exceptions, prompts, side effects, laziness, generated
62+ output, and branch order; scan sibling code for the same Optional smell.
12563
12664## When To Open References
12765
12866Open [ references/optional-examples.md] ( references/optional-examples.md ) when:
12967
13068- you're unsure whether a stream source is a real collection or a single Optional workaround;
13169- a fallback has side effects or checked exceptions;
70+ - priority selectors need a worked example;
13271- ` findFirst() ` versus ` findAny() ` is under review;
13372- you need examples for testing an agent or skill implementation.
13473
0 commit comments