๐ฆ Ultimate Guide to Conditional Flow in Spring Batch (Deep Dive)
Spring Batch powers large-scale enterprise workloads and often needs to make **dynamic decisions**:
- Should Step B run after Step A?
- Should we retry, skip, or branch?
- Should we trigger an alternate flow on data failure?
- Should we route based on job parameters?
This is where **Conditional Flow** becomes essential. In this ultimate guide, we explore everything from basic transitions to complex multi-flow architectures.
๐ What is Conditional Flow?
Conditional flow lets your batch job **behave like a workflow**, choosing different paths based on:
- ExitStatus of a Step
- JobExecutionDecider with custom logic
- External data or job parameters
- Failures, skips, warning states
- Parallel execution (split flows)
// High-Level Flow Diagram
[Step A]
|
| COMPLETED
v
[Step B]
|
| FAILED
v
[Error Handler Step]
๐ฆ Maven Dependencies
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
๐งฑ Defining Steps
We define three simple tasklet steps for demonstration.
@Bean
public Step startStep() {
return stepBuilderFactory.get("startStep")
.tasklet((c, ctx) -> {
System.out.println("Running Start Step");
return RepeatStatus.FINISHED;
}).build();
}
@Bean
public Step successStep() {
return stepBuilderFactory.get("successStep")
.tasklet((c, ctx) -> {
System.out.println("Running Success Step");
return RepeatStatus.FINISHED;
}).build();
}
@Bean
public Step errorStep() {
return stepBuilderFactory.get("errorStep")
.tasklet((c, ctx) -> {
System.out.println("Running Error Step");
return RepeatStatus.FINISHED;
}).build();
}
๐ง Understanding Step ExitStatus vs FlowExecutionStatus
| Type | Used Where? | Purpose |
|---|---|---|
| ExitStatus | Step Execution | Determines if step succeeded (COMPLETED), failed (FAILED), etc. |
| FlowExecutionStatus | Deciders | Routes next step based on business logic (SUCCESS, ERROR, etc.) |
๐ง Creating a Custom JobExecutionDecider
@Component
public class RandomDecider implements JobExecutionDecider {
@Override
public FlowExecutionStatus decide(JobExecution jobExecution, StepExecution stepExecution) {
boolean status = new Random().nextBoolean();
return status
? new FlowExecutionStatus("SUCCESS")
: new FlowExecutionStatus("ERROR");
}
}
๐งฑ Basic Conditional Job Flow
@Bean
public Job conditionalJob() {
return jobBuilderFactory.get("conditionalJob")
.start(startStep())
.next(decider())
.on("SUCCESS").to(successStep())
.from(decider())
.on("ERROR").to(errorStep())
.end()
.build();
}
@Bean
public JobExecutionDecider decider() {
return new RandomDecider();
}
๐ Conditional Flow Based on ExitStatus
You can route steps purely based on ExitStatus without a decider:
@Bean
public Job exitStatusJob() {
return jobBuilderFactory.get("exitStatusJob")
.start(stepA())
.on("COMPLETED").to(stepB())
.from(stepA())
.on("FAILED").to(failureHandler())
.end()
.build();
}
๐งฉ Conditional Flow Based on Job Parameters
Often you want to route steps based on input parameters:
@Component
public class ParameterBasedDecider implements JobExecutionDecider {
@Override
public FlowExecutionStatus decide(JobExecution jobExecution, StepExecution stepExecution) {
String mode = jobExecution.getJobParameters().getString("mode");
return "FULL".equalsIgnoreCase(mode)
? new FlowExecutionStatus("FULL_LOAD")
: new FlowExecutionStatus("DELTA_LOAD");
}
}
๐ Splitting the Job Into Parallel Flows
Spring Batch supports **parallel flows** using `split()`.
@Bean
public Job parallelJob() {
Flow flow1 = new FlowBuilder<Flow>("flow1")
.start(step1())
.next(step2())
.build();
Flow flow2 = new FlowBuilder<Flow>("flow2")
.start(step3())
.build();
return jobBuilderFactory.get("parallelJob")
.start(flow1)
.split(new SimpleAsyncTaskExecutor()).add(flow2)
.end()
.build();
}
๐งฑ Nested Conditional Flows (Advanced)
@Bean
public Job nestedFlowJob() {
Flow innerFlow = new FlowBuilder<Flow>("innerFlow")
.start(stepA())
.next(stepB())
.build();
return jobBuilderFactory.get("nestedFlowJob")
.start(startStep())
.next(decider())
.on("RUN_INNER").to(innerFlow)
.from(decider())
.on("SKIP_INNER").to(stepC())
.end()
.build();
}
๐งจ Common Mistakes & How to Avoid Them
- ❌ Using `ExitStatus` when custom logic is required → Use Decider
- ❌ Forgetting `.end()` after multi-step transitions
- ❌ Reusing the same decider instance inside multiple flows
- ❌ Complex routing inside steps (keep business logic out of step)
๐ง Real-World Use Cases
- ๐ **Reprocessing only failed records** (using param-based decider)
- ๐ **Full load vs delta load routing**
- ๐ **Switching flow when a file is missing**
- ๐ **Different flows for weekday/weekend processing**
๐ Trigger Job Using REST
@RestController
@RequestMapping("/jobs")
public class JobController {
@Autowired private JobLauncher launcher;
@Autowired private Job conditionalJob;
@GetMapping("/run")
public String run() throws Exception {
JobParameters params = new JobParametersBuilder()
.addLong("time", System.currentTimeMillis())
.addString("mode", "FULL")
.toJobParameters();
launcher.run(conditionalJob, params);
return "Job Started!";
}
}
✅ Summary
- Conditional Flow makes Spring Batch behave like a workflow engine.
- Use ExitStatus for simple transitions.
- Use JobExecutionDecider for complex decisions.
- Parallel and nested flows help solve advanced scenarios.
- Job parameters allow dynamic routing at runtime.
Subscribe to our YouTube channel: Spring Java Lab for practical Spring Batch tutorials!
๐ Related Spring Batch Tutorials
Explore related Spring Batch concepts to better understand job flows, step execution, error handling, and performance optimization.
๐งฑ Spring Batch Core Components
Understand Job, Step, ItemReader, ItemProcessor, and ItemWriter — the foundation required before implementing conditional job flows.
⚙️ Spring Batch Tasklet
Learn when to use Tasklet-based steps versus chunk-oriented processing in conditional job execution.
๐งต Multithreaded Step in Spring Batch
Improve job performance by executing steps in parallel after branching using conditional flows.
๐ Spring Batch Retry Mechanism
Handle transient failures gracefully in conditional job paths using retry and backoff strategies.
๐ซ Skip Policy & Error Handling
Learn how skip policies work alongside conditional flows to control execution when records fail.
๐ JobExecutionListener
Track job status, step outcomes, and decision results during conditional job execution.