Spring Batch has a good support for reading the input data of a batch job from different data sources such as files (CSV, XML) and databases.

However, it’s quite common that you have to read the input data from a data source that’s not supported out of the box. This means that you have to implement a component that reads the input data from your data source.

This blog post helps you to solve that problem. After you have read this blog post, you:

  • Understand how you can implement a custom ItemReader.
  • Know how you can configure the ItemReader bean which provides the input data for your batch job.

Let’s begin.

This blog post assumes that:

Creating a Custom ItemReader

You can create a custom ItemReader by following these steps:

First, you have to create a class that implements the ItemReader<T> interface and provide the type of the returned object as a type parameter.

Second, you have to implement the T read() method of the ItemReader<T> interface by following these rules:

  • The read() method returns an object that contains the information of the next item.
  • If the next item isn’t found, the read() method must return null.

Let’s create a custom ItemReader that returns the student information of an online testing course as StudentDTO objects which are read from the memory.

The StudentDTO class is a simple data transfer object, and its source code looks as follows:

 1public class StudentDTO {
 2
 3    private String emailAddress;
 4    private String name;
 5    private String purchasedPackage;
 6
 7    public StudentDTO() {}
 8
 9    public String getEmailAddress() {
10        return emailAddress;
11    }
12
13    public String getName() {
14        return name;
15    }
16
17    public String getPurchasedPackage() {
18        return purchasedPackage;
19    }
20
21    public void setEmailAddress(String emailAddress) {
22        this.emailAddress = emailAddress;
23    }
24
25    public void setName(String name) {
26        this.name = name;
27    }
28
29    public void setPurchasedPackage(String purchasedPackage) {
30        this.purchasedPackage = purchasedPackage;
31    }
32}

You can implement your ItemReader by following these steps:

First, you have to create a class that implements the ItemReader<T> interface and specify the type of the object which is returned by the T read() method. After you have created this class, its source code looks as follows:

1import org.springframework.batch.item.ItemReader;
2
3public class InMemoryStudentReader implements ItemReader<StudentDTO> {
4}

Second, you have to initialize the input data that’s returned by your ItemReader. You can initialize your input data by following these steps:

  1. Add a List<Student> field to your ItemReader class. This field contains the student information of the course.
  2. Add an int field called nextStudentIndex to your ItemReader class. This field contains the index of the next StudentDTO object that’s returned by your ItemReader.
  3. Add a private initialize() method to your ItemReader class. This method creates the student data and sets the index of the next student to 0.
  4. Create a constructor that invokes the initialize() method.

After you have initialized your input data, the source code of your ItemReader class looks as follows:

 1import org.springframework.batch.item.ItemReader;
 2
 3import java.util.Arrays;
 4import java.util.Collections;
 5import java.util.List;
 6
 7public class InMemoryStudentReader implements ItemReader<StudentDTO> {
 8
 9    private int nextStudentIndex;
10    private List<StudentDTO> studentData;
11
12    InMemoryStudentReader() {
13        initialize();
14    }
15
16    private void initialize() {
17        StudentDTO tony = new StudentDTO();
18        tony.setEmailAddress("tony.tester@gmail.com");
19        tony.setName("Tony Tester");
20        tony.setPurchasedPackage("master");
21
22        StudentDTO nick = new StudentDTO();
23        nick.setEmailAddress("nick.newbie@gmail.com");
24        nick.setName("Nick Newbie");
25        nick.setPurchasedPackage("starter");
26
27        StudentDTO ian = new StudentDTO();
28        ian.setEmailAddress("ian.intermediate@gmail.com");
29        ian.setName("Ian Intermediate");
30        ian.setPurchasedPackage("intermediate");
31
32        studentData = Collections.unmodifiableList(Arrays.asList(tony, nick, ian));
33        nextStudentIndex = 0;
34    }
35}

Third, you have to implement the read() method of the ItemReader interface by following these rules:

  • If the next student is found, return the found StudentDTO object and increase the value of the nextStudentIndex field by 1.
  • If the next student isn’t found, set the value of the nextStudentIndex field to 0.
  • If the next student isn’t found, return null.

After you have implemented the read() method, the source code of your ItemReader class looks as follows:

 1import org.springframework.batch.item.ItemReader;
 2
 3import java.util.Arrays;
 4import java.util.Collections;
 5import java.util.List;
 6
 7public class InMemoryStudentReader implements ItemReader<StudentDTO> {
 8
 9    private int nextStudentIndex;
10    private List<StudentDTO> studentData;
11
12    InMemoryStudentReader() {
13        initialize();
14    }
15
16    private void initialize() {
17        StudentDTO tony = new StudentDTO();
18        tony.setEmailAddress("tony.tester@gmail.com");
19        tony.setName("Tony Tester");
20        tony.setPurchasedPackage("master");
21
22        StudentDTO nick = new StudentDTO();
23        nick.setEmailAddress("nick.newbie@gmail.com");
24        nick.setName("Nick Newbie");
25        nick.setPurchasedPackage("starter");
26
27        StudentDTO ian = new StudentDTO();
28        ian.setEmailAddress("ian.intermediate@gmail.com");
29        ian.setName("Ian Intermediate");
30        ian.setPurchasedPackage("intermediate");
31
32        studentData = Collections.unmodifiableList(Arrays.asList(tony, nick, ian));
33        nextStudentIndex = 0;
34    }
35
36    @Override
37    public StudentDTO read() throws Exception {
38        StudentDTO nextStudent = null;
39
40        if (nextStudentIndex < studentData.size()) {
41            nextStudent = studentData.get(nextStudentIndex);
42            nextStudentIndex++;
43        }
44        else {
45            nextStudentIndex = 0;
46        }
47
48        return nextStudent;
49    }
50}

After you have created your custom ItemReader class, you have to configure the ItemReader bean that provides the input data for your Spring Batch job. Next, you will find out how you can configure this bean.

Configuring the ItemReader Bean

You can configure your ItemReader bean by following these steps:

First, you have to create the configuration class that contains the beans which describe the flow of your batch job. The source code of your configuration class looks as follows:

1import org.springframework.context.annotation.Configuration;
2
3@Configuration
4public class SpringBatchExampleJobConfig {
5}

Second, you have to create the method that configures your ItemReader bean. This method must return an ItemReader<StudentDTO> object. After you have created this method, the source code of your configuration class looks as follows:

 1import org.springframework.batch.item.ItemReader;
 2import org.springframework.context.annotation.Bean;
 3import org.springframework.context.annotation.Configuration;
 4
 5@Configuration
 6public class SpringBatchExampleJobConfig {
 7
 8    @Bean
 9    public ItemReader<StudentDTO> itemReader() {
10
11    }
12}

Third, you have to ensure that the ItemReader() method returns a new InMemoryStudentReader object. After you have implemented the ItemReader() method, the source code of your configuration class looks as follows:

 1import org.springframework.batch.item.ItemReader;
 2import org.springframework.context.annotation.Bean;
 3import org.springframework.context.annotation.Configuration;
 4
 5@Configuration
 6public class SpringBatchExampleJobConfig {
 7
 8    @Bean
 9    public ItemReader<StudentDTO> itemReader() {
10        return new InMemoryStudentReader();
11    }
12}

You can now create a custom ItemReader class and you understand how you can configure an ItemReader bean which provides the input data for your batch job. Let’s summarize what you learned from this blog post.

Summary

This blog post has taught you four things:

  • You can create a custom ItemReader by implementing the ItemReader<T> interface.
  • When you implement the ItemReader<T> interface, you must provide the type of the returned object as a type parameter.
  • The T read() method of the ItemReader<T> interface must return the next T object. If the next object isn’t found, it must return null.
  • After you have created your custom ItemReader class, you have to configure the ItemReader bean that provides the input data for your Spring Batch job.

The next part of this tutorial describes how you can create a custom ItemReader that reads the input data of your batch job by using an external REST API.

P.S. You can get the example applications of this blog post from Github.