When multiple `on(...)` patterns could match a step's ExitStatus (e.g. `"COMPLETED"` and `"C*"` and `"*"`), how does Spring Batch decide which transition wins?
answer
- most specific wins, not declaration order
- fewer wildcards = more specific
- literal > ? > * ... > catch-all *
- equal specificity = ambiguous, avoid
- always keep a * fallback
basics
~20 sSpring Batch picks the most specific matching pattern. A literal like "COMPLETED" beats "C*", which beats "*". Specificity is judged by how many wildcard characters the pattern has — fewer wildcards (and more literal characters) wins.
solid answer
~40 sWhen several registered transitions match a step's exit code, Spring Batch does not use declaration order — it chooses the **most specific** pattern. Specificity is determined by comparing the patterns: a pattern with fewer wildcard characters (`*`/`?`) and more literal characters is more specific. So for exit code "COMPLETED", `"COMPLETED"` (exact) beats `"COMPLETED*"` beats `"C*"` beats `"*"`. This lets you register a broad `on("*")` catch-all alongside specific handlers without the catch-all swallowing everything. The comparison is done by the framework's pattern-sorting (an internal Comparator on the state transitions), so ordering your `.on(...)` calls in the builder does not change the outcome. Practically: define specific branches plus one `*` fallback and trust the specificity rule.
code
java · 6 lines// Order of .on(...) calls does NOT decide precedence; specificity does.
job.start(step)
.on("*").to(normal) // least specific: only fires if nothing better matches
.from(step).on("FAILED").to(recovery) // exact literal: wins for exit code FAILED
.from(step).on("COMPLETED*").to(audit) // fires for COMPLETED / COMPLETED WITH SKIPS
.end();go deeper
Not expected to know specificity resolution.
Should at least know a catch-all doesn't override specific patterns.
Expected to state 'most specific wins, order-independent' and rank literal>?>*.
Should flag equal-specificity ambiguity and design disjoint, self-documenting pattern sets.
## The situation You can register many transitions off one step, and their patterns can overlap. For exit code `"COMPLETED"`, all of `"COMPLETED"`, `"COMPLETED*"`, `"C*"`, `"*"` match. Only one transition can fire — which? ## Rule: most specific wins, not first declared Spring Batch sorts the candidate transitions by **specificity** and picks the most specific match. Declaration/builder order is irrelevant. Internally each transition wraps a pattern; the framework compares patterns so that: - A pattern with **fewer wildcard characters** is more specific. - Among the two wildcards, `*` (matches many chars) is considered *less* specific than `?` (matches exactly one), because `*` is broader. - An exact literal (no wildcards) is the most specific of all. So the ranking for exit code "COMPLETED": 1. `"COMPLETED"` (exact — 0 wildcards) 2. `"COMPLETE?"` / `"COMPLETED*"` (one wildcard) 3. `"C*"` (one `*`, few literals) 4. `"*"` (catch-all — least specific) ## Why this design matters It lets you write: ```java job.start(step) .on("FAILED").to(recovery) .from(step).on("COMPLETED WITH SKIPS").to(auditSkips) .from(step).on("*").to(normal) // catch-all, safely last-resort .end(); ``` The `*` will only fire for exit codes not matched by a more specific pattern. If order mattered instead, a catch-all declared early would hijack everything. ## Interaction with wildcards recap - `*` = zero-or-more characters. - `?` = exactly one character. These are glob patterns, not regex — no character classes, no anchors, no alternation. ## Gotchas - If **two patterns are equally specific** and both match, the behavior is ambiguous/undefined-ish; avoid registering two same-specificity overlapping patterns (e.g. `"C*D"` and `"CO*"`). Keep patterns disjoint or clearly ranked. - People assume ordering their `.on(...)` calls controls precedence — it does not. Rely on specificity. - Remember exit codes can be multi-word ("COMPLETED WITH SKIPS"); a bare `"COMPLETED"` will NOT match that string at all (it's not more-specific-vs-less — it simply doesn't match), so you still need `"COMPLETED*"` or `"*"` to catch it. - No matching transition at all -> the flow errors out; always include a `*` fallback. ## When to use Use a layered set of specific patterns plus one `*` catch-all for robust branching. Reach for `?` rarely — mostly for fixed-length codes. Prefer explicit literal codes for readability.
- If you declare `on("*")` first and `on("FAILED")` last, which fires on a FAILED exit?on("FAILED") — it is an exact literal and therefore more specific than the `*` catch-all. Builder/declaration order does not affect the choice; specificity does.
- What happens if two equally-specific patterns both match the same exit code?The resolution is ambiguous — there is no well-defined tie-break you should rely on, so it's a design smell. Keep overlapping patterns at clearly different specificities or make them mutually exclusive.
saying these in an interview costs you the question
- Believing the first-declared matching transition wins
- Thinking `on("COMPLETED")` is 'more specific' so it will still match "COMPLETED WITH SKIPS" (it doesn't match at all)
- Treating patterns as regex with anchors/classes