Bug description
Spring Batch framework not creating an emty output files when data doesn't flow through that Classifier with the MultiResourceItemWriter
Environment
Spring Boot v2.7.1, Java version - 11
Steps to reproduce
Here is the code
Employee.java
@AllArgsConstructor
@NoArgsConstructor
@Data
@Builder
public class Employee {
private String empId;
private String firstName;
private String lastName;
private String role;
@Override
public String toString() {
return empId + ","+ firstName+ ","+ lastName+ ","+ role;
}
}
package com.example;
import org.springframework.batch.item.ItemWriter;
import org.springframework.classify.Classifier;
import lombok.Setter;
@Setter
public class EmployeeClassifier implements Classifier<Employee, ItemWriter<? super Employee>> {
private static final long serialVersionUID = 1L;
private ItemWriter<Employee> javaDeveloperFileItemWriter;
private ItemWriter<Employee> pythonDeveloperFileItemWriter;
private ItemWriter<Employee> cloudDeveloperFileItemWriter;
public EmployeeClassifier() {
}
public EmployeeClassifier(ItemWriter<Employee> javaDeveloperFileItemWriter,
ItemWriter<Employee> pythonDeveloperFileItemWriter,
ItemWriter<Employee> cloudDeveloperFileItemWriter) {
this.javaDeveloperFileItemWriter = javaDeveloperFileItemWriter;
this.pythonDeveloperFileItemWriter = pythonDeveloperFileItemWriter;
this.cloudDeveloperFileItemWriter = cloudDeveloperFileItemWriter;
}
@Override
public ItemWriter<? super Employee> classify(Employee employee) {
if(employee.getRole().equals("Java Developer")){
return javaDeveloperFileItemWriter;
}
else if(employee.getRole().equals("Python Developer")){
return pythonDeveloperFileItemWriter;
}
return cloudDeveloperFileItemWriter;
}
}
package com.example;
import org.springframework.batch.item.file.mapping.FieldSetMapper;
import org.springframework.batch.item.file.transform.FieldSet;
import org.springframework.validation.BindException;
public class EmployeeFieldSetMapper implements FieldSetMapper<Employee> {
@Override
public Employee mapFieldSet(FieldSet fieldSet) throws BindException {
return Employee.builder()
.empId(fieldSet.readRawString("empId"))
.firstName(fieldSet.readRawString("firstName"))
.lastName(fieldSet.readRawString("lastName"))
.role(fieldSet.readRawString("role"))
.build();
}
}
package com.example;
import org.springframework.batch.item.file.mapping.FieldSetMapper;
import org.springframework.batch.item.file.transform.FieldSet;
import org.springframework.validation.BindException;
public class EmployeeFieldSetMapper implements FieldSetMapper<Employee> {
@Override
public Employee mapFieldSet(FieldSet fieldSet) throws BindException {
return Employee.builder()
.empId(fieldSet.readRawString("empId"))
.firstName(fieldSet.readRawString("firstName"))
.lastName(fieldSet.readRawString("lastName"))
.role(fieldSet.readRawString("role"))
.build();
}
}
package com.example;
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.ItemWriter;
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.file.builder.MultiResourceItemWriterBuilder;
import org.springframework.batch.item.file.mapping.DefaultLineMapper;
import org.springframework.batch.item.file.transform.DelimitedLineTokenizer;
import org.springframework.batch.item.file.transform.PassThroughLineAggregator;
import org.springframework.batch.item.support.ClassifierCompositeItemWriter;
import org.springframework.batch.item.support.builder.ClassifierCompositeItemWriterBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.classify.Classifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.FileSystemResource;
@Configuration
public class MyJobConfig {
@Autowired
private JobBuilderFactory jobBuilderFactory;
@Autowired
private StepBuilderFactory stepBuilderFactory;
@Bean
public FlatFileItemReader<Employee> itemReader() {
DelimitedLineTokenizer tokenizer = new DelimitedLineTokenizer();
tokenizer.setNames("empId", "firstName", "lastName", "role");
DefaultLineMapper<Employee> employeeLineMapper = new DefaultLineMapper<>();
employeeLineMapper.setLineTokenizer(tokenizer);
employeeLineMapper.setFieldSetMapper(new EmployeeFieldSetMapper());
employeeLineMapper.afterPropertiesSet();
return new FlatFileItemReaderBuilder<Employee>()
.name("flatFileReader")
.linesToSkip(1)
.resource(new ClassPathResource("employee.csv"))
.lineMapper(employeeLineMapper)
.build();
}
@Bean
public ClassifierCompositeItemWriter<Employee> classifierCompositeItemWriter() throws Exception {
Classifier<Employee, ItemWriter<? super Employee>> classifier =
new EmployeeClassifier(javaDeveloperItemWriter(), pythonDeveloperItemWriter(), cloudDeveloperItemWriter());
return new ClassifierCompositeItemWriterBuilder<Employee>()
.classifier(classifier)
.build();
}
@Bean
public ItemWriter<Employee> javaDeveloperItemWriter() {
FlatFileItemWriter<Employee> itemWriter = new FlatFileItemWriterBuilder<Employee>()
.lineAggregator(new PassThroughLineAggregator<>())
.name("itemsWriter")
.build();
return new MultiResourceItemWriterBuilder<Employee>()
.name("javaDeveloperItemWriter")
.delegate(itemWriter)
.resource(new FileSystemResource("javaDeveloper-employee.csv"))
.itemCountLimitPerResource(2)
.resourceSuffixCreator(index -> "-" + index)
.build();
}
@Bean
public ItemWriter<Employee> pythonDeveloperItemWriter() {
FlatFileItemWriter<Employee> itemWriter = new FlatFileItemWriterBuilder<Employee>()
.lineAggregator(new PassThroughLineAggregator<>())
.name("itemsWriter")
.build();
return new MultiResourceItemWriterBuilder<Employee>()
.name("pythonDeveloperItemWriter")
.delegate(itemWriter)
.resource(new FileSystemResource("pythonDeveloper-employee.csv"))
.itemCountLimitPerResource(2)
.resourceSuffixCreator(index -> "-" + index)
.build();
}
@Bean
public ItemWriter<Employee> cloudDeveloperItemWriter() {
FlatFileItemWriter<Employee> itemWriter = new FlatFileItemWriterBuilder<Employee>()
.lineAggregator(new PassThroughLineAggregator<>())
.name("itemsWriter")
.build();
return new MultiResourceItemWriterBuilder<Employee>()
.name("cloudDeveloperItemWriter")
.delegate(itemWriter)
.resource(new FileSystemResource("cloudDeveloper-employee.csv"))
.itemCountLimitPerResource(2)
.resourceSuffixCreator(index -> "-" + index)
.build();
}
@Bean
public Step step() throws Exception {
return stepBuilderFactory.get("step")
.<Employee, Employee>chunk(1)
.reader(itemReader())
.writer(classifierCompositeItemWriter())
.build();
}
@Bean
public Job job() throws Exception {
return jobBuilderFactory.get("job")
.start(step())
.build();
}
}
employee.csv
empId,firstName,lastName,role
1,John ,Doe,Java Developer
2,Jane ,Doe,Python Developer
empId,firstName,lastName,role
1,John ,Doe,Java Developer
2,Jane ,Doe,Python Developer
package com.example;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
@EnableBatchProcessing
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class SpringBatchMultipleFilesWithCompositeApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBatchMultipleFilesWithCompositeApplication.class, args);
}
}
Expected behavior
Even though there is no data for the role cloud develoer, expectation from Spring Batch to create an empty file with headers for the cloud developer.
Or am I missing anything?
I was hoping to see cloudDeveloper-1.csv file

Bug description
Spring Batch framework not creating an emty output files when data doesn't flow through that
Classifierwith theMultiResourceItemWriterEnvironment
Spring Boot v2.7.1, Java version - 11
Steps to reproduce
Here is the code
Employee.java
employee.csv
Expected behavior
Even though there is no data for the role cloud develoer, expectation from Spring Batch to create an empty file with headers for the cloud developer.
Or am I missing anything?
I was hoping to see cloudDeveloper-1.csv file