Spring Batch One Reader with Multiple Processors and Writers
1. Introduction
Spring Batch is a lightweight, comprehensive batch processing framework designed for robust, scalable, and repeatable data-processing jobs in Java. I will create a Spring Boot project that configures one reader multiple processors writers in a Spring Batch framework.
2. Setup
I will create a Spring Boot project and configure a Spring Batch step that reads a list of person data from the persons.csv file, then processes data differently based on the process type, and writes the processed data into a text file and a database table.
2.1 Create a Gradle Project with Spring Batch
In this step, I will create a Gradle project along with Spring Batch, Lombok, H2 Database, Spring Data JPA, and JDK17 via Spring Starter. More details can be found here.
build.gradle
plugins {
id 'java'
id 'org.springframework.boot' version '3.5.3'
id 'io.spring.dependency-management' version '1.1.7'
}
group = 'org.zheng.demo.batch'
version = '0.0.1-SNAPSHOT'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-batch'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.batch:spring-batch-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
tasks.named('test') {
useJUnitPlatform()
}
2.2 Generated Spring Boot Application
No change is needed for the generated Spring Boot application. Here is the generated ManyProcessorsApplication.java.
ManyProcessorsApplication.java
package org.zheng.demo.batch;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ManyProcessorsApplication {
public static void main(String[] args) {
SpringApplication.run(ManyProcessorsApplication.class, args);
}
}
2.3 Configure Spring Batch Properties
In this step, I will update application.properties to enable the Spring Batch process.
application.properties
spring.application.name=manyProcessors spring.datasource.url=jdbc:h2:mem:testdb spring.datasource.driver-class-name=org.h2.Driver spring.datasource.username=sa spring.datasource.password= spring.jpa.database-platform=org.hibernate.dialect.H2Dialect # Make sure this is explicitly turned on spring.sql.init.mode=always spring.sql.init.schema-locations=classpath:schema-h2.sql,classpath:schema-all.sql logging.level.org.springframework.batch=DEBUG logging.level.org.springframework.boot.autoconfigure.batch=DEBUG
3. Batch Processing Persons
3.1 Person Data
In this step, I will create a Person.java that includes four data members: id, firstName, lastName, and processType. Note: these data members will be used in step 5.2 when mapping to database columns.
Person.java
package org.zheng.demo.batch.data;
import lombok.Data;
@Data
public class Person {
private Long id;
private String firstName;
private String lastName;
private String processType;
}
3.2 Person DDL
In this step, I will create a schema-all.sql file that defines the T_PERSON table. T_PERSON has four columns: id, first_Name, last_Name, and process_Type. The dbWriter defined in step 5.2 writes to this table.
schedma-all.sql
DROP table T_PERSON if exists; create table T_PERSON ( id bigint auto_increment, first_name vARCHAR(40), last_name VARCHAR(60), process_type VARCHAR(1) );
3.3 Persons.csv File
In this step, I will create a persons.csv file that includes the source data for the batch process. The reader configured in step 5.2 reads data from it. The Spring Batch processor defined in step 4.4 routes to a different processor based on process_type.
persons.csv
id,firstName,lastName,processType 1,Mary,Zheng,A 2,Bob,Joe,B 3,John,Doe,C 4,Andy,Johnson,A 5,Ted,Bear,C 6,Grace,Cheng,B 7,April,Green,A
- There are three values for the
processType: A, B, and C. Each will have its processor defined in step 4.
4. Multiple Person Processors
4.1 TypeAProcessor
In this step, I will create a TypeAProcessor.java to process the Person object when the processType is 'A'. It converts firstName and lastName to uppercase.
TypeAProcessor.java
package org.zheng.demo.batch.step;
import org.springframework.batch.item.ItemProcessor;
import org.zheng.demo.batch.data.Person;
public class TypeAProcessor implements ItemProcessor<Person, Person> {
@Override
public Person process(Person item) throws Exception {
item.setFirstName(item.getFirstName().toUpperCase());
item.setLastName(item.getLastName().toUpperCase());
return item;
}
}
4.2 TypeBProcessor
In this step, I will create a TypeBProcessor.java to process the Person object when the processType is 'B'. It converts firstName and lastName to lowercase.
TypeBProcessor.java
package org.zheng.demo.batch.step;
import org.springframework.batch.item.ItemProcessor;
import org.zheng.demo.batch.data.Person;
public class TypeBProcessor implements ItemProcessor<Person, Person> {
@Override
public Person process(Person item) throws Exception {
item.setFirstName(item.getFirstName().toLowerCase());
item.setLastName(item.getLastName().toLowerCase());
return item;
}
}
4.3 TypeCProcessor
In this step, I will create a TypeCProcessor.java to process the Person object when the processType is 'C'. It converts firstName to lowercase and lastName to uppercase.
TypeCProcessor.java
package org.zheng.demo.batch.step;
import org.springframework.batch.item.ItemProcessor;
import org.zheng.demo.batch.data.Person;
public class TypeCProcessor implements ItemProcessor<Person, Person> {
@Override
public Person process(Person item) throws Exception {
item.setFirstName(item.getFirstName().toLowerCase());
item.setLastName(item.getLastName().toUpperCase());
return item;
}
}
4.4 PersonProcessorRouter
In this step, I will create a PersonProcessorRouter.java that routes to a different processor based on its processType.
PersonProcessorRouter.java
package org.zheng.demo.batch.step;
import org.springframework.batch.item.ItemProcessor;
import org.zheng.demo.batch.data.Person;
public class PersonProcessorRouter implements ItemProcessor<Person, Person> {
private final TypeAProcessor processorA;
private final TypeBProcessor processorB;
private final TypeCProcessor processorC;
@Override
public Person process(Person item) throws Exception {
switch (item.getProcessType()) {
case "A":
return processorA.process(item);
case "B":
return processorB.process(item);
case "C":
return processorC.process(item);
default:
break;
}
return item;
}
public PersonProcessorRouter(TypeAProcessor processorA, TypeBProcessor processorB, TypeCProcessor processorC) {
super();
this.processorA = processorA;
this.processorB = processorB;
this.processorC = processorC;
}
}
- Line 13-19: routes to a different processor based on the
processType. This can be updated easily when adding newprocessTypes.
5. Spring Batch Configuration
Spring Batch framework provides the necessary beans to configure a Job and Step. In this step, I will create a JobCompletionListener class and configure a fileReader, dbWriter, fileWriter, compositeWriter, multipleProcessor, importPersonsJob, step, and runJob.
5.1 JobCompletionListener
In this step, I will create a JobCompletionListener.java that implements JobExecutionListener and overrides the afterJob method that verifies the persons are saved in the T_PERSON table.
JobCompletionListener.java
package org.zheng.demo.batch.config;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobExecutionListener;
import org.springframework.jdbc.core.DataClassRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.zheng.demo.batch.data.Person;
@Component
public class JobCompletionListener implements JobExecutionListener {
private static final String SELECT_SQL = "SELECT id, first_name, last_name, process_type from T_PERSON";
private final JdbcTemplate jdbcTemplate;
public JobCompletionListener(JdbcTemplate jdbcTemplate) {
super();
this.jdbcTemplate = jdbcTemplate;
}
@Override
public void afterJob(JobExecution jobEx) {
if (BatchStatus.COMPLETED.equals(jobEx.getStatus())) {
System.out.println("Job finished! Verified the Data.");
jdbcTemplate.query(SELECT_SQL, new DataClassRowMapper<>(Person.class))
.forEach(person -> System.out.printf("processed person: %s\n", person.toString()));
}
}
}
- Line 14: defines the
SELECT_SQLto retrieve the data from theT_PERSONtable. - Line 27: both
firstNameandlastNameare transformed based on theprocessType.
5.2 One Reader Multiple Processors Writers Configuration
In this step, I will create a SpringBatchConfig.java class to configure the following Spring Batch Beans:
FlatFileItemReader<Person> fileReaderreads data from thepersons.csvfile.FlatFileItemWriter<Person> fileWriterwrites to theoutput/processed_persons.txtfile.JdbcBatchItemWriter<Person> dbWriterwrites to the database byINSERT_SQL.ItemWriter<Person> compositeWritercontains bothfileWriteranddbWriter.PersonProcessorRouter multipleProcessorroutes toTypeAProcessor,TypeBProcessor, orTypeCProcessor.JobimportPersonsJobconfigures aJobwithjobListenerand starts with thestep.Step stepconfigures a step withfileReader,multipleProcessor,compositeWriter, andchunksize 4.runJobstarts theimportPersonsJobjob.
SpringBatchConfig.java
package org.zheng.demo.batch.config;
import java.util.List;
import javax.sql.DataSource;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.job.builder.JobBuilder;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.database.JdbcBatchItemWriter;
import org.springframework.batch.item.database.builder.JdbcBatchItemWriterBuilder;
import org.springframework.batch.item.file.FlatFileItemReader;
import org.springframework.batch.item.file.FlatFileItemWriter;
import org.springframework.batch.item.file.builder.FlatFileItemReaderBuilder;
import org.springframework.batch.item.file.builder.FlatFileItemWriterBuilder;
import org.springframework.batch.item.support.CompositeItemWriter;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.transaction.PlatformTransactionManager;
import org.zheng.demo.batch.data.Person;
import org.zheng.demo.batch.step.PersonProcessorRouter;
import org.zheng.demo.batch.step.TypeAProcessor;
import org.zheng.demo.batch.step.TypeBProcessor;
import org.zheng.demo.batch.step.TypeCProcessor;
@Configuration
@EnableBatchProcessing
public class SpringBatchConfig {
private static final String OUTPUT_PERSON_FILE = "output/processed_persons.txt";
private static final String PROCESS_TYPE = "processType";
private static final String LAST_NAME = "lastName";
private static final String FIRST_NAME = "firstName";
private static final String PERSON_ID = "id";
private static final String INSERT_SQL = "INSERT INTO T_PERSON (id, first_name, last_name, process_type) VALUES (:id, :firstName, :lastName, :processType) ";
@Bean
public FlatFileItemReader<Person> fileReader() {
return new FlatFileItemReaderBuilder<Person>().name("personItemReader")
.resource(new ClassPathResource("persons.csv")).delimited()
.names(PERSON_ID, FIRST_NAME, LAST_NAME, PROCESS_TYPE).linesToSkip(1).targetType(Person.class).build();
}
@Bean
public FlatFileItemWriter<Person> fileWriter() {
return new FlatFileItemWriterBuilder<Person>().name("personItemWriter")
.resource(new FileSystemResource(OUTPUT_PERSON_FILE)).delimited().delimiter(",")
.names(PERSON_ID, FIRST_NAME, LAST_NAME, PROCESS_TYPE).build();
}
@Bean
public JdbcBatchItemWriter<Person> dbWriter(DataSource dataSource) {
return new JdbcBatchItemWriterBuilder<Person>().sql(INSERT_SQL).dataSource(dataSource).beanMapped().build();
}
@Bean
public ItemWriter<Person> compositeWriter(JdbcBatchItemWriter<Person> dbWriter,
FlatFileItemWriter<Person> fileWriter) {
CompositeItemWriter<Person> writer = new CompositeItemWriter<>();
writer.setDelegates(List.of(dbWriter, fileWriter));
return writer;
}
@Bean
public PersonProcessorRouter multipleProcessor() {
return new PersonProcessorRouter(new TypeAProcessor(), new TypeBProcessor(), new TypeCProcessor());
}
@Bean
public Job importPersonsJob(JobRepository jobRepo, Step step, JobCompletionListener jobListener) {
return new JobBuilder("importPersonsJob", jobRepo).listener(jobListener).start(step).build();
}
@Bean
public Step step(JobRepository jobRepo, PlatformTransactionManager tm, JdbcBatchItemWriter<Person> dbWriter,
FlatFileItemWriter<Person> fileWriter) {
return new StepBuilder("step", jobRepo).<Person, Person>chunk(4, tm).reader(fileReader()).processor(multipleProcessor())
.writer(compositeWriter(dbWriter, fileWriter)).build();
}
@Bean
public CommandLineRunner runJob(JobLauncher jobLauncher, Job importPersonsJob) {
return args -> {
System.out.println("🚀 Launching importPersonsJob...");
jobLauncher.run(importPersonsJob,
new JobParametersBuilder().addLong("time", System.currentTimeMillis()).toJobParameters());
System.out.println("🚀 Done importPersonsJob...");
};
}
}
- Line 47-50:
fileReaderreads data from thepersons.csvfile. - Line 66-69:
compositeWritercontains bothfileWriteranddbWriter. - Line 75:
multipleProcessorroutes toTypeAProcessor,TypeBProcessor, orTypeCProcessor. - Line 84-87:
stepconfigures a step withfileReader,multipleProcessor,compositeWriter, andchunksize 4.
5.3 Demo One Reader Multiple Processors Writers
Run the Spring Boot Application and capture the server log.
Server Log
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.5.3)
2025-07-05T21:30:25.057-05:00 INFO 28592 --- [manyProcessors] [ main] o.z.d.batch.ManyProcessorsApplication : Starting ManyProcessorsApplication using Java 17.0.11 with PID 28592 (C:\MaryTools\workspace\manyProcessors\bin\main started by azpm0 in C:\MaryTools\workspace\manyProcessors)
2025-07-05T21:30:25.060-05:00 INFO 28592 --- [manyProcessors] [ main] o.z.d.batch.ManyProcessorsApplication : No active profile set, falling back to 1 default profile: "default"
2025-07-05T21:30:25.459-05:00 INFO 28592 --- [manyProcessors] [ main] o.s.b.c.c.annotation.BatchRegistrar : Finished Spring Batch infrastructure beans configuration in 7 ms.
2025-07-05T21:30:25.607-05:00 INFO 28592 --- [manyProcessors] [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2025-07-05T21:30:25.624-05:00 INFO 28592 --- [manyProcessors] [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 13 ms. Found 0 JPA repository interfaces.
2025-07-05T21:30:25.928-05:00 INFO 28592 --- [manyProcessors] [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2025-07-05T21:30:26.159-05:00 INFO 28592 --- [manyProcessors] [ main] com.zaxxer.hikari.pool.HikariPool : HikariPool-1 - Added connection conn0: url=jdbc:h2:mem:testdb user=SA
2025-07-05T21:30:26.162-05:00 INFO 28592 --- [manyProcessors] [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
2025-07-05T21:30:26.308-05:00 INFO 28592 --- [manyProcessors] [ main] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [name: default]
2025-07-05T21:30:26.378-05:00 INFO 28592 --- [manyProcessors] [ main] org.hibernate.Version : HHH000412: Hibernate ORM core version 6.6.18.Final
2025-07-05T21:30:26.421-05:00 INFO 28592 --- [manyProcessors] [ main] o.h.c.internal.RegionFactoryInitiator : HHH000026: Second-level cache disabled
2025-07-05T21:30:26.717-05:00 INFO 28592 --- [manyProcessors] [ main] o.s.o.j.p.SpringPersistenceUnitInfo : No LoadTimeWeaver setup: ignoring JPA class transformer
2025-07-05T21:30:26.767-05:00 WARN 28592 --- [manyProcessors] [ main] org.hibernate.orm.deprecation : HHH90000025: H2Dialect does not need to be specified explicitly using 'hibernate.dialect' (remove the property setting and it will be selected by default)
2025-07-05T21:30:26.790-05:00 INFO 28592 --- [manyProcessors] [ main] org.hibernate.orm.connections.pooling : HHH10001005: Database info:
Database JDBC URL [Connecting through datasource 'HikariDataSource (HikariPool-1)']
Database driver: undefined/unknown
Database version: 2.3.232
Autocommit mode: undefined/unknown
Isolation level: undefined/unknown
Minimum pool size: undefined/unknown
Maximum pool size: undefined/unknown
2025-07-05T21:30:27.127-05:00 INFO 28592 --- [manyProcessors] [ main] o.h.e.t.j.p.i.JtaPlatformInitiator : HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration)
2025-07-05T21:30:27.129-05:00 INFO 28592 --- [manyProcessors] [ main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2025-07-05T21:30:27.215-05:00 INFO 28592 --- [manyProcessors] [ main] o.s.b.c.r.s.JobRepositoryFactoryBean : No database type set, using meta data indicating: H2
2025-07-05T21:30:27.278-05:00 INFO 28592 --- [manyProcessors] [ main] .c.a.BatchObservabilityBeanPostProcessor : No Micrometer observation registry found, defaulting to ObservationRegistry.NOOP
2025-07-05T21:30:27.286-05:00 INFO 28592 --- [manyProcessors] [ main] .c.a.BatchObservabilityBeanPostProcessor : No Micrometer observation registry found, defaulting to ObservationRegistry.NOOP
2025-07-05T21:30:27.289-05:00 INFO 28592 --- [manyProcessors] [ main] o.s.b.c.l.s.TaskExecutorJobLauncher : No TaskExecutor has been set, defaulting to synchronous executor.
2025-07-05T21:30:27.421-05:00 DEBUG 28592 --- [manyProcessors] [ main] .s.JobRegistrySmartInitializingSingleton : Registering job: importPersonsJob
2025-07-05T21:30:27.446-05:00 INFO 28592 --- [manyProcessors] [ main] o.z.d.batch.ManyProcessorsApplication : Started ManyProcessorsApplication in 2.86 seconds (process running for 3.274)
🚀 Launching importPersonsJob...
2025-07-05T21:30:27.547-05:00 INFO 28592 --- [manyProcessors] [ main] o.s.b.c.l.s.TaskExecutorJobLauncher : Job: [SimpleJob: [name=importPersonsJob]] launched with the following parameters: [{'time':'{value=1751769027453, type=class java.lang.Long, identifying=true}'}]
2025-07-05T21:30:27.548-05:00 DEBUG 28592 --- [manyProcessors] [ main] o.s.batch.core.job.AbstractJob : Job execution starting: JobExecution: id=1, version=0, startTime=null, endTime=null, lastUpdated=2025-07-05T21:30:27.535685900, status=STARTING, exitStatus=exitCode=UNKNOWN;exitDescription=, job=[JobInstance: id=1, version=0, Job=[importPersonsJob]], jobParameters=[{'time':'{value=1751769027453, type=class java.lang.Long, identifying=true}'}]
2025-07-05T21:30:27.577-05:00 INFO 28592 --- [manyProcessors] [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [step]
2025-07-05T21:30:27.577-05:00 DEBUG 28592 --- [manyProcessors] [ main] o.s.batch.core.step.AbstractStep : Executing: id=1
2025-07-05T21:30:27.595-05:00 DEBUG 28592 --- [manyProcessors] [ main] o.s.batch.repeat.support.RepeatTemplate : Starting repeat context.
2025-07-05T21:30:27.596-05:00 DEBUG 28592 --- [manyProcessors] [ main] o.s.batch.repeat.support.RepeatTemplate : Repeat operation about to start at count=1
2025-07-05T21:30:27.596-05:00 DEBUG 28592 --- [manyProcessors] [ main] o.s.b.c.s.c.StepContextRepeatCallback : Preparing chunk execution for StepContext: org.springframework.batch.core.scope.context.StepContext@1b69fc07
2025-07-05T21:30:27.597-05:00 DEBUG 28592 --- [manyProcessors] [ main] o.s.b.c.s.c.StepContextRepeatCallback : Chunk execution starting: queue size=0
2025-07-05T21:30:27.600-05:00 DEBUG 28592 --- [manyProcessors] [ main] o.s.batch.repeat.support.RepeatTemplate : Starting repeat context.
2025-07-05T21:30:27.600-05:00 DEBUG 28592 --- [manyProcessors] [ main] o.s.batch.repeat.support.RepeatTemplate : Repeat operation about to start at count=1
2025-07-05T21:30:27.635-05:00 DEBUG 28592 --- [manyProcessors] [ main] o.s.batch.repeat.support.RepeatTemplate : Repeat operation about to start at count=2
2025-07-05T21:30:27.636-05:00 DEBUG 28592 --- [manyProcessors] [ main] o.s.batch.repeat.support.RepeatTemplate : Repeat operation about to start at count=3
2025-07-05T21:30:27.637-05:00 DEBUG 28592 --- [manyProcessors] [ main] o.s.batch.repeat.support.RepeatTemplate : Repeat operation about to start at count=4
2025-07-05T21:30:27.638-05:00 DEBUG 28592 --- [manyProcessors] [ main] o.s.batch.repeat.support.RepeatTemplate : Repeat is complete according to policy and result value.
2025-07-05T21:30:27.640-05:00 DEBUG 28592 --- [manyProcessors] [ main] o.s.b.item.database.JdbcBatchItemWriter : Executing batch with 4 items.
2025-07-05T21:30:27.648-05:00 DEBUG 28592 --- [manyProcessors] [ main] o.s.b.i.support.AbstractFileItemWriter : Writing to file with 4 items.
2025-07-05T21:30:27.651-05:00 DEBUG 28592 --- [manyProcessors] [ main] o.s.b.c.step.item.ChunkOrientedTasklet : Inputs not busy, ended: false
2025-07-05T21:30:27.655-05:00 DEBUG 28592 --- [manyProcessors] [ main] o.s.batch.core.step.tasklet.TaskletStep : Applying contribution: [StepContribution: read=4, written=4, filtered=0, readSkips=0, writeSkips=0, processSkips=0, exitStatus=EXECUTING]
2025-07-05T21:30:27.661-05:00 DEBUG 28592 --- [manyProcessors] [ main] o.s.batch.core.step.tasklet.TaskletStep : Saving step execution before commit: StepExecution: id=1, version=1, name=step, status=STARTED, exitStatus=EXECUTING, readCount=4, filterCount=0, writeCount=4 readSkipCount=0, writeSkipCount=0, processSkipCount=0, commitCount=1, rollbackCount=0, exitDescription=
2025-07-05T21:30:27.663-05:00 DEBUG 28592 --- [manyProcessors] [ main] o.s.batch.repeat.support.RepeatTemplate : Repeat operation about to start at count=2
2025-07-05T21:30:27.663-05:00 DEBUG 28592 --- [manyProcessors] [ main] o.s.b.c.s.c.StepContextRepeatCallback : Preparing chunk execution for StepContext: org.springframework.batch.core.scope.context.StepContext@1b69fc07
2025-07-05T21:30:27.664-05:00 DEBUG 28592 --- [manyProcessors] [ main] o.s.b.c.s.c.StepContextRepeatCallback : Chunk execution starting: queue size=0
2025-07-05T21:30:27.664-05:00 DEBUG 28592 --- [manyProcessors] [ main] o.s.batch.repeat.support.RepeatTemplate : Starting repeat context.
2025-07-05T21:30:27.664-05:00 DEBUG 28592 --- [manyProcessors] [ main] o.s.batch.repeat.support.RepeatTemplate : Repeat operation about to start at count=1
2025-07-05T21:30:27.666-05:00 DEBUG 28592 --- [manyProcessors] [ main] o.s.batch.repeat.support.RepeatTemplate : Repeat operation about to start at count=2
2025-07-05T21:30:27.668-05:00 DEBUG 28592 --- [manyProcessors] [ main] o.s.batch.repeat.support.RepeatTemplate : Repeat operation about to start at count=3
2025-07-05T21:30:27.669-05:00 DEBUG 28592 --- [manyProcessors] [ main] o.s.batch.repeat.support.RepeatTemplate : Repeat operation about to start at count=4
2025-07-05T21:30:27.669-05:00 DEBUG 28592 --- [manyProcessors] [ main] o.s.batch.repeat.support.RepeatTemplate : Repeat is complete according to policy and result value.
2025-07-05T21:30:27.670-05:00 DEBUG 28592 --- [manyProcessors] [ main] o.s.b.item.database.JdbcBatchItemWriter : Executing batch with 3 items.
2025-07-05T21:30:27.671-05:00 DEBUG 28592 --- [manyProcessors] [ main] o.s.b.i.support.AbstractFileItemWriter : Writing to file with 3 items.
2025-07-05T21:30:27.672-05:00 DEBUG 28592 --- [manyProcessors] [ main] o.s.b.c.step.item.ChunkOrientedTasklet : Inputs not busy, ended: true
2025-07-05T21:30:27.672-05:00 DEBUG 28592 --- [manyProcessors] [ main] o.s.batch.core.step.tasklet.TaskletStep : Applying contribution: [StepContribution: read=3, written=3, filtered=0, readSkips=0, writeSkips=0, processSkips=0, exitStatus=EXECUTING]
2025-07-05T21:30:27.673-05:00 DEBUG 28592 --- [manyProcessors] [ main] o.s.batch.core.step.tasklet.TaskletStep : Saving step execution before commit: StepExecution: id=1, version=2, name=step, status=STARTED, exitStatus=EXECUTING, readCount=7, filterCount=0, writeCount=7 readSkipCount=0, writeSkipCount=0, processSkipCount=0, commitCount=2, rollbackCount=0, exitDescription=
2025-07-05T21:30:27.676-05:00 DEBUG 28592 --- [manyProcessors] [ main] o.s.batch.repeat.support.RepeatTemplate : Repeat is complete according to policy and result value.
2025-07-05T21:30:27.676-05:00 DEBUG 28592 --- [manyProcessors] [ main] o.s.batch.core.step.AbstractStep : Step execution success: id=1
2025-07-05T21:30:27.678-05:00 INFO 28592 --- [manyProcessors] [ main] o.s.batch.core.step.AbstractStep : Step: [step] executed in 101ms
2025-07-05T21:30:27.683-05:00 DEBUG 28592 --- [manyProcessors] [ main] o.s.batch.core.step.AbstractStep : Step execution complete: StepExecution: id=1, version=4, name=step, status=COMPLETED, exitStatus=COMPLETED, readCount=7, filterCount=0, writeCount=7 readSkipCount=0, writeSkipCount=0, processSkipCount=0, commitCount=2, rollbackCount=0
2025-07-05T21:30:27.686-05:00 DEBUG 28592 --- [manyProcessors] [ main] o.s.batch.core.job.AbstractJob : Upgrading JobExecution status: StepExecution: id=1, version=4, name=step, status=COMPLETED, exitStatus=COMPLETED, readCount=7, filterCount=0, writeCount=7 readSkipCount=0, writeSkipCount=0, processSkipCount=0, commitCount=2, rollbackCount=0, exitDescription=
2025-07-05T21:30:27.687-05:00 DEBUG 28592 --- [manyProcessors] [ main] o.s.batch.core.job.AbstractJob : Job execution complete: JobExecution: id=1, version=1, startTime=2025-07-05T21:30:27.558575200, endTime=null, lastUpdated=2025-07-05T21:30:27.558575200, status=COMPLETED, exitStatus=exitCode=COMPLETED;exitDescription=, job=[JobInstance: id=1, version=0, Job=[importPersonsJob]], jobParameters=[{'time':'{value=1751769027453, type=class java.lang.Long, identifying=true}'}]
Job finished! Verified the Data.
processed person: Person(id=1, firstName=MARY, lastName=ZHENG, processType=A)
processed person: Person(id=2, firstName=bob, lastName=joe, processType=B)
processed person: Person(id=3, firstName=john, lastName=DOE, processType=C)
processed person: Person(id=4, firstName=ANDY, lastName=JOHNSON, processType=A)
processed person: Person(id=5, firstName=ted, lastName=BEAR, processType=C)
processed person: Person(id=6, firstName=grace, lastName=cheng, processType=B)
processed person: Person(id=7, firstName=APRIL, lastName=GREEN, processType=A)
2025-07-05T21:30:27.701-05:00 INFO 28592 --- [manyProcessors] [ main] o.s.b.c.l.s.TaskExecutorJobLauncher : Job: [SimpleJob: [name=importPersonsJob]] completed with the following parameters: [{'time':'{value=1751769027453, type=class java.lang.Long, identifying=true}'}] and the following status: [COMPLETED] in 131ms
🚀 Done importPersonsJob...
2025-07-05T21:30:27.709-05:00 DEBUG 28592 --- [manyProcessors] [ionShutdownHook] .s.JobRegistrySmartInitializingSingleton : Unregistering job: importPersonsJob
2025-07-05T21:30:27.709-05:00 INFO 28592 --- [manyProcessors] [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2025-07-05T21:30:27.713-05:00 INFO 28592 --- [manyProcessors] [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated...
2025-07-05T21:30:27.717-05:00 INFO 28592 --- [manyProcessors] [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed.
- Line 79-87: print out the processed person, verify the
firstNameandlastNamebased on theprocessType.
Open the output file and verify the processed person.
Processed_person.txt
1,MARY,ZHENG,A 2,bob,joe,B 3,john,DOE,C 4,ANDY,JOHNSON,A 5,ted,BEAR,C 6,grace,cheng,B 7,APRIL,GREEN,A
- Confirmed the output file matches the database
T_PERSONon thefirstName,lastNamebased on theprocessTypeas well.
6. Conclusion
In this article, we learned how to configure a Spring Batch job using a single reader but multiple processors and writers. We read data from a CSV file, routed each record to a specific processor based on its content, and finally delegated the writing to multiple writers.
7. Download
This was an example of a Gradle project which included one reader multiple processors writers.
You can download the full source code of this example here: Spring Batch One Reader with Multiple Processors and Writers




