Table of Contents
- Prerequisites for Spring Batch Multi-Step Job
- Deep Dive into Spring Batch Multi-Step Job Concept
- Step-by-Step Guide to Creating a Spring Batch Multi-Step Job
- Full Example of a Spring Batch Multi-Step Job with Conditional Flow
- Common Mistakes to Avoid in Spring Batch Multi-Step Job
- Mistake 1: Incorrect Step Configuration
- Mistake 2: Insufficient Error Handling
- Production Tips for Spring Batch Multi-Step Job
- Testing Spring Batch Multi-Step Job with Conditional Flow
- Key Takeaways for Implementing Spring Batch Multi-Step Job
- Configuring Conditional Flow in Spring Batch Multi-Step Job
Prerequisites for Spring Batch Multi-Step Job
To start with Spring Batch multi-step job with conditional flow, you need to have the required dependencies in your project. The Spring Boot version should be at least 2.5.0, and the Java version should be at least 11. You also need to have the Spring Batch dependency in your project.
The spring-boot-starter-batch dependency includes all the necessary dependencies for Spring Batch. You can add this dependency to your pom.xml file if you are using Maven, or to your build.gradle file if you are using Gradle. For more information on Spring Boot project structure, you can refer to our previous article.
Here is an example of how you can add the spring-boot-starter-batch dependency to your pom.xml file:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-batch</artifactId> </dependency>
You also need to have a database to store the batch job metadata. You can use any database that is supported by Spring Batch, such as MySQL or PostgreSQL.
Here is an example of a simple BatchConfig class that configures the Spring Batch job:
package com.example.batch;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableBatchProcessing
public class BatchConfig {
@Autowired
private JobBuilderFactory jobBuilderFactory;
@Autowired
private StepBuilderFactory stepBuilderFactory;
// Define a job that has one step
@Bean
public Job job() {
// Create a new job with a unique id
return jobBuilderFactory.get("job")
.incrementer(new RunIdIncrementer())
.start(step())
.build();
}
// Define a step that does something
@Bean
public Step step() {
// Create a new step that does something
return stepBuilderFactory.get("step")
.tasklet((contribution, chunkContext) -> {
// Do something here, for example, log a message
System.out.println("Doing something...");
return RepeatStatus.FINISHED;
})
.build();
}
}
When you run this job, you should see the message “Doing something…” printed to the console. The expected output is:
Doing something...
For more information on Spring Batch tutorial, you can refer to our previous article.
Deep Dive into Spring Batch Multi-Step Job Concept
A **job** in Spring Batch is a sequence of **steps**, where each **step** represents a single unit of work. The **flow** of a job determines the order in which these **steps** are executed. To configure a **job**, you need to define a **job** bean and specify the **steps** it will execute. For more information on **job** configuration, refer to our article on Configuring a Spring Batch Job.
The **flow** of a **job** can be controlled using **decision** elements, which allow you to execute different **steps** based on certain conditions. This is useful when you need to handle different scenarios or error conditions. A **decision** element is typically used in conjunction with a **step** to determine the next course of action.
To illustrate this concept, consider a **job** that reads data from a file, processes it, and writes it to a database. If the file is empty, you may want to skip the processing **step** and proceed directly to the next **step**. You can achieve this by using a **decision** element to check the file size and execute the processing **step** only if it is not empty.
Here is an example of how you can configure a **job** with a conditional **flow**:
package com.example.springbatch;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.job.flow.JobExecutionDecider;
import org.springframework.batch.core.job.flow.FlowExecutionStatus;
import org.springframework.batch.core.job.flow.JobExecutionDecider;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableBatchProcessing
public class JobConfig {
@Autowired
private JobBuilderFactory jobBuilderFactory;
@Autowired
private StepBuilderFactory stepBuilderFactory;
@Bean
public Job job() {
return jobBuilderFactory.get("job")
.start(step1())
.next(decider())
.from(decider()).on("PROCEED").to(step2())
.from(decider()).on("SKIP").to(step3())
.end()
.build();
}
@Bean
public Step step1() {
return stepBuilderFactory.get("step1")
.tasklet((contribution, chunkContext) -> {
// Read data from file
return RepeatStatus.FINISHED;
})
.build();
}
@Bean
public Step step2() {
return stepBuilderFactory.get("step2")
.tasklet((contribution, chunkContext) -> {
// Process data
return RepeatStatus.FINISHED;
})
.build();
}
@Bean
public Step step3() {
return stepBuilderFactory.get("step3")
.tasklet((contribution, chunkContext) -> {
// Write data to database
return RepeatStatus.FINISHED;
})
.build();
}
@Bean
public JobExecutionDecider decider() {
return new JobExecutionDecider() {
@Override
public FlowExecutionStatus decide(JobExecution jobExecution, StepExecution stepExecution) {
// Check file size and return "PROCEED" if not empty, "SKIP" otherwise
return new FlowExecutionStatus("PROCEED");
}
};
}
}
When you run this **job**, it will execute **step1**, then use the **decider** to determine whether to execute **step2** or **step3**. The expected output will depend on the file size and the logic implemented in the **decider**.
Job execution complete
For more information on **step** configuration, refer to our article on Configuring a Spring Batch Step.
Step-by-Step Guide to Creating a Spring Batch Multi-Step Job
To create a Spring Batch multi-step job with conditional flow, you need to define a Job bean that contains multiple Step beans. Each Step can have its own ItemReader, ItemProcessor, and ItemWriter.
The conditional flow is achieved using a JobExecutionDecider that determines the next step based on the previous step’s execution result.
For more information on Job configuration, visit our Spring Batch Job Configuration tutorial.
A Job can have multiple Step beans, each with its own ItemReader, ItemProcessor, and ItemWriter.
The ItemReader reads data from a source, the ItemProcessor processes the data, and the ItemWriter writes the processed data to a destination.
The conditional flow is achieved using a JobExecutionDecider that determines the next step based on the previous step’s execution result.
Here is an example of a Spring Batch multi-step job with conditional flow:
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.job.flow.JobExecutionDecider;
import org.springframework.batch.core.job.flow.FlowExecutionStatus;
import org.springframework.batch.core.job.flow.FlowExecutor;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.support.ListItemReader;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
import java.util.List;
@Configuration
public class MultiStepJobConfig {
@Autowired
private JobBuilderFactory jobBuilderFactory;
@Autowired
private StepBuilderFactory stepBuilderFactory;
@Bean
public Job multiStepJob() {
return jobBuilderFactory.get("multiStepJob")
.start(step1())
.next(decider())
.from(decider()).on("FAILED").to(step2())
.from(decider()).on("COMPLETED").to(step3())
.end()
.build();
}
@Bean
public Step step1() {
// Why: Define the first step with an ItemReader and ItemWriter
return stepBuilderFactory.get("step1")
.chunk(1)
.reader(itemReader())
.writer(itemWriter())
.build();
}
@Bean
public ItemReader itemReader() {
// Why: Create a simple ItemReader that reads from a list
List items = new ArrayList<>();
items.add("Item1");
items.add("Item2");
return new ListItemReader<>(items);
}
@Bean
public ItemWriter itemWriter() {
// Why: Create a simple ItemWriter that prints the items
return items -> items.forEach(System.out::println);
}
@Bean
public Step step2() {
// Why: Define the second step with an ItemReader and ItemWriter
return stepBuilderFactory.get("step2")
.chunk(1)
.reader(itemReader())
.writer(itemWriter())
.build();
}
@Bean
public Step step3() {
// Why: Define the third step with an ItemReader and ItemWriter
return stepBuilderFactory.get("step3")
.chunk(1)
.reader(itemReader())
.writer(itemWriter())
.build();
}
@Bean
public JobExecutionDecider decider() {
// Why: Create a decider that determines the next step based on the previous step's execution result
return (jobExecution, stepExecution) -> {
if (stepExecution.getStatus() == BatchStatus.FAILED) {
return new FlowExecutionStatus("FAILED");
} else {
return new FlowExecutionStatus("
Full Example of a Spring Batch Multi-Step Job with Conditional Flow
The Spring Batch framework provides a robust way to handle batch processing with conditional flow. To demonstrate this, we will create a job that reads data from a database, processes it, and then writes it to a file. The job will have multiple steps and use decision elements to control the flow. The first step in our job will be to read data from a database using theJdbcCursorItemReader. We will then process the data using a ItemProcessor and write it to a file using the FlatFileItemWriter. The job will use a decision element to determine which step to execute next based on the outcome of the previous step. For more information on ItemReader and ItemWriter interfaces, visit our Spring Batch ItemReader and ItemWriter tutorial.
Here is the complete code example:
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.job.flow.JobExecutionDecider;
import org.springframework.batch.item.database.JdbcCursorItemReader;
import org.springframework.batch.item.file.FlatFileItemWriter;
import org.springframework.batch.item.support.CompositeItemWriter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
@Configuration
@EnableBatchProcessing
public class BatchConfig {
@Autowired
private JobBuilderFactory jobBuilderFactory;
@Autowired
private StepBuilderFactory stepBuilderFactory;
@Bean
public Job job() {
return jobBuilderFactory.get("job")
.start(step1())
.next(decider())
.from(decider())
.on("FAILED")
.to(step2())
.from(decider())
.on("COMPLETED")
.to(step3())
.end()
.build();
}
@Bean
public Step step1() {
// Read data from database
return stepBuilderFactory.get("step1")
.chunk(10)
.reader(reader())
.processor(processor())
.writer(writer())
.build();
}
@Bean
public JdbcCursorItemReader<String> reader() {
// Configure reader to read from database
JdbcCursorItemReader<String> reader = new JdbcCursorItemReader<>();
reader.setDataSource(dataSource());
reader.setSql("SELECT * FROM table");
return reader;
}
@Bean
public FlatFileItemWriter<String> writer() {
// Configure writer to write to file
FlatFileItemWriter<String> writer = new FlatFileItemWriter<>();
writer.setResource(new ClassPathResource("output.txt"));
return writer;
}
@Bean
public JobExecutionDecider decider() {
// Decide which step to execute next
return new MyDecider();
}
}
The expected output of this job will be:
Data written to output.txt file
For further reading on Spring Batch and its features, visit our Spring Batch Tutorial and Spring Batch Configuration pages.
Common Mistakes to Avoid in Spring Batch Multi-Step Job
When designing a Spring Batch multi-step job, it is crucial to understand the conditional flow and how to handle potential errors. A common pitfall is not properly configuring the job repository, which can lead to issues with job execution and restartability. For more information on configuring the job repository, see our article on Configuring the Job Repository in Spring Batch.
Mistake 1: Incorrect Step Configuration
A common mistake is incorrectly configuring the Step beans, which can lead to job failures. The following code example demonstrates an incorrect configuration:
public class IncorrectStepConfig {
// WRONG: missing @Bean annotation
public Step incorrectStep() {
// ...
}
}
This will result in a NoBeanDefinitionFoundException exception. The correct configuration is:
public class CorrectStepConfig {
@Bean
public Step correctStep() {
// configure the step correctly
// we need to specify the @Bean annotation to register the step as a Spring bean
return Steps.get("correctStep")
.tasklet(new Tasklet() {
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
// tasklet implementation
return RepeatStatus.FINISHED;
}
})
.build();
}
}
Expected output:
Job execution complete
Mistake 2: Insufficient Error Handling
Another common mistake is not implementing sufficient error handling mechanisms, which can lead to job failures and make it difficult to diagnose issues. For more information on error handling in Spring Batch, see our article on Error Handling in Spring Batch. The following code example demonstrates an incorrect implementation:
public class IncorrectErrorHandler {
// WRONG: not handling potential exceptions
public void handleException() {
// ...
}
}
This will result in an unhandled Exception being thrown. The correct implementation is:
public class CorrectErrorHandler {
@Bean
public JobExecutionListener listener() {
return new JobExecutionListener() {
@Override
public void beforeJob(JobExecution jobExecution) {
// ...
}
@Override
public void afterJob(JobExecution jobExecution) {
// handle potential exceptions
if (jobExecution.getStatus() == BatchStatus.FAILED) {
// log the exception and perform any necessary cleanup
}
}
};
}
}
By following these guidelines and avoiding common mistakes, you can ensure that your Spring Batch multi-step job is properly configured and can handle potential errors. For further reading on Spring Batch and its features, see our article on Introduction to Spring Batch.
Production Tips for Spring Batch Multi-Step Job
When deploying Spring Batch jobs in production, it is essential to consider best practices for monitoring and maintaining these jobs. One crucial aspect is implementing conditional flow to handle different scenarios that may arise during job execution.
Production tip: Use
JobExecutionListenerto monitor job execution and take necessary actions in case of failures or other events.
To achieve this, you can use the JobExecutionListener interface, which provides methods to handle job execution events. For example, you can use the afterJob method to send notifications or update job status.
public class JobExecutionListenerImpl implements JobExecutionListener {
@Override
public void beforeJob(JobExecution jobExecution) {
// Initialize job execution context
}
@Override
public void afterJob(JobExecution jobExecution) {
// Send notification or update job status
if (jobExecution.getStatus() == BatchStatus.FAILED) {
// Handle job failure
}
}
}
For more information on implementing job listeners, refer to our article on Spring Batch Job Listeners.
Production tip: Use logging to track job execution and identify potential issues.
To demonstrate the usage of JobExecutionListener, consider the following example:
package com.example.springbatch;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecutionListener;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableBatchProcessing
public class BatchConfig {
@Autowired
private JobBuilderFactory jobBuilderFactory;
@Autowired
private StepBuilderFactory stepBuilderFactory;
@Bean
public Job job() {
return jobBuilderFactory.get("job")
.incrementer(new RunIdIncrementer())
.listener(new JobExecutionListenerImpl()) // Register job execution listener
.start(step())
.end()
.build();
}
@Bean
public Step step() {
return stepBuilderFactory.get("step")
.tasklet((contribution, chunkContext) -> {
// Tasklet implementation
return RepeatStatus.FINISHED;
})
.build();
}
}
The expected output will be:
Job execution started Job execution completed with status: COMPLETED
By following these best practices and using Spring Batch features such as JobExecutionListener and logging, you can ensure reliable and maintainable batch job execution in production environments. For further reading on Spring Batch and its features, refer to our article on Spring Batch Tutorial.
Testing Spring Batch Multi-Step Job with Conditional Flow
When testing **Spring Batch** jobs, it's essential to cover both unit testing and integration testing strategies. Unit testing focuses on individual components, such as **ItemReader** and **ItemWriter**, while integration testing verifies the entire job flow. For a multi-step job with conditional flow, testing becomes more complex due to the dynamic nature of the job execution.
To unit test a **Job**, you can use a testing framework like **JUnit** and mock the required dependencies. For example, you can test the **JobLauncher** to verify that the job is launched correctly. For more information on setting up a **Spring Batch** project, refer to our article on [Configuring Spring Batch](/configuring-spring-batch).
When writing integration tests for a **Spring Batch** job, you should test the entire job flow, including the conditional flow. You can use the **JobLauncherTestUtils** class to launch the job and verify the expected output.
public class JobIntegrationTest {
@Autowired
private JobLauncherTestUtils jobLauncherTestUtils;
@Test
public void testJob() throws Exception {
// Launch the job
JobExecution execution = jobLauncherTestUtils.launchJob();
// Verify the job execution status
assertEquals(BatchStatus.COMPLETED, execution.getStatus());
}
}
The expected output of the above test would be:
Job execution status: COMPLETED
To test the conditional flow, you can use a **JobExecutionListener** to verify that the expected steps are executed. For further reading on **Spring Batch** listeners, see our article on [Using Spring Batch Listeners](/using-spring-batch-listeners).
In addition to testing the job flow, you should also test the **ItemProcessor** and **ItemWriter** components to ensure they are working correctly. You can use a testing framework like **Mockito** to mock the required dependencies and verify the expected behavior. By covering both unit testing and integration testing, you can ensure that your **Spring Batch** job is working correctly and reliably.
Key Takeaways for Implementing Spring Batch Multi-Step Job
When implementing a **Spring Batch** multi-step job, it is essential to understand the concept of **job execution** and **step execution**. A job consists of one or more steps, and each step represents a single unit of work. The **JobBuilderFactory** and **StepBuilderFactory** are used to create jobs and steps, respectively.
To implement a multi-step job with conditional flow, you need to use the **JobExecutionDecider** interface, which allows you to decide the next step based on the previous step's execution result. For more information on **job configuration**, refer to our article on [Configuring a Spring Batch Job](/configuring-spring-batch-job).
The following example demonstrates a simple multi-step job with conditional flow:
package com.example.springbatch;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.job.flow.JobExecutionDecider;
import org.springframework.batch.core.job.flow.FlowExecutionStatus;
import org.springframework.batch.core.job.flow.JobExecutionDecider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableBatchProcessing
public class MultiStepJobConfig {
@Autowired
private JobBuilderFactory jobBuilderFactory;
@Autowired
private StepBuilderFactory stepBuilderFactory;
@Bean
public Job multiStepJob() {
// Create a job with two steps
return jobBuilderFactory.get("multiStepJob")
.start(step1())
.next(decider())
.from(decider())
.on("FAILED")
.to(step2())
.from(decider())
.on("COMPLETED")
.to(step3())
.end()
.build();
}
@Bean
public Step step1() {
// Step 1: Read data from a file
return stepBuilderFactory.get("step1")
.tasklet((contribution, chunkContext) -> {
// Read data from a file
System.out.println("Reading data from a file");
return RepeatStatus.FINISHED;
})
.build();
}
@Bean
public JobExecutionDecider decider() {
// Decider to decide the next step based on the previous step's execution result
return new MyDecider();
}
@Bean
public Step step2() {
// Step 2: Process data
return stepBuilderFactory.get("step2")
.tasklet((contribution, chunkContext) -> {
// Process data
System.out.println("Processing data");
return RepeatStatus.FINISHED;
})
.build();
}
@Bean
public Step step3() {
// Step 3: Write data to a file
return stepBuilderFactory.get("step3")
.tasklet((contribution, chunkContext) -> {
// Write data to a file
System.out.println("Writing data to a file");
return RepeatStatus.FINISHED;
})
.build();
}
}
class MyDecider implements JobExecutionDecider {
@Override
public FlowExecutionStatus decide(JobExecution jobExecution, StepExecution stepExecution) {
// Decide the next step based on the previous step's execution result
if (stepExecution.getStatus() == BatchStatus.FAILED) {
return new FlowExecutionStatus("FAILED");
} else {
return new FlowExecutionStatus("COMPLETED");
}
}
}
The expected output of this job will be:
Reading data from a file Processing data Writing data to a file
For further reading on **step execution listeners**, refer to our article on [Using Step Execution Listeners in Spring Batch](/using-step-execution-listeners-spring-batch).
Configuring Conditional Flow in Spring Batch Multi-Step Job
To configure conditional flow in a Spring Batch multi-step job, you need to use the JobExecutionDecider interface. This interface allows you to decide the next step in the job flow based on certain conditions. The decide method of this interface returns a FlowExecutionStatus object that determines the next step.
The conditional flow is typically used to handle different scenarios based on the outcome of a previous step. For example, if a step fails, you may want to skip to a recovery step. To achieve this, you can use the JobExecutionDecider to evaluate the exit status of the previous step and return a FlowExecutionStatus that corresponds to the next step.
Here is an example of how to use the JobExecutionDecider to configure conditional flow:
public class MyJobExecutionDecider implements JobExecutionDecider {
@Override
public FlowExecutionStatus decide(JobExecution jobExecution, StepExecution stepExecution) {
// Check the exit status of the previous step
String exitStatus = stepExecution.getExitStatus().getExitCode();
// If the previous step failed, return a FlowExecutionStatus that corresponds to the recovery step
if (exitStatus.equals("FAILED")) {
return new FlowExecutionStatus("RECOVERY");
} else {
// If the previous step was successful, return a FlowExecutionStatus that corresponds to the next step
return new FlowExecutionStatus("SUCCESS");
}
}
}
For more information on job execution and how to handle different scenarios, you can refer to our previous article on job execution in Spring Batch. The expected output of the above code will be:
RECOVERY
if the previous step failed, or:
SUCCESS
if the previous step was successful. You can use this JobExecutionDecider in your job configuration to configure the conditional flow.
spring-batch-examples — Clone, Star & Contribute

Leave a Reply