In this post under Spring Batch, I will explain with an example how to use Spring Batch provided ExceptionClassifierSkipPolicy class.
ExceptionClassifierSkipPolicy will not contain the logic of how to handle exceptions when thrown. Instead it is a delegator class which passes the exceptions thrown to appropriate exception handling SkipPolicy classes.
ExceptionClassifierSkipPolicy will contain a map where key will be exception class object and value will be a reference to appropriate exception handling skip policy class.
For our example, we will create a simulator class which will throw IllegalArgumentException and NullPointerException when reading records.
Below is the code of simulator class
ExceptionSimulator
1 package package29;
2
3 import org.springframework.batch.item.ItemProcessor;
4
5 public class ExceptionSimulator implements ItemProcessor<Employee, Employee> {
6 int count = 0;
7
8 @Override
9 public Employee process(Employee employee) throws Exception {
10 Employee employee1 = null;
11
12 if(count == 0) {
13 employee1 = employee;
14 count = count + 1;
15 } else if (count == 1) {
16 count = count + 1;
17 throw new NullPointerException();
18 } else if (count == 2) {
19 count = 0;
20 throw new IllegalArgumentException();
21 }
22 return employee1;
23 }
24 }
In the above code, we are throwing NullPointerException for every second record and IllegalArgumentException for every third record.
Next we will create class that implement skip policies for these two exceptions
IllegalArgumentExceptionSkipPolicy
1 package package29;
2
3 import org.springframework.batch.core.step.skip.SkipLimitExceededException;
4 import org.springframework.batch.core.step.skip.SkipPolicy;
5
6 public class IllegalArgumentExceptionSkipPolicy implements SkipPolicy {
7 int count = 0;
8
9 @Override
10 public boolean shouldSkip(Throwable throwable, int skipCount) throws SkipLimitExceededException {
11 if(count == 6) {
12 return false;
13 } else {
14 System.out.println("IllegalArgumentException: " + count);
15 count = count + 1;
16 return true;
17 }
18 }
19 }
In the above Skip policy class, whenever IllegalArgumentException is thrown, it will be printed in the console but if the number of such exception reaches 6, the processing of records is stopped.
NullPointerExceptionSkipPolicy
1 package package29;
2
3 import org.springframework.batch.core.step.skip.SkipLimitExceededException;
4 import org.springframework.batch.core.step.skip.SkipPolicy;
5
6 public class NullPointerExceptionSkipPolicy implements SkipPolicy {
7 int count = 0;
8
9 @Override
10 public boolean shouldSkip(Throwable throwable, int skipCount) throws SkipLimitExceededException {
11 if(count == 3) {
12 return false;
13 } else {
14 System.out.println("NullPointerException: " + count);
15 count = count + 1;
16 return true;
17 }
18 }
19 }
In the above Skip policy class, whenever NullPointerException is thrown, it will be printed in the console but if the number of such exception reaches 3, the processing of records is stopped.
So different exceptions will have different policies.
Next we integrate this with ExceptionClassifierSkipPolicy class in the xml as shown below
1 <bean id="nullPointerExceptionSkipPolicy" class="package29.NullPointerExceptionSkipPolicy"/>
2
3 <bean id="illegalArgumentExceptionSkipPolicy" class="package29.IllegalArgumentExceptionSkipPolicy"/>
4
5 <bean id="exceptionClassifierSkipPolicy" class="org.springframework.batch.core.step.skip.ExceptionClassifierSkipPolicy">
6 <property name="policyMap">
7 <util:map key-type="java.lang.Class">
8 <entry key="java.lang.NullPointerException" value-ref="nullPointerExceptionSkipPolicy" />
9 <entry key="java.lang.IllegalArgumentException" value-ref="illegalArgumentExceptionSkipPolicy" />
10 </util:map>
11 </property>
12 </bean>
In the above xml code, at line 1 we create a bean definition for class NullPointerExceptionSkipPolicy.
At line 3 we create a bean definition for class IllegalArgumentExceptionSkipPolicy
Then at line 5 we create a bean definition for ExceptionClassifierSkipPolicy. It has property by name “policyMap” of type
Map<java.lang.Class<? extends java.lang.Throwable>,SkipPolicy>
From line 7 to 10 we create a map where we refer to the beans created at line 1 and 3. The we set the map to the property “policyMap” at line 6.
Below is the complete output
Output
Mar 28, 2020 11:37:18 AM org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@5b80350b: startup date [Sat Mar 28 11:37:18 IST 2020]; root of context hierarchy
Mar 28, 2020 11:37:18 AM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [package29/job.xml]
Mar 28, 2020 11:37:19 AM org.springframework.batch.core.launch.support.SimpleJobLauncher afterPropertiesSet
INFO: No TaskExecutor has been set, defaulting to synchronous executor.
Mar 28, 2020 11:37:19 AM org.springframework.batch.core.launch.support.SimpleJobLauncher run
INFO: Job: [FlowJob: [name=importEmployees]] launched with the following parameters: [{date=1585375639784}]
Mar 28, 2020 11:37:19 AM org.springframework.batch.core.job.SimpleStepHandler handleStep
INFO: Executing step: [readWriteEmployees]
IllegalArgumentException: 0
NullPointerException: 0
IllegalArgumentException: 1
NullPointerException: 1
IllegalArgumentException: 2
NullPointerException: 2
IllegalArgumentException: 3
Mar 28, 2020 11:37:19 AM org.springframework.batch.core.step.AbstractStep execute
SEVERE: Encountered an error executing step readWriteEmployees in job importEmployees
org.springframework.retry.RetryException: Non-skippable exception in recoverer while processing; nested exception is java.lang.NullPointerException
at org.springframework.batch.core.step.item.FaultTolerantChunkProcessor$2.recover(FaultTolerantChunkProcessor.java:283)
at org.springframework.retry.support.RetryTemplate.handleRetryExhausted(RetryTemplate.java:512)
at org.springframework.retry.support.RetryTemplate.doExecute(RetryTemplate.java:351)
at org.springframework.retry.support.RetryTemplate.execute(RetryTemplate.java:211)
at org.springframework.batch.core.step.item.BatchRetryTemplate.execute(BatchRetryTemplate.java:217)
at org.springframework.batch.core.step.item.FaultTolerantChunkProcessor.transform(FaultTolerantChunkProcessor.java:292)
at org.springframework.batch.core.step.item.SimpleChunkProcessor.process(SimpleChunkProcessor.java:202)
at org.springframework.batch.core.step.item.ChunkOrientedTasklet.execute(ChunkOrientedTasklet.java:75)
at org.springframework.batch.core.step.tasklet.TaskletStep$ChunkTransactionCallback.doInTransaction(TaskletStep.java:406)
at org.springframework.batch.core.step.tasklet.TaskletStep$ChunkTransactionCallback.doInTransaction(TaskletStep.java:330)
at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:140)
at org.springframework.batch.core.step.tasklet.TaskletStep$2.doInChunkContext(TaskletStep.java:272)
at org.springframework.batch.core.scope.context.StepContextRepeatCallback.doInIteration(StepContextRepeatCallback.java:81)
at org.springframework.batch.repeat.support.RepeatTemplate.getNextResult(RepeatTemplate.java:375)
at org.springframework.batch.repeat.support.RepeatTemplate.executeInternal(RepeatTemplate.java:215)
at org.springframework.batch.repeat.support.RepeatTemplate.iterate(RepeatTemplate.java:145)
at org.springframework.batch.core.step.tasklet.TaskletStep.doExecute(TaskletStep.java:257)
at org.springframework.batch.core.step.AbstractStep.execute(AbstractStep.java:200)
at org.springframework.batch.core.job.SimpleStepHandler.handleStep(SimpleStepHandler.java:148)
at org.springframework.batch.core.job.flow.JobFlowExecutor.executeStep(JobFlowExecutor.java:66)
at org.springframework.batch.core.job.flow.support.state.StepState.handle(StepState.java:67)
at org.springframework.batch.core.job.flow.support.SimpleFlow.resume(SimpleFlow.java:169)
at org.springframework.batch.core.job.flow.support.SimpleFlow.start(SimpleFlow.java:144)
at org.springframework.batch.core.job.flow.FlowJob.doExecute(FlowJob.java:136)
at org.springframework.batch.core.job.AbstractJob.execute(AbstractJob.java:308)
at org.springframework.batch.core.launch.support.SimpleJobLauncher$1.run(SimpleJobLauncher.java:141)
at org.springframework.core.task.SyncTaskExecutor.execute(SyncTaskExecutor.java:50)
at org.springframework.batch.core.launch.support.SimpleJobLauncher.run(SimpleJobLauncher.java:134)
at package29.Example29.main(Example29.java:23)
Caused by: java.lang.NullPointerException
at package29.ExceptionSimulator.process(ExceptionSimulator.java:17)
at package29.ExceptionSimulator.process(ExceptionSimulator.java:1)
at org.springframework.batch.core.step.item.SimpleChunkProcessor.doProcess(SimpleChunkProcessor.java:126)
at org.springframework.batch.core.step.item.FaultTolerantChunkProcessor$1.doWithRetry(FaultTolerantChunkProcessor.java:227)
at org.springframework.retry.support.RetryTemplate.doExecute(RetryTemplate.java:287)
… 26 more
Mar 28, 2020 11:37:19 AM org.springframework.batch.core.launch.support.SimpleJobLauncher run
INFO: Job: [FlowJob: [name=importEmployees]] completed with the following parameters: [{date=1585375639784}] and the following status: [FAILED]
Below is the complete configuration file for your reference
Complete xml configuration file
<?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"
xmlns:util="http://www.springframework.org/schema/util"
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
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<bean id="employee" class="package29.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="package29.ExceptionSimulator" />
<bean id="nullPointerExceptionSkipPolicy" class="package29.NullPointerExceptionSkipPolicy"/>
<bean id="illegalArgumentExceptionSkipPolicy" class="package29.IllegalArgumentExceptionSkipPolicy"/>
<bean id="exceptionClassifierSkipPolicy" class="org.springframework.batch.core.step.skip.ExceptionClassifierSkipPolicy">
<property name="policyMap">
<util:map key-type="java.lang.Class">
<entry key="java.lang.NullPointerException" value-ref="nullPointerExceptionSkipPolicy" />
<entry key="java.lang.IllegalArgumentException" value-ref="illegalArgumentExceptionSkipPolicy" />
</util:map>
</property>
</bean>
<batch:job id="importEmployees">
<batch:step id="readWriteEmployees">
<batch:tasklet>
<batch:chunk reader="reader" writer="writer" processor="exceptionSimulator" commit-interval="50" skip-policy="exceptionClassifierSkipPolicy"/>
</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>