In my previous post, I explained how to configure job retry if job fails in first attempt.
In this post under Spring Batch, I will explain with example how to configure RetryListener.
With the help of RetryListener you can listen to error events and respond appropriately.
To create an exception that will force the job to retry, I will use ExceptionSimulator class as shown below
ExceptionSimulator
package package32;
import org.springframework.batch.item.ItemProcessor;
public class ExceptionSimulator implements ItemProcessor<Employee, Employee> {
int count = 0;
@Override
public Employee process(Employee employee) throws Exception {
Employee employee1 = null;
if (count % 2 == 0) {
employee1 = employee;
count = count + 1;
} else if (count %2 == 1) {
count = count + 1;
throw new IllegalArgumentException();
}
return employee1;
}
}
Employee records at even position are returned as it is but records at odd position will throw IllegalArgumentException.
Next we will create a listener class that implements org.springframework.retry.RetryListener interface as shown below
CustomRetryListener
1 package package32;
2
3 import org.springframework.retry.RetryCallback;
4 import org.springframework.retry.RetryContext;
5 import org.springframework.retry.RetryListener;
6
7 public class CustomRetryListener implements RetryListener {
8 @Override
9 public <T, E extends Throwable> void close(RetryContext retryContext, RetryCallback<T, E> retryCallback, Throwable throwable) {
10 }
11
12 @Override
13 public <T, E extends Throwable> void onError(RetryContext retryContext, RetryCallback<T, E> retryCallback, Throwable throwable) {
14 System.out.println("Retry Count: " + retryContext.getRetryCount());
15 }
16
17 @Override
18 public <T, E extends Throwable> boolean open(RetryContext retryContext, RetryCallback<T, E> retryCallback) {
19 return true;
20 }
21 }
As shown in the above code, we create CustomRetryListener class implementing RetryListener interface. The interface has three methods “close”, “onError”, and “open”. We need to focus on “onError” and “open” method.
We need to implement the “open” method and its return value should always be true if we want the listener “onError” method to be called. If false, “onError” method will not be called. The method “onError” will be called when an error happens.
Next we have to configure the listener with the job as shown in the below xml
1 <bean id="customRetryListener" class="package32.CustomRetryListener"/>
2
3 <batch:job id="importEmployees">
4 <batch:step id="readWriteEmployees">
5 <batch:tasklet start-limit="2">
6 <batch:chunk reader="reader" writer="writer" commit-interval="50" retry-limit="5" processor="exceptionSimulator">
7 <batch:retryable-exception-classes>
8 <batch:include class="java.lang.IllegalArgumentException"/>
9 </batch:retryable-exception-classes>
10 <batch:retry-listeners>
11 <batch:listener ref="customRetryListener"/>
12 </batch:retry-listeners>
13 </batch:chunk>
14 </batch:tasklet>
15 </batch:step>
16 </batch:job>
At line 1 we create a bean named “customRetryListener” and at line 11 we set the bean as a retry listener.
Below is the complete xml configuration for your reference
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:batch="http://www.springframework.org/schema/batch"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/batch http://www.springframework.org/schema/batch/spring-batch.xsd">
<bean id="employee" class="package32.Employee" scope="prototype"/>
<bean id="reader" class="org.springframework.batch.item.file.FlatFileItemReader">
<property name="resource" value="file:FileInput.txt"/>
<property name="lineMapper">
<bean class="org.springframework.batch.item.file.mapping.DefaultLineMapper">
<property name="lineTokenizer">
<bean class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer">
<property name="names" value="id,name,status,salary"/>
</bean>
</property>
<property name="fieldSetMapper">
<bean class="org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper">
<property name="prototypeBeanName" value="employee"/>
</bean>
</property>
</bean>
</property>
</bean>
<bean id="writer" class="org.springframework.batch.item.file.FlatFileItemWriter">
<property name="resource" value="file:FileOutput.txt"/>
<property name="lineAggregator">
<bean class="org.springframework.batch.item.file.transform.DelimitedLineAggregator">
<property name="fieldExtractor">
<bean class="org.springframework.batch.item.file.transform.BeanWrapperFieldExtractor">
<property name="names" value="id,name,status,salary"/>
</bean>
</property>
</bean>
</property>
</bean>
<bean id="exceptionSimulator" class="package32.ExceptionSimulator" />
<bean id="customRetryListener" class="package32.CustomRetryListener"/>
<batch:job id="importEmployees">
<batch:step id="readWriteEmployees">
<batch:tasklet start-limit="2">
<batch:chunk reader="reader" writer="writer" commit-interval="50" retry-limit="5" processor="exceptionSimulator">
<batch:retryable-exception-classes>
<batch:include class="java.lang.IllegalArgumentException"/>
</batch:retryable-exception-classes>
<batch:retry-listeners>
<batch:listener ref="customRetryListener"/>
</batch:retry-listeners>
</batch:chunk>
</batch:tasklet>
</batch:step>
</batch:job>
<bean id="transactionManager" class="org.springframework.batch.support.transaction.ResourcelessTransactionManager" />
<bean id="jobRepository" class="org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean">
<property name="transactionManager" ref="transactionManager"/>
</bean>
<bean id="jobLauncher" class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
<property name="jobRepository" ref="jobRepository"/>
</bean>
</beans>