-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Description
Hello Spring Batch team
Bug description
When using ChunkOrientedStepBuilder.skip() to specify skippable exceptions without calling skipLimit(), the step builder fails with an IllegalArgumentException during the build phase. This occurs because skipLimit defaults to -1, which is rejected by LimitCheckingExceptionHierarchySkipPolicy constructor.
This is the same root cause as the retry configuration issue (#5068), where the default value is -1 and the validation logic rejects it.
Environment
- Spring Batch version: 6.0.0-RC1
- Spring Framework version: 7.0.0-RC2
Steps to reproduce
- Create a chunk-oriented step using
StepBuilder - Call
.skip(SomeException.class)without calling.skipLimit() - Attempt to build the step
Expected behavior
The step should build successfully without throwing an exception.
Actual behavior
Exception thrown:
Caused by: java.lang.IllegalArgumentException: The skipLimit must be greater than zero
at org.springframework.util.Assert.isTrue(Assert.java:117)
at org.springframework.batch.core.step.skip.LimitCheckingExceptionHierarchySkipPolicy.<init>(LimitCheckingExceptionHierarchySkipPolicy.java:45)
at org.springframework.batch.core.step.builder.ChunkOrientedStepBuilder.build(ChunkOrientedStepBuilder.java:415)
Minimal Complete Reproducible example
@Configuration
public class IssueReproductionJobConfiguration {
@Bean
public Job issueReproductionJob(JobRepository jobRepository, Step issueReproductionStep) {
return new JobBuilder(jobRepository)
.start(issueReproductionStep)
.build();
}
@Bean
public Step issueReproductionStep(JobRepository jobRepository) {
AtomicInteger counter = new AtomicInteger(0);
return new StepBuilder(jobRepository)
.chunk(5)
.reader(() -> {
int count = counter.incrementAndGet();
if (count <= 5) {
return "kill-" + count;
}
return null;
})
.writer(items -> items.forEach(item ->
System.out.println("💀 Terminated: " + item)
))
.faultTolerant()
.skip(IOException.class)
//.skipLimit(1) // ← This must be added for proper operation
.build(); // ← IllegalArgumentException thrown here
}
}Root cause analysis
In ChunkOrientedStepBuilder:
private long skipLimit = -1; // Default value
public ChunkOrientedStep build() {
// ...
if (this.skipPolicy == null) {
// This condition uses OR, so it's true when only skippableExceptions is set
if (!this.skippableExceptions.isEmpty() || this.skipLimit > 0) {
this.skipPolicy = new LimitCheckingExceptionHierarchySkipPolicy(
this.skippableExceptions,
this.skipLimit // ← Passes -1 here
); // ← Fails here
}
else {
this.skipPolicy = new AlwaysSkipItemSkipPolicy();
}
}
// ...
}In LimitCheckingExceptionHierarchySkipPolicy:
public LimitCheckingExceptionHierarchySkipPolicy(
Set<Class> skippableExceptions,
long skipLimit) {
Assert.notEmpty(skippableExceptions, "The skippableExceptions must not be empty");
Assert.isTrue(skipLimit > 0, "The skipLimit must be greater than zero"); // ← Rejects -1
this.skippableExceptions = skippableExceptions;
this.skipLimit = skipLimit;
}Suggested fixes
Option 1: Change default value
private long skipLimit = 0; // Change from -1 to 0Option 2: Add documentation
Add JavaDoc to skip() method stating that skipLimit() must be called with a positive value (greater than 0).
/**
* Configure exceptions that should be skipped.
* Note: {@link #skipLimit(long)} must be called with a positive value
* before building the step when using this method.
* @param skippableExceptions exceptions to skip
* @return this for fluent chaining
*/
@SafeVarargs
public final ChunkOrientedStepBuilder skip(Class... skippableExceptions)Thank you for reviewing this issue. Please let me know if you need any additional information or clarification.