skip to content

When multiple `on(...)` patterns could match a step's ExitStatus (e.g. `"COMPLETED"` and `"C*"` and `"*"`), how does Spring Batch decide which transition wins?

level: seniorimportance: should knowfreq 30%

answer

  1. most specific wins, not declaration order
  2. fewer wildcards = more specific
  3. literal > ? > * ... > catch-all *
  4. equal specificity = ambiguous, avoid
  5. always keep a * fallback

basics

~20 s

Spring 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 s

When 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
java
// 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

for a junior

Not expected to know specificity resolution.

for a middle

Should at least know a catch-all doesn't override specific patterns.

for a senior

Expected to state 'most specific wins, order-independent' and rank literal>?>*.

for a principal

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

context