Activiti/Activiti · warning · ActivitiException
retries failed with ActivitiOptimisticLockingException…
Error message
retries failed with ActivitiOptimisticLockingException. Giving up.
What it means
Thrown by RetryInterceptor.execute after a command failed with ActivitiOptimisticLockingException on every one of numOfRetries attempts. Activiti retries optimistic-lock conflicts (typically from concurrent updates to the same DB row) with a wait between attempts; if all retries are exhausted, this ActivitiException is thrown instead. It signals persistent concurrent modification, not a transient blip.
Solutions
- Reduce contention: shard job executor work, use async executor exclusivity, or enable exclusive jobs for the activities in conflict.
- Increase numOfRetries and/or the wait time in the retry interceptor configuration if conflicts are brief.
- Serialize access to the hot entity: process the conflicting work in a single thread/executor or add application-level locking.
- Retry the operation at a higher level with backoff after this exception, since the entity state has since changed.
- Upgrade Activiti — later versions improved optimistic locking retry behavior for jobs.
Example fix
// before: default single-attempt behavior colliding in a cluster
ProcessEngineConfiguration.createProcessEngineConfigurationFromResource("activiti.cfg.xml");
// after: configure more optimistic-lock retries
ProcessEngineConfiguration cfg = ProcessEngineConfiguration
.createProcessEngineConfigurationFromResource("activiti.cfg.xml");
cfg.getProcessEngineConfiguration().setAsyncExecutorActivate(true);
// in config XML: <property name="numberOfRetries" value="5"/> Defensive patterns
Strategy: retry
Validate before calling
// Detect hot rows before executing: check for an exclusive job lock or pending job on the same execution
Job pendingJob = managementService.createJobQuery()
.executionId(executionId).singleResult();
if (pendingJob != null) {
// another worker is likely processing this execution — defer your command
} Try / catch
try {
runtimeService.setVariable(executionId, "status", "done");
} catch (org.activiti.engine.ActivitiOptimisticLockingException e) {
// single conflict: safe to retry once after short backoff
Thread.sleep(200);
runtimeService.setVariable(executionId, "status", "done");
} catch (org.activiti.engine.ActivitiException e) {
if (e.getMessage().contains("retries failed with ActivitiOptimisticLockingException")) {
// persistent contention: re-read state and re-apply with fresh data
}
} Prevention
- Enable exclusive jobs (asyncExecutorExclusive) for activities prone to concurrent updates.
- Limit the number of concurrent job executors per process instance in clustered setups.
- Keep commands short to shrink the optimistic-lock window.
- After conflicts, always re-read the entity before retrying — do not replay stale state.
When it happens
Trigger: Executing a command wrapped in RetryInterceptor when every attempt throws ActivitiOptimisticLockingException — i.e. another transaction updates the same row (job, execution, task, or variables) and commits before this one, for numOfRetries consecutive attempts.
Common situations: Multiple job executors or clustered engine nodes processing the same job/execution concurrently; many async continuations racing on the same process instance; high-contention workflows where several users update the same task at once.
Related errors
- was updated by another transaction concurrently
- was updated by another transaction concurrently
- Could not lock process instance
- Activiti database problem
- Ad-hoc sub process has running child executions that need…
AI-assisted analysis of Activiti/Activiti@56435b1a97 (2026-09-09).
Data as JSON: /api/errors/f235119fef4a74b2.
Report an issue: GitHub.
Appendix: source
Thrown at activiti-core/activiti-engine/src/main/java/org/activiti/engine/impl/interceptor/RetryInterceptor.java:57
do {
if (failedAttempts > 0) {
log.info("Waiting for {}ms before retrying the command.", waitTime);
waitBeforeRetry(waitTime);
waitTime *= waitIncreaseFactor;
}
try {
// try to execute the command
return next.execute(config, command);
} catch (ActivitiOptimisticLockingException e) {
log.info("Caught optimistic locking exception: " + e);
}
failedAttempts++;
} while (failedAttempts <= numOfRetries);
throw new ActivitiException(
numOfRetries + " retries failed with ActivitiOptimisticLockingException. Giving up."
);
}
protected void waitBeforeRetry(long waitTime) {
try {
Thread.sleep(waitTime);
} catch (InterruptedException e) {
log.debug("I am interrupted while waiting for a retry.");
}
}
public void setNumOfRetries(int numOfRetries) {
this.numOfRetries = numOfRetries;
}
public void setWaitIncreaseFactor(int waitIncreaseFactor) {
this.waitIncreaseFactor = waitIncreaseFactor;View on GitHub (pinned to 56435b1a97)