Spring Batch ClassifierCompositeItemWriter Example
This is an in-depth article related to the Spring Batch Classifier with ClassifierCompositeItemWriter.
1. Introduction
Spring Batch is a processing framework designed for the robust execution of jobs. Spring Batch Framework is an open-source library for batch processing. Batch Processing is the execution of a series of jobs. Spring Batch has classes and APIs to read/write resources, job creation, job metadata storage, job data persistence, transaction management, job processing statistics, job restart, and partitioning techniques to process high-volume of data. It has a job repository that takes care of scheduling and job interaction. A job consists of multiple steps. Each step has the sequence of reading job metadata and processing the input to write the output.
Spring batch is based on the traditional batch architecture. In the traditional batch framework, a job repository performs the work of scheduling and interacting with the job. The framework handles the low-level storage work of handling the jobs.
2. Spring Batch ClassifierCompositeItemWriter
2.1 Prerequisites
Java 8 or 9 is required on the Linux, windows, or mac operating system. Maven 3.6.1 is required for building the spring batch application.
2.2 Download
You can download Java 8 can be downloaded from the Oracle web site . Apache Maven 3.6.1 can be downloaded from Apache site. Spring framework latest releases are available from the spring website.
2.3 Setup
You can set the environment variables for JAVA_HOME and PATH. They can be set as shown below:
Environment Setup for Java
JAVA_HOME="/desktop/jdk1.8.0_73" export JAVA_HOME PATH=$JAVA_HOME/bin:$PATH export PATH
The environment variables for maven are set as below:Environment Setup for Maven
Run Command
JAVA_HOME="/jboss/jdk1.8.0_73″ export M2_HOME=/users/bhagvan.kommadi/Desktop/apache-maven-3.6.1 export M2=$M2_HOME/bin export PATH=$M2:$PATH
2.4 Building the application
2.4.1 Spring
You can start building Spring applications using the Spring Boot framework. Spring Boot has a minimal configuration of Spring. Spring Boot has features related to security, tracing, application health management, and runtime support for webservers. Spring configuration is done through maven pom.xml. The XML configuration is shown as below:
Spring Configuration
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework</groupId>
<artifactId>spring-helloworld</artifactId>
<version>0.1.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<properties>
<java.version>1.8</java.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
You can create a HelloWorldController class as the web controller. The class is annotated using @RestController. Rest Controller is used to handling requests in Spring Model View Controller framework. Annotation @RequestMapping is used to annotate the index() method. The code for the HelloWorldController class is shown below:
HelloWorld Controller
package helloworld;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
@RestController
public class HelloWorldController {
@RequestMapping("/")
public String index() {
return "Hello World\n";
}
}
HelloWorldApp is created as the Spring Boot web application. When the application starts, beans, and settings are wired up dynamically. They are applied to the application context. The code for HelloWorldApp class is shown below:
HelloWorld App
package helloworld;
import java.util.Arrays;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication
public class HelloWorldApp {
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(HelloWorldApp.class, args);
System.out.println("Inspecting the beans");
String[] beans = ctx.getBeanDefinitionNames();
Arrays.sort(beans);
for (String name : beans) {
System.out.println("Bean Name" +name);
}
}
}
Maven is used for building the application. The command below builds the application.
Maven Build Command
mvn package
The output of the executed command is shown below.

The jar file spring-helloworld-0.1.0.jar is created. The following command is used for executing the jar file.
Run Command
java -jar target/spring-helloworld-0.1.0.jar
The output of the executed command is shown below.

Curl command is invoked on the command line for the execution of index method. The method returns a String “Hello World” text. @RestController aggregates the two annotations @Controller and @ResponseBody. This results in returning data. The ouput is shown as below.

2.5 Spring Batch
Spring Batch is a lightweight, comprehensive batch framework. Developers can use it to create batch applications. Batch applications are designed to handle the daily and end of the daily operations of the enterprise. It has features as mentioned below:
- Large Data Processing
- Logging
- Tracing
- Resource Management
- Job Processing
- Transaction management
- Chunk based processing
- Declarative I/O
- Start/Stop/Restart
- Retry/Skip
- Web-based administration interface
Let us start looking at creation of a Spring batch job with Batch Classifier and Composite Item writer.
2.5.1 Spring Batch Classifier Composite Item Writer
Spring Batch decorators are used to classify the data to write to multiple destinations. Batch decorators are based on design patterns. Item Reader and ItemWriter are batch decorators. Additional functionality can be added to the ItemReader and ItemWriters. The data can be saved in a database, CSV, or XML file.
ClassifierCompositeItem Writer is based on a router pattern. This class is thread-safe. It helps in writing multiple items to different destinations. The code implementation of EmployeeClassifier class is shown below:
EmployeeClassifier
package org.javacodegeeks.batch.decorator.classifier;
import org.springframework.batch.item.ItemWriter;
import org.springframework.classify.Classifier;
import org.javacodegeeks.batch.decorator.model.Employee;
public class EmployeeClassifier implements Classifier<Employee, ItemWriter> {
private static final long serialVersionUID = 1L;
private ItemWriter evenItemWriter;
private ItemWriter oddItemWriter;
public EmployeeClassifier(ItemWriter evenItemWriter, ItemWriter oddItemWriter) {
this.evenItemWriter = evenItemWriter;
this.oddItemWriter = oddItemWriter;
}
@Override
public ItemWriter classify(Employee employee) {
return employee.getId() % 2 == 0 ? evenItemWriter : oddItemWriter;
}
}
The code below shows the Employee class which is part of the model.
Employee Class
package org.javacodegeeks.batch.decorator.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@Builder
@NoArgsConstructor
public class Employee {
private Long id;
private String firstName;
private String lastName;
private String birthdate;
}
The database table is created for employee in employeedb. Below is the create sql for the database.
Employee sql
CREATE TABLE 'employeedb'.'employee' (
'id' MEDIUMINT(8) UNSIGNED NOT NULL AUTO_INCREMENT,
'firstName' VARCHAR(255) NULL,
'lastName' VARCHAR(255) NULL,
'birthdate' VARCHAR(255) NULL,
PRIMARY KEY ('id')
) AUTO_INCREMENT=1;
The data can be created using the sql script below.
Employee table data sql
INSERT INTO `employeedb`.`employee` (`id`, `firstName`, `lastName`,`birthdate`) VALUES ('1', 'john', 'smith','10-11-1962 10:10:10');
INSERT INTO `employeedb`.`employee` (`id`, `firstName`, `lastName`,`birthdate`) VALUES ('2', 'george', 'kinsey','11-01-1982 10:10:10');
INSERT INTO `employeedb`.`employee` (`id`, `firstName`, `lastName`,`birthdate`) VALUES ('3', 'thomas', 'durham','17-06-1953 10:10:10');
SpringJobConfiguration class code is shown below:
SpringJobConfiguration
package org.javacodegeeks.batch.decorator.config;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.item.database.JdbcPagingItemReader;
import org.springframework.batch.item.database.Order;
import org.springframework.batch.item.database.support.MySqlPagingQueryProvider;
import org.springframework.batch.item.file.FlatFileItemWriter;
import org.springframework.batch.item.support.ClassifierCompositeItemWriter;
import org.springframework.batch.item.xml.StaxEventItemWriter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.FileSystemResource;
import org.springframework.oxm.xstream.XStreamMarshaller;
import org.javacodegeeks.batch.decorator.aggregator.CustomLineAggregator;
import org.javacodegeeks.batch.decorator.classifier.EmployeeClassifier;
import org.javacodegeeks.batch.decorator.mapper.EmployeeRowMapper;
import org.javacodegeeks.batch.decorator.model.Employee;
@Configuration
public class SpringJobConfiguration {
@Autowired
private JobBuilderFactory jobBuilderFactory;
@Autowired
private StepBuilderFactory stepBuilderFactory;
@Autowired
private DataSource dataSource;
@Bean
public JdbcPagingItemReader customerPagingItemReader() {
JdbcPagingItemReader reader = new JdbcPagingItemReader();
reader.setDataSource(this.dataSource);
reader.setFetchSize(1000);
reader.setRowMapper(new EmployeeRowMapper());
Map sortKeys = new HashMap();
sortKeys.put("id", Order.ASCENDING);
MySqlPagingQueryProvider queryProvider = new MySqlPagingQueryProvider();
queryProvider.setSelectClause("id, firstName, lastName, birthdate");
queryProvider.setFromClause("from employee");
queryProvider.setSortKeys(sortKeys);
reader.setQueryProvider(queryProvider);
return reader;
}
@Bean
public FlatFileItemWriter jsonItemWriter() throws Exception {
String customerOutputPath = File.createTempFile("employeeOutput", ".out").getAbsolutePath();
System.out.println(">> Output Path = " + customerOutputPath);
FlatFileItemWriter writer = new FlatFileItemWriter();
writer.setLineAggregator(new CustomLineAggregator());
writer.setResource(new FileSystemResource(customerOutputPath));
writer.afterPropertiesSet();
return writer;
}
@Bean
public StaxEventItemWriter xmlItemWriter() throws Exception {
String customerOutputPath = File.createTempFile("employeeOutput", ".out").getAbsolutePath();
System.out.println(">> Output Path = " + customerOutputPath);
Map aliases = new HashMap();
aliases.put("employee", Employee.class);
XStreamMarshaller marshaller = new XStreamMarshaller();
marshaller.setAliases(aliases);
StaxEventItemWriter writer = new StaxEventItemWriter();
writer.setRootTagName("employees");
writer.setMarshaller(marshaller);
writer.setResource(new FileSystemResource(customerOutputPath));
writer.afterPropertiesSet();
return writer;
}
@Bean
public ClassifierCompositeItemWriter classifierEmployeeCompositeItemWriter() throws Exception {
ClassifierCompositeItemWriter compositeItemWriter = new ClassifierCompositeItemWriter();
compositeItemWriter.setClassifier(new EmployeeClassifier(xmlItemWriter(), jsonItemWriter()));
return compositeItemWriter;
}
@Bean
public Step step1() throws Exception {
return stepBuilderFactory.get("step1")
.chunk(10)
.reader(customerPagingItemReader())
.writer(classifierEmployeeCompositeItemWriter())
.stream(xmlItemWriter())
.stream(jsonItemWriter())
.build();
}
@Bean
public Job job() throws Exception {
return jobBuilderFactory.get("job")
.start(step1())
.build();
}
}
SpringBatchApplication class code is shown below:
SpringBatchApplication Class
package org.javacodegeeks.batch.decorator;
import java.util.Date;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@EnableBatchProcessing
public class SpringBatchApplication
implements CommandLineRunner {
@Autowired
private JobLauncher jobLauncher;
@Autowired
private Job job;
public static void main(String[] args) {
SpringApplication.run(SpringBatchApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
JobParameters jobParameters = new JobParametersBuilder()
.addString("JobId", String.valueOf(System.currentTimeMillis()))
.addDate("date", new Date())
.addLong("time",System.currentTimeMillis()).toJobParameters();
JobExecution execution = jobLauncher.run(job, jobParameters);
System.out.println("STATUS :: "+execution.getStatus());
}
}
Maven pom.xml is shown below which is used for the build and execution of the project.
Maven pom.xml
<?xml version="1.0"?> <project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.2.RELEASE</version> <relativePath /> </parent> <groupId>org.javacodegeeks</groupId> <artifactId>batch-classifier</artifactId> <name>batch-classifier</name> <packaging>jar</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-batch</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <!-- Spring OXM --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-oxm</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>com.thoughtworks.xstream</groupId> <artifactId>xstream</artifactId> <version>1.4.7</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.batch</groupId> <artifactId>spring-batch-test</artifactId> <scope>test</scope> </dependency> </dependencies> </project>
Code can be built using the below command.
Build Command
mvn package
The output of the command is shown below:
Build Command Output
apples-MacBook-Air:batchclassifier bhagvan.kommadi$ mvn package [INFO] Scanning for projects... [INFO] [INFO] ---------------------------------- [INFO] Building batch-classifier 2.2.2.RELEASE [INFO] --------------------------------[ jar ]--------------------------------- [INFO] [INFO] --- maven-resources-plugin:3.1.0:resources (default-resources) @ batch-classifier --- [INFO] Using 'UTF-8' encoding to copy filtered resources. [INFO] Copying 1 resource [INFO] Copying 2 resources [INFO] [INFO] --- maven-compiler-plugin:3.8.1:compile (default-compile) @ batch-classifier --- [INFO] Changes detected - recompiling the module! [INFO] Compiling 6 source files to /Users/bhagvan.kommadi/Desktop/JavacodeGeeks/Code/springbatchclassifier/batchclassifier/target/classes [INFO] [INFO] --- maven-resources-plugin:3.1.0:testResources (default-testResources) @ batch-classifier --- [INFO] Using 'UTF-8' encoding to copy filtered resources. [INFO] skip non existing resourceDirectory /Users/bhagvan.kommadi/Desktop/JavacodeGeeks/Code/springbatchclassifier/batchclassifier/src/test/resources [INFO] [INFO] --- maven-compiler-plugin:3.8.1:testCompile (default-testCompile) @ batch-classifier --- [INFO] Changes detected - recompiling the module! [INFO] Compiling 1 source file to /Users/bhagvan.kommadi/Desktop/JavacodeGeeks/Code/springbatchclassifier/batchclassifier/target/test-classes [INFO] [INFO] --- maven-surefire-plugin:2.22.2:test (default-test) @ batch-classifier --- [INFO] [INFO] ------------------------------------------------------- [INFO] T E S T S [INFO] ------------------------------------------------------- [INFO] Running org.javacodegeeks.batch.decorator.AppTest [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.005 s - in org.javacodegeeks.batch.decorator.AppTest [INFO] [INFO] Results: [INFO] [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 [INFO] [INFO] [INFO] --- maven-jar-plugin:3.1.2:jar (default-jar) @ batch-classifier --- [INFO] Building jar: /Users/bhagvan.kommadi/Desktop/JavacodeGeeks/Code/springbatchclassifier/batchclassifier/target/batch-classifier-2.2.2.RELEASE.jar [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 10.690 s [INFO] Finished at: 2020-11-03T21:56:14+05:30 [INFO] ------------------------------------------------------------------------ apples-MacBook-Air:batchclassifier bhagvan.kommadi$
Code can be executed using the command shown below:
Run Command
mvn spring-boot:run
The output of the above command is shown below:
Run Command Output
apples-MacBook-Air:batchclassifier bhagvan.kommadi$ mvn spring-boot:run
[INFO] Scanning for projects...
[INFO]
[INFO] ----------------------------------
[INFO] Building batch-classifier 2.2.2.RELEASE
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] >>> spring-boot-maven-plugin:2.2.2.RELEASE:run (default-cli) > test-compile @ batch-classifier >>>
[INFO]
[INFO] --- maven-resources-plugin:3.1.0:resources (default-resources) @ batch-classifier ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO] Copying 2 resources
[INFO]
[INFO] --- maven-compiler-plugin:3.8.1:compile (default-compile) @ batch-classifier ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-resources-plugin:3.1.0:testResources (default-testResources) @ batch-classifier ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /Users/bhagvan.kommadi/Desktop/JavacodeGeeks/Code/springbatchclassifier/batchclassifier/src/test/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.8.1:testCompile (default-testCompile) @ batch-classifier ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] <<< spring-boot-maven-plugin:2.2.2.RELEASE:run (default-cli) < test-compile @ batch-classifier <<> Output Path = /var/folders/cr/0y892lq14qv7r24yl0gh0_dm0000gp/T/employeeOutput8605284055436216810.out
>> Output Path = /var/folders/cr/0y892lq14qv7r24yl0gh0_dm0000gp/T/employeeOutput913271422004184523.out
2020-11-03 21:58:54.079 INFO 4105 --- [ main] o.s.b.c.r.s.JobRepositoryFactoryBean : No database type set, using meta data indicating: MYSQL
2020-11-03 21:58:54.116 INFO 4105 --- [ main] o.s.b.c.l.support.SimpleJobLauncher : No TaskExecutor has been set, defaulting to synchronous executor.
2020-11-03 21:58:54.246 INFO 4105 --- [ main] o.j.b.decorator.SpringBatchApplication : Started SpringBatchApplication in 3.051 seconds (JVM running for 3.964)
2020-11-03 21:58:54.249 INFO 4105 --- [ main] o.s.b.a.b.JobLauncherCommandLineRunner : Running default command line with: []
2020-11-03 21:58:54.431 INFO 4105 --- [ main] o.s.b.c.l.support.SimpleJobLauncher : Job: [SimpleJob: [name=job]] launched with the following parameters: [{}]
2020-11-03 21:58:54.507 INFO 4105 --- [ main] o.s.batch.core.job.SimpleStepHandler : Step already complete or not restartable, so no action to execute: StepExecution: id=1, version=3, name=step1, status=COMPLETED, exitStatus=COMPLETED, readCount=0, filterCount=0, writeCount=0 readSkipCount=0, writeSkipCount=0, processSkipCount=0, commitCount=1, rollbackCount=0, exitDescription=
2020-11-03 21:58:54.526 INFO 4105 --- [ main] o.s.b.c.l.support.SimpleJobLauncher : Job: [SimpleJob: [name=job]] completed with the following parameters: [{}] and the following status: [COMPLETED] in 33ms
2020-11-03 21:58:54.572 INFO 4105 --- [ main] o.s.b.c.l.support.SimpleJobLauncher : Job: [SimpleJob: [name=job]] launched with the following parameters: [{JobId=1604420934528, date=1604420934528, time=1604420934528}]
2020-11-03 21:58:54.595 INFO 4105 --- [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [step1]
2020-11-03 21:58:54.741 INFO 4105 --- [ main] o.s.batch.core.step.AbstractStep : Step: [step1] executed in 144ms
2020-11-03 21:58:54.758 INFO 4105 --- [ main] o.s.b.c.l.support.SimpleJobLauncher : Job: [SimpleJob: [name=job]] completed with the following parameters: [{JobId=1604420934528, date=1604420934528, time=1604420934528}] and the following status: [COMPLETED] in 180ms
STATUS :: COMPLETED
2020-11-03 21:58:54.764 WARN 4105 --- [extShutdownHook] o.s.b.f.support.DisposableBeanAdapter : Destroy method 'close' on bean with name 'xmlItemWriter' threw an exception: java.lang.NullPointerException
2020-11-03 21:58:54.765 INFO 4105 --- [extShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated...
2020-11-03 21:58:54.773 INFO 4105 --- [extShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 8.810 s
[INFO] Finished at: 2020-11-03T21:58:54+05:30
[INFO] ------------------------------------------------------------------------
apples-MacBook-Air:batchclassifier bhagvan.kommadi$
The xml output is shown below:
XML Output
<?xml version="1.0" encoding="UTF-8"? ><employees> <employee> <id>2</id> <firstName>george</firstName> <lastName>kinsey</lastName> <birthdate>11-01-1982 10:10:10</birthdate> </employee> </employees>
The json output is presented below:
JSON Output
{"id":1,"firstName":"john","lastName":"smith","birthdate":"10-11-1962 10:10:10"}
{"id":3,"firstName":"thomas","lastName":"durham","birthdate":"17-06-1953 10:10:10"}
3. Download the Source Code
That was an article related to the Spring Batch Classifier with ClassifierCompositeItemWriter.
You can download the full source code of this example here: Spring Batch ClassifierCompositeItemWriter Example

