Spring Batch has a good support for reading data from different data sources such as files (CSV or XML) or databases. However, it doesn’t have a built-in support for reading input data from a REST API. If you want to use a REST API as a data source of your Spring Batch job, you have to implement a custom ItemReader which reads the input data from the REST API.
This blog post describes how you can implement your custom ItemReader. After you have read this blog post, you:
- Understand how you can implement an
ItemReaderwhich reads the input data of your batch job by using theRestTemplateclass. - Know how you can configure the
ItemReaderbean which provides the input data for your batch job.
Let’s begin.
This blog post assumes that:
- You are familiar with Spring Batch
- You can get the required dependencies with Maven or Gradle
- You can create a custom
ItemReader
Introduction to the Example Application
During this blog post you will implement an ItemReader which reads the input data of your Spring Batch job from a REST API endpoint that processes GET requests send to the path: ‘/api/student/’. This API endpoint returns the information of all students who are enrolled to an online course. To be more specific, your API endpoint returns the following JSON document:
1[
2 {
3 "emailAddress": "tony.tester@gmail.com",
4 "name": "Tony Tester",
5 "purchasedPackage": "master"
6 },
7 {
8 "emailAddress": "nick.newbie@gmail.com",
9 "name": "Nick Newbie",
10 "purchasedPackage": "starter"
11 },
12 {
13 "emailAddress": "ian.intermediate@gmail.com",
14 "name": "Ian Intermediate",
15 "purchasedPackage": "intermediate"
16 }
17]
You have to transform the returned JSON document into StudentDTO objects which are processed by your batch job. The StudentDTO class contains the information of a single student, 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}
Next, you will implement a custom ItemReader which reads the input data of your batch job from the described API endpoint.
Implementing Your Custom ItemReader
You can implement your custom ItemReader by following these steps:
First, you have to create a new class (RESTStudentReader) and implement the ItemReader interface. When you implement the ItemReader interface, you must set the type of the returned object to StudentDTO.
After you have created your ItemReader class, its source code looks as follows:
1import org.springframework.batch.item.ItemReader;
2
3class RESTStudentReader implements ItemReader<StudentDTO> {
4
5}
Second, you have to add the following private fields to the RESTStudentReader class:
- The
final apiUrlfield contains the url of the invoked REST API. - The
final RestTemplatefield contains a reference to theRestTemplateobject which you use when you read the student information. - The
nextStudentIndexfield contains the index of the nextStudentDTOobject. - The
studentDatafield contains the foundStudentDTOobjects.
After you have added these fields to the RESTStudentReader class, its source code looks as follows:
1import org.springframework.batch.item.ItemReader;
2import org.springframework.web.client.RestTemplate;
3
4import java.util.List;
5
6class RESTStudentReader implements ItemReader<StudentDTO> {
7
8 private final String apiUrl;
9 private final RestTemplate restTemplate;
10
11 private int nextStudentIndex;
12 private List<StudentDTO> studentData;
13}
Third, you have to add a constructor to the RESTStudentReader class and implement it by following these steps:
- Ensure that the constructor takes the url of the invoked REST API and a
RestTemplateobject as constructor arguments. - Implement the constructor by storing its constructor arguments in the fields of the created object. Set the value of the
nextStudentIndexfield to 0.
After you have implemented the constructor, the source code of the RESTStudentReader class looks as follows:
1import org.springframework.batch.item.ItemReader;
2import org.springframework.web.client.RestTemplate;
3
4import java.util.List;
5
6class RESTStudentReader implements ItemReader<StudentDTO> {
7
8 private final String apiUrl;
9 private final RestTemplate restTemplate;
10
11 private int nextStudentIndex;
12 private List<StudentDTO> studentData;
13
14 RESTStudentReader(String apiUrl, RestTemplate restTemplate) {
15 this.apiUrl = apiUrl;
16 this.restTemplate = restTemplate;
17 nextStudentIndex = 0;
18 }
19}
Fourth, you have to add a public read() method to the RESTStudentReader class and specify that the method returns a StudentDTO object. Also, you must ensure that this method can throw an Exception. After you have added this method to the RESTStudentReader class, you have to implement it by following these rules:
- If the student information hasn’t been read, read the student information by invoking the REST API.
- If the next student is found, return the found
StudentDTOobject and increase the value of thenextStudentIndexfield (the index of the next student) by 1. - If the next student isn’t found, return
null. Ensure that yourItemReaderreads the input data from the REST API when itsread()method is invoked for the next time (set the value of thenextStudentIndexfield to 0, and set the value of thestudentDatafield tonull).
After you have implemented the RESTStudentReader class, its source code looks as follows:
1import org.springframework.batch.item.ItemReader;
2import org.springframework.http.ResponseEntity;
3import org.springframework.web.client.RestTemplate;
4
5import java.util.Arrays;
6import java.util.List;
7
8class RESTStudentReader implements ItemReader<StudentDTO> {
9
10 private final String apiUrl;
11 private final RestTemplate restTemplate;
12
13 private int nextStudentIndex;
14 private List<StudentDTO> studentData;
15
16 RESTStudentReader(String apiUrl, RestTemplate restTemplate) {
17 this.apiUrl = apiUrl;
18 this.restTemplate = restTemplate;
19 nextStudentIndex = 0;
20 }
21
22 @Override
23 public StudentDTO read() throws Exception {
24 if (studentDataIsNotInitialized()) {
25 studentData = fetchStudentDataFromAPI();
26 }
27
28 StudentDTO nextStudent = null;
29
30 if (nextStudentIndex < studentData.size()) {
31 nextStudent = studentData.get(nextStudentIndex);
32 nextStudentIndex++;
33 }
34 else {
35 nextStudentIndex = 0;
36 studentData = null;
37 }
38
39 return nextStudent;
40 }
41
42 private boolean studentDataIsNotInitialized() {
43 return this.studentData == null;
44 }
45
46 private List<StudentDTO> fetchStudentDataFromAPI() {
47 ResponseEntity<StudentDTO[]> response = restTemplate.getForEntity(apiUrl,
48 StudentDTO[].class
49 );
50 StudentDTO[] studentData = response.getBody();
51 return Arrays.asList(studentData);
52 }
53}
Additional Reading:
Before you can use your new ItemReader, you have to configure the RestTemplate bean. Let’s move on and find out how you can configure this bean.
Configuring the RestTemplate Bean
You can configure the RestTemplate bean by following these steps:
- Add a
public restTemplate()method to your application context configuration class. Ensure that therestTemplate()method returns aRestTemplateobject and annotate it with the@Beanannotation. - Implement the
restTemplate()method by returning a newRestTemplateobject.
If you use Spring Framework, the source code of your application context configuration class looks as follows:
1import org.springframework.context.annotation.Bean;
2import org.springframework.context.annotation.Configuration;
3import org.springframework.web.client.RestTemplate;
4
5@Configuration
6public class SpringBatchExampleContext {
7
8 @Bean
9 public RestTemplate restTemplate() {
10 return new RestTemplate();
11 }
12}
If you use Spring Boot, you can also add the restTemplate() method to your application class which is annotated with the @SpringBootApplication annotation. The source code of this class looks as follows:
1import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
2import org.springframework.boot.SpringApplication;
3import org.springframework.boot.autoconfigure.SpringBootApplication;
4import org.springframework.context.annotation.Bean;
5import org.springframework.scheduling.annotation.EnableScheduling;
6import org.springframework.web.client.RestTemplate;
7
8@SpringBootApplication
9@EnableBatchProcessing
10@EnableScheduling
11public class SpringBatchExampleApplication {
12
13 @Bean
14 public RestTemplate restTemplate() {
15 return new RestTemplate();
16 }
17
18 public static void main(String[] args) {
19 SpringApplication.run(SpringBatchExampleApplication.class, args);
20 }
21}
You might have to add some additional dependencies to the classpath before you can configure the RestTemplate bean. These dependencies are described in the following:
- If you are using Spring Framework, you have to add the
spring-webmvcdependency to the classpath. - If you are using Spring Boot, you have to add the
spring-boot-starter-webdependency to the classpath.
After you have configured the RestTemplate bean, you can finally configure your ItemReader bean.
Configuring the ItemReader Bean
You can configure the ItemReader bean by following these steps:
First, you have to create a new configuration class. After you have created this class, its source code looks as follows:
1import org.springframework.context.annotation.Configuration;
2
3@Configuration
4public class SpringBatchExampleJobConfig {
5
6}
Second, you have to create a new method that configures your ItemReader bean. This method returns an ItemReader<StudentDTO> object, and it takes an Environment object and a RestTemplate object as method parameters.
After you have added this method to your configuration class, its source code looks as follows:
1import org.springframework.batch.item.ItemReader;
2import org.springframework.context.annotation.Bean;
3import org.springframework.context.annotation.Configuration;
4import org.springframework.core.env.Environment;
5import org.springframework.web.client.RestTemplate;
6
7@Configuration
8public class SpringBatchExampleJobConfig {
9
10 @Bean
11 public ItemReader<StudentDTO> itemReader(Environment environment,
12 RestTemplate restTemplate) {
13
14 }
15}
Fourth, you have to implement the itemReader() method by returning a new RESTStudentReader object. When you create a new RESTStudentReader object, you have to pass the following objects as constructor arguments:
- The url of the invoked REST API. You can read this information from a properties file by using the
Environmentobject given as a method parameter. - The
RestTemplateobject which is used to query the student information from the invoked REST API.
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;
4import org.springframework.core.env.Environment;
5import org.springframework.web.client.RestTemplate;
6
7@Configuration
8public class SpringBatchExampleJobConfig {
9
10 @Bean
11 public ItemReader<StudentDTO> itemReader(Environment environment,
12 RestTemplate restTemplate) {
13 return new RESTStudentReader(environment.getRequiredProperty("rest.api.url"),
14 restTemplate
15 );
16 }
17}
You can now write a custom ItemReader which reads the input data of your batch job from a REST API. Let’s summarize what you learned from this blog post.
Summary
This blog post has taught you two things:
- Spring Batch doesn’t have an
ItemReaderthat can read information from a REST API. - If you want to read the input data of your batch job from a REST API, you can read this information by using the
RestTemplateclass.
The next part of this tutorial describes how you can read the input data of your batch job from an Excel spreadsheet.
P.S. You can get the example application of this blog post from Github.