Enterprise Java

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 new processTypes.

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_SQL to retrieve the data from the T_PERSON table.
  • Line 27: both firstName and lastName are transformed based on the processType.

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> fileReader reads data from the persons.csv file.
  • FlatFileItemWriter<Person> fileWriter writes to the output/processed_persons.txt file.
  • JdbcBatchItemWriter<Person> dbWriter writes to the database by INSERT_SQL.
  • ItemWriter<Person> compositeWriter contains both fileWriter and dbWriter.
  • PersonProcessorRouter multipleProcessor routes to TypeAProcessor, TypeBProcessor, or TypeCProcessor.
  • Job importPersonsJob configures a Job with jobListener and starts with the step.
  • Step step configures a step with fileReader, multipleProcessor, compositeWriter, and chunk size 4.
  • runJob starts the importPersonsJob job.

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: fileReader reads data from the persons.csv file.
  • Line 66-69: compositeWriter contains both fileWriter and dbWriter.
  • Line 75: multipleProcessor routes to TypeAProcessorTypeBProcessor, or TypeCProcessor.
  • Line 84-87: step configures a step with fileReader, multipleProcessor, compositeWriter, and chunk size 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 firstName and lastName based on the processType.

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_PERSON on the firstName, lastName based on the processType as 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.

Download
You can download the full source code of this example here: Spring Batch One Reader with Multiple Processors and Writers

Mary Zheng

Mary graduated from the Mechanical Engineering department at ShangHai JiaoTong University. She also holds a Master degree in Computer Science from Webster University. During her studies she has been involved with a large number of projects ranging from programming and software engineering. She worked as a lead Software Engineer where she led and worked with others to design, implement, and monitor the software solution.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Back to top button