Skip to content

Commit e3ff2e6

Browse files
committed
Fix TaskletStep to propagate checked exceptions when rollback is disabled
Fixes spring-projects#5316 Propagate checked exceptions from Tasklet.execute even when transactionAttribute.rollbackOn(e) returns false. Only mark rollback metadata (ROLLBACK_EXCEPTION_KEY and rollback count) when rollback is actually configured for the exception. Also add regression tests for PROPAGATION_NOT_SUPPORTED to ensure a checked exception fails the step instead of being silently retried. Signed-off-by: Nikita Nagar <permanayan84@gmail.com>
1 parent c357979 commit e3ff2e6

File tree

2 files changed

+60
-1
lines changed

2 files changed

+60
-1
lines changed

spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/TaskletStep.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -417,8 +417,8 @@ public RepeatStatus doInTransaction(TransactionStatus status) {
417417
catch (Exception e) {
418418
if (transactionAttribute.rollbackOn(e)) {
419419
chunkContext.setAttribute(ChunkListener.ROLLBACK_EXCEPTION_KEY, e);
420-
throw e;
421420
}
421+
throw e;
422422
}
423423
}
424424
finally {

spring-batch-core/src/test/java/org/springframework/batch/core/step/item/TaskletStepExceptionTests.java

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import org.jspecify.annotations.Nullable;
4040
import org.springframework.transaction.TransactionException;
4141
import org.springframework.transaction.UnexpectedRollbackException;
42+
import org.springframework.transaction.interceptor.DefaultTransactionAttribute;
4243
import org.springframework.transaction.support.DefaultTransactionStatus;
4344
import org.springframework.transaction.support.TransactionSynchronizationManager;
4445

@@ -574,6 +575,64 @@ public JobInstance createJobInstance(String jobName, JobParameters jobParameters
574575

575576
}
576577

578+
@Test
579+
void testCheckedExceptionPropagatedWhenRollbackDisabled() throws Exception {
580+
// Test for regression of issue where checked exceptions were swallowed
581+
// when transactionAttribute.rollbackOn(ex) returned false
582+
taskletStep.setTransactionAttribute(new DefaultTransactionAttribute() {
583+
@Override
584+
public boolean rollbackOn(Throwable ex) {
585+
// Simulate PROPAGATION_NOT_SUPPORTED behavior - no rollback for checked exceptions
586+
if (ex instanceof RuntimeException) {
587+
return true;
588+
}
589+
return false;
590+
}
591+
});
592+
593+
// Create a tasklet that throws a checked exception
594+
taskletStep.setTasklet(new Tasklet() {
595+
@Nullable
596+
@Override
597+
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
598+
throw new Exception("Checked exception from tasklet");
599+
}
600+
});
601+
602+
// Execute should fail with the checked exception
603+
taskletStep.execute(stepExecution);
604+
assertEquals(FAILED, stepExecution.getStatus());
605+
assertTrue(stepExecution.getFailureExceptions().get(0) instanceof Exception);
606+
assertEquals("Checked exception from tasklet",
607+
stepExecution.getFailureExceptions().get(0).getMessage());
608+
}
609+
610+
@Test
611+
void testRuntimeExceptionPropagatedWhenRollbackDisabled() throws Exception {
612+
// Even for runtime exceptions, they should be propagated
613+
taskletStep.setTransactionAttribute(new DefaultTransactionAttribute() {
614+
@Override
615+
public boolean rollbackOn(Throwable ex) {
616+
// Disable rollback for all exceptions
617+
return false;
618+
}
619+
});
620+
621+
// Create a tasklet that throws a runtime exception
622+
taskletStep.setTasklet(new Tasklet() {
623+
@Nullable
624+
@Override
625+
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
626+
throw new RuntimeException("Runtime exception from tasklet");
627+
}
628+
});
629+
630+
// Execute should fail with the runtime exception
631+
taskletStep.execute(stepExecution);
632+
assertEquals(FAILED, stepExecution.getStatus());
633+
assertTrue(stepExecution.getFailureExceptions().get(0) instanceof RuntimeException);
634+
}
635+
577636
private static class FailingRollbackTransactionManager extends ResourcelessTransactionManager {
578637

579638
@Override

0 commit comments

Comments
 (0)