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

  1. Reduce contention: shard job executor work, use async executor exclusivity, or enable exclusive jobs for the activities in conflict.
  2. Increase numOfRetries and/or the wait time in the retry interceptor configuration if conflicts are brief.
  3. Serialize access to the hot entity: process the conflicting work in a single thread/executor or add application-level locking.
  4. Retry the operation at a higher level with backoff after this exception, since the entity state has since changed.
  5. 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

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


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)