When would you choose a JobExecutionDecider versus alternatives (custom step ExitStatus, split/parallel flows, or externalizing orchestration), and what trade-offs guide that decision?
answer
- pass/fail -> ExitStatus; computed choice -> decider
- parallel -> split, not decider
- repeated subprocess -> nested flow bean
- cross-job / long-running / human step -> externalize
- each branch multiplies restart scenarios
basics
~20 sUse a decider when routing depends on logic a step's pass/fail can't express and you want the branch explicit in the job graph. Use step ExitStatus for simple success/failure branching, split flows for parallel independent paths, and external orchestration when the workflow spans many jobs or systems.
solid answer
~50 sA JobExecutionDecider is the right tool for in-job conditional routing where the decision is computed logic (counts, calendar, flags) rather than a step's success or failure, and where you want that branch visible and testable in the flow graph. If the branch is truly just 'did the step succeed or fail', prefer plain ExitStatus transitions or a StepExecutionListener returning a custom ExitStatus — no decider needed. For running independent paths concurrently, use a split flow with a TaskExecutor, not a decider (deciders are sequential choice, not parallelism). When the workflow spans multiple jobs, human approvals, long waits, or heterogeneous systems, a batch flow becomes a poor state machine; externalize to a workflow engine or an event-driven orchestrator and keep each Spring Batch job focused. The trade-off axes are: expressiveness vs. graph complexity, restart/idempotency guarantees, observability, and whether the logic belongs to the batch runtime at all.
go deeper
Recognize a decider is for choosing between paths, not for parallelism or waiting.
Contrast decider vs plain ExitStatus branching and know split is for concurrency.
Weigh explicitness, testability, and restart cost; extract nested flows to control complexity.
Draw the boundary between in-job data-processing routing (batch) and cross-job/long-running business orchestration (external), and set conventions that keep flow graphs restart-safe and observable.
## The decision space At a branch point in a batch job you have several mechanisms. Choosing well is a design-judgment question. ### 1. Plain step ExitStatus transitions `.on("COMPLETED").to(...)` / `.on("FAILED").to(...)`. Best when routing is exactly the step's outcome. Zero extra classes. Reach for this first; a decider here is over-engineering. ### 2. Custom ExitStatus via StepExecutionListener `afterStep()` returns `new ExitStatus("NO_DATA")`. Good when the routing signal is naturally computed *by the step that just ran* and is tightly coupled to its work. Downside: the step now knows about routing vocabulary; harder to reuse the step elsewhere. ### 3. JobExecutionDecider Best when: the decision needs inputs beyond one step's outcome (aggregate of several steps, job parameters, promoted context, calendar), you want the branch **explicit in the flow graph** (self-documenting state machine), and you want to **unit test** the decision independently of any step. Downside: another node in the graph; if overused the flow becomes a tangled state machine that's hard to reason about and restart correctly. ### 4. Split / parallel flows `.split(taskExecutor).add(flow1, flow2)`. This is for running independent branches **concurrently**, not for choosing one. Deciders and splits solve different problems; don't force a decider to fake parallelism. Note splits aggregate child statuses by severity into the parent FlowExecutionStatus. ### 5. Nested flows / modular flows Extract a subgraph into a reusable `Flow` bean and reference it. Use when the same conditional subprocess repeats, to tame graph complexity that a proliferation of deciders would create. ### 6. Externalized orchestration When the process spans **multiple jobs**, **human approval steps**, **long/indeterminate waits**, **retries across systems**, or **event triggers**, a Spring Batch flow is the wrong state machine — it's designed for chunk-oriented data processing within one job, not durable long-running business workflows. Options: an event-driven approach (messages/Spring Integration triggering separate jobs), a scheduler/orchestrator (Spring Cloud Data Flow for composed tasks), or a dedicated workflow engine. Keep each batch job cohesive and let the orchestrator own cross-job branching. ## Trade-off axes - **Expressiveness vs. complexity**: deciders add power but each branch is state you must maintain and test; too many turn the job into spaghetti. Prefer few, well-named branches. - **Restart & idempotency**: every branch multiplies restart scenarios. Deciders must be deterministic over persisted state (see restart gotchas). More branches = more restart paths to reason about. - **Observability**: an explicit decider/branch shows up in the flow and in logs; hidden routing inside a step's ExitStatus is less discoverable. Favor explicitness for operationally critical branches. - **Ownership of logic**: if the branching is *business process* logic that outlives this job, it probably belongs outside batch. If it's *data processing* routing, keep it in the job. - **Reusability**: a decider is a bean reusable across jobs; a listener-embedded ExitStatus couples routing to a step. ## Anti-patterns to call out - Using a decider to poll/wait for external readiness (blocks a batch thread; use event-driven triggering). - Encoding a whole business workflow as a deep decider graph (unmaintainable; externalize). - Deciders performing heavy I/O or mutations (decision points should be fast and side-effect-free). - Duplicating routing logic in both a step's ExitStatus and a decider. ## Summary heuristic Simple pass/fail -> ExitStatus. Computed choice inside one job, want it explicit/testable -> decider. Concurrency -> split. Repeated subprocess -> nested flow. Cross-job / long-running / human-in-the-loop -> external orchestration.
- A job needs to wait for an external approval before continuing. Is a decider that polls an approval table a good design?No. Polling blocks a batch thread and turns a data-processing job into a long-running workflow it isn't built for. Better: split into separate jobs and trigger the continuation via an event/message when approval arrives, or use a workflow engine to own the wait state.
- How do split flows differ from deciders in how they affect the resulting status?A decider picks one path and its returned FlowExecutionStatus drives the next transition. A split runs branches concurrently and aggregates their statuses by severity (worst wins) into the parent flow's status. One is choice; the other is parallel fan-out/join.
saying these in an interview costs you the question
- Using a decider to achieve parallelism instead of a split.
- Modeling an entire multi-job business workflow as a decider graph.
- Polling/waiting inside a decider, blocking batch threads.
- Adding a decider where a simple ExitStatus branch suffices.
- Treating a Spring Batch flow as a general-purpose durable workflow engine.