Bug description
When an application uses a dedicated exception as control flow to skip an item, the exception is accepted by the
configured skip policy and the SkipListener is invoked exactly once. The job completes successfully.
The logging differs between the deprecated TaskletStep/FaultTolerantChunkProcessor path and the current
ChunkOrientedStep path:
-
The deprecated path logs the corresponding scan transition at DEBUG without an exception argument. This is
effectively invisible with normal production logging.
-
ChunkOrientedStep logs the same expected control-flow transition at INFO and attaches a RetryException:
if (this.faultTolerant && exception instanceof RetryException retryException
&& this.skipPolicy.shouldSkip(retryException.getCause(), -1)) {
logger.info("Retry exhausted, entering scan mode for next transaction", retryException);
// ...
}
-
The rollback path also logs an unconditional ERROR:
catch (Exception e) {
logger.error("Rolling back chunk transaction", e);
status.setRollbackOnly();
// ...
}
The wording Retry exhausted is misleading when no .retryable(...) exception is configured. In this case, the
default retry policy performs zero retries. The message means only that the write failed and the skip policy accepted
the exception. The rollback is also an expected part of the skip/scan flow, not an application failure.
Environment
- Spring Batch:
6.0.5
- Spring Boot:
4.1.1
- Java:
17+ (the attached project was run with Java 25)
- Build tool: Maven
3+
- Database: none; the reproducer uses
ResourcelessJobRepository and
ResourcelessTransactionManager
- Logging: Spring Boot starter logging with SLF4J and Logback
- Operating system: macOS (the behavior is not expected to be OS-specific)
Steps to reproduce
-
Download and extract the attached ZIP.
-
Change into the extracted project directory.
-
Run:
mvn clean spring-boot:run -Dspring-boot.run.arguments=--spring.main.web-application-type=none
-
Observe the logs for both taskletJob and chunkOrientedJob.
The application configures a writer that throws DeliberateSkipException for one item, configures that exception as
skippable, and does not configure any retryable exception. The SkipListener logs one application-level skip
message, and both jobs finish with COMPLETED.
The chunkOrientedJob output includes messages equivalent to:
INFO ... ChunkOrientedStep - Retry exhausted, entering scan mode for next transaction
org.springframework.core.retry.RetryException: Retry policy for operation 'Retryable write operation' exhausted; aborting execution
ERROR ... ChunkOrientedStep - Rolling back chunk transaction
org.springframework.core.retry.RetryException: Retry policy for operation 'Retryable write operation' exhausted; aborting execution
INFO ... ChunkOrientedStep - Rollback complete, scan will execute in next transaction
INFO ... ChunkOrientedStep - Executing scan in new transaction after rollback
The taskletJob uses the deprecated chunk-builder overload and produces the corresponding scan transition without the
same INFO message and exception stack trace.
Expected behavior
An expected skip/scan transition should not emit misleading INFO and ERROR messages with exception stack traces
when no retry is configured and the job completes successfully. The diagnostic logging should reflect that this is an
accepted skip and expected rollback, rather than an exhausted retry or application failure.
In particular:
- When no retryable exception is configured, the scan transition should not be described as
Retry exhausted.
- An exception accepted by the skip policy should not produce an unconditional
ERROR log merely because the chunk
transaction is rolled back as part of the expected skip/scan algorithm.
- This diagnostic logging differs from the legacy
FaultTolerantChunkProcessor path and is not described in the
Spring Batch 6 reference documentation or migration guide.
- The expected skip/scan transition should either be logged at an appropriate lower level without the internal
RetryException, or use wording that accurately describes the accepted skip and expected transaction rollback.
The job should continue to invoke the SkipListener once and complete successfully, as it does in this reproducer.
Minimal Complete Reproducible example
The attached ZIP contains a complete Maven Spring Boot application. It contains:
pom.xml with spring-boot-starter-batch and Spring Batch 6.0.5 resolved by Spring Boot dependency management.
src/main/java/com/example/batch/SkipLoggingReproducerApplication.java with both execution paths.
- An in-memory, database-free setup using
ResourcelessJobRepository.
- A writer that throws a dedicated exception for one item.
- A skip policy and
SkipListener that demonstrate that the item is skipped successfully.
The project was verified with:
Both jobs complete with status COMPLETED; the issue is the misleading and inconsistent logging during the expected
skip/scan transition, not a failed job.
spring-batch-skip-logging-reproducer.zip
Bug description
When an application uses a dedicated exception as control flow to skip an item, the exception is accepted by the
configured skip policy and the
SkipListeneris invoked exactly once. The job completes successfully.The logging differs between the deprecated
TaskletStep/FaultTolerantChunkProcessorpath and the currentChunkOrientedSteppath:The deprecated path logs the corresponding scan transition at
DEBUGwithout an exception argument. This iseffectively invisible with normal production logging.
ChunkOrientedSteplogs the same expected control-flow transition atINFOand attaches aRetryException:The rollback path also logs an unconditional
ERROR:The wording
Retry exhaustedis misleading when no.retryable(...)exception is configured. In this case, thedefault retry policy performs zero retries. The message means only that the write failed and the skip policy accepted
the exception. The rollback is also an expected part of the skip/scan flow, not an application failure.
Environment
6.0.54.1.117+(the attached project was run with Java25)3+ResourcelessJobRepositoryandResourcelessTransactionManagerSteps to reproduce
Download and extract the attached ZIP.
Change into the extracted project directory.
Run:
Observe the logs for both
taskletJobandchunkOrientedJob.The application configures a writer that throws
DeliberateSkipExceptionfor one item, configures that exception asskippable, and does not configure any retryable exception. The
SkipListenerlogs one application-level skipmessage, and both jobs finish with
COMPLETED.The
chunkOrientedJoboutput includes messages equivalent to:The
taskletJobuses the deprecated chunk-builder overload and produces the corresponding scan transition without thesame
INFOmessage and exception stack trace.Expected behavior
An expected skip/scan transition should not emit misleading
INFOandERRORmessages with exception stack traceswhen no retry is configured and the job completes successfully. The diagnostic logging should reflect that this is an
accepted skip and expected rollback, rather than an exhausted retry or application failure.
In particular:
Retry exhausted.ERRORlog merely because the chunktransaction is rolled back as part of the expected skip/scan algorithm.
FaultTolerantChunkProcessorpath and is not described in theSpring Batch 6 reference documentation or migration guide.
RetryException, or use wording that accurately describes the accepted skip and expected transaction rollback.The job should continue to invoke the
SkipListeneronce and complete successfully, as it does in this reproducer.Minimal Complete Reproducible example
The attached ZIP contains a complete Maven Spring Boot application. It contains:
pom.xmlwithspring-boot-starter-batchand Spring Batch6.0.5resolved by Spring Boot dependency management.src/main/java/com/example/batch/SkipLoggingReproducerApplication.javawith both execution paths.ResourcelessJobRepository.SkipListenerthat demonstrate that the item is skipped successfully.The project was verified with:
Both jobs complete with status
COMPLETED; the issue is the misleading and inconsistent logging during the expectedskip/scan transition, not a failed job.
spring-batch-skip-logging-reproducer.zip