Repeat & retry internals
|
This section documents the current Spring Batch line — 6.0.x, on Spring Framework 7 and Spring Boot 4.1.x, with a Java 17+ baseline — as published at the Spring Batch reference documentation. No specific patch version is pinned. Some surfaces (Spring Cloud Task and Spring Cloud Data Flow orchestration, the deployer-based partition handler, and JSR-352) are linked, not documented in depth. This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. |
The chunk loop and the fault-tolerance options are conveniences over two general-purpose abstractions:
RepeatOperations, which runs something repeatedly until a policy says stop, and RetryOperations, which
runs something again after a failure. Both are usable on their own.
RepeatOperations
public interface RepeatOperations {
RepeatStatus iterate(RepeatCallback callback) throws RepeatException;
}
RepeatTemplate is the implementation. It calls the callback in a loop, passing a RepeatContext, and stops
when the callback returns RepeatStatus.FINISHED or the CompletionPolicy says the batch is complete:
RepeatTemplate template = new RepeatTemplate();
template.setCompletionPolicy(new SimpleCompletionPolicy(100)); // at most 100 iterations
template.setExceptionHandler(new SimpleLimitExceptionHandler(5)); // tolerate 5 failures, then rethrow
template.iterate(context -> {
Trade trade = tradeQueue.poll();
if (trade == null) {
return RepeatStatus.FINISHED;
}
tradeService.settle(trade);
return RepeatStatus.CONTINUABLE;
});
This is exactly the shape of the chunk loop in Chunk-oriented processing: "read and process until the completion policy says the chunk is full".
CompletionPolicy
| Policy | Stops the batch when |
|---|---|
|
A fixed number of iterations has run. This is what an integer chunk size creates. |
|
A wall-clock budget has elapsed — the last iteration is allowed to finish. |
|
A counter derived from the result (bytes, records, cost) reaches a limit. |
|
Any (or all) of several policies is satisfied. |
A custom policy is a small class, and it is how "commit whenever the business key changes" is expressed:
public class KeyChangeCompletionPolicy extends CompletionPolicySupport {
private String currentKey;
@Override
public boolean isComplete(RepeatContext context, RepeatStatus status) {
return status == RepeatStatus.FINISHED;
}
@Override
public void update(RepeatContext context) {
String key = (String) context.getAttribute("account.key");
if (currentKey != null && !currentKey.equals(key)) {
context.setCompleteOnly(); // close the chunk at the key boundary
}
currentKey = key;
}
}
ExceptionHandler
The ExceptionHandler decides whether an exception thrown inside an iteration ends the loop.
SimpleLimitExceptionHandler rethrows only after N occurrences — the mechanism behind a step’s skip limit.
LogOrRethrowExceptionHandler classifies exceptions into "log and continue" and "rethrow".
RepeatTemplate can also be given a TaskExecutor (TaskExecutorRepeatTemplate) to run iterations
concurrently — the basis of the multi-threaded step in
Scaling & parallel processing.
RetryOperations
Retry is the other half. Spring Framework 7 ships its own core retry support — the types live in
org.springframework.core.retry, and this is what the newer ChunkOrientedStepBuilder uses. Note that the
.faultTolerant() builder is still on the Spring Retry library
(Fault tolerance: skip & retry), so the policy
below applies to the former, not the latter:
RetryTemplate retryTemplate = new RetryTemplate();
retryTemplate.setRetryPolicy(RetryPolicy.builder()
.maxAttempts(3)
.includes(TransientDataAccessException.class, RemoteAccessException.class)
.excludes(DataIntegrityViolationException.class)
.delay(Duration.ofMillis(200))
.multiplier(2.0)
.maxDelay(Duration.ofSeconds(5))
.build());
Trade settled = retryTemplate.execute(() -> settlementClient.settle(trade));
The pieces:
-
RetryPolicy— how many attempts, which exceptions qualify, and the delay between attempts. A custom policy can inspect the exception and the attempt count. -
BackOffPolicy— fixed, exponential or exponential-with-jitter delays. Retrying a contended resource without back-off usually reproduces the same failure; jitter additionally prevents a thundering herd when many workers fail together. -
RetryListener— callbacks on each failed attempt and on exhaustion; the natural place for logging and metrics (Observability). -
Recovery — a fallback invoked when the attempts are exhausted, so the caller gets a degraded result instead of an exception.
Stateless vs. stateful retry
| Behaviour | |
|---|---|
Stateless |
All attempts happen inside one call, in a loop, on the same thread and inside the same transaction. Right for a failure that does not poison the transaction — a flaky remote call, a read timeout. |
Stateful |
The failure aborts the transaction; the retry happens on a subsequent call, with the attempt count keyed by an item identity and stored in a retry context. Right for a database failure that marks the transaction rollback-only, such as a deadlock — there is no point retrying inside a transaction that can no longer commit. |
This distinction is why a fault-tolerant chunk step retries the way it does: because the chunk’s transaction has already rolled back, the framework replays the chunk in a new transaction and keys the retry state by item — stateful retry, applied automatically.
How a fault-tolerant step maps onto these
| Step-level setting | Underlying mechanism |
|---|---|
|
A |
|
The same, with the supplied |
|
A |
|
The |
|
An |
|
A rollback classifier consulted before the transaction is marked rollback-only. |
Nearly all batch code should stay at the step level — .faultTolerant().skip(…).retry(…) as described in
Fault tolerance: skip & retry. Reach for
RetryTemplate or RepeatTemplate directly only inside a tasklet, a custom reader or writer, or a service
that is called from batch and online code alike.