spring-projects / spring-projects/spring-batch
OptimisticLockingFailureException when calling `JobOperator.stop`
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 3k
- Forks
- 2.5k
- Avg merge
- 6d 53m
- Merged PRs (30d)
- 3
Description
I implemented a class that implements the StoppableTasklet interface. I store the executing thread, and the task contains a Thread.sleep(). The stop() method calls interrupt() on the stored thread.
I start the job using JobOperator.start(), wait for a short time, and then call JobOperator.stop(). At that point, I get an OptimisticLockingFailureException.
I also create a ThreadPoolTaskExecutor using the @BatchTaskExecutor qualifier.
I put together a sample project with an integration test using Testcontainers and a PostgreSQL database. Java 25 is required, and the project can be built with:
./mvnw package
The exception does not occur every time, but it happens very frequently.
You can find the sample project here: https://github.com/vicziani/spring-batch-bug
Stack trace:
org.springframework.dao.OptimisticLockingFailureException: Attempt to update step execution id=1 with wrong version (2), where current version is 3
at org.springframework.batch.core.repository.dao.jdbc.JdbcStepExecutionDao.updateStepExecution(JdbcStepExecutionDao.java:256) ~[spring-batch-core-6.0.4.jar:6.0.4]
at org.springframework.batch.core.repository.support.SimpleJobRepository.update(SimpleJobRepository.java:171) ~[spring-batch-core-6.0.4.jar:6.0.4]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:565) ~[na:na]
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) ~[spring-aop-7.0.8.jar:7.0.8]
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190) ~[spring-aop-7.0.8.jar:7.0.8]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:158) ~[spring-aop-7.0.8.jar:7.0.8]
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:133) ~[spring-tx-7.0.8.jar:7.0.8]
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:371) ~[spring-tx-7.0.8.jar:7.0.8]
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:130) ~[spring-tx-7.0.8.jar:7.0.8]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-7.0.8.jar:7.0.8]
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:222) ~[spring-aop-7.0.8.jar:7.0.8]
at jdk.proxy2/jdk.proxy2.$Proxy86.update(Unknown Source) ~[na:na]
at org.springframework.batch.core.step.AbstractStep.execute(AbstractStep.java:302) ~[spring-batch-core-6.0.4.jar:6.0.4]
at org.springframework.batch.core.job.SimpleStepHandler.handleStep(SimpleStepHandler.java:128) ~[spring-batch-core-6.0.4.jar:6.0.4]
at org.springframework.batch.core.job.AbstractJob.handleStep(AbstractJob.java:397) ~[spring-batch-core-6.0.4.jar:6.0.4]
at org.springframework.batch.core.job.SimpleJob.doExecute(SimpleJob.java:129) ~[spring-batch-core-6.0.4.jar:6.0.4]
at org.springframework.batch.core.job.AbstractJob.execute(AbstractJob.java:293) ~[spring-batch-core-6.0.4.jar:6.0.4]
at org.springframework.batch.core.launch.support.TaskExecutorJobLauncher$1.run(TaskExecutorJobLauncher.java:220) ~[spring-batch-core-6.0.4.jar:6.0.4]
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1090) ~[na:na]
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:614) ~[na:na]
at java.base/java.lang.Thread.run(Thread.java:1474) ~[na:na]
The other thing I don't understand is that if the stop() method in StoppableTasklet is deprecated, why is it still necessary to implement it, and what is the recommended way to implement it?
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by building and running the linked sample project with Java 25 using ./mvnw package, then inspect its integration test and the StoppableTasklet implementation. Trace JobOperator.stop() alongside the JdbcStepExecutionDao.updateStepExecution stack-trace path and the ThreadPoolTaskExecutor configuration. Done means reproducing the optimistic-locking failure reliably and establishing the recommended behavior for deprecated StoppableTasklet.stop().
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java, postgresql, spring
- Domain
- backend, databases
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100