{"record":{"id":"f235119fef4a74b2","repo":"Activiti/Activiti","slug":"retries-failed-with-activitioptimisticlockingexce","errorCode":null,"errorMessage":" retries failed with ActivitiOptimisticLockingException. Giving up.","messagePattern":" retries failed with ActivitiOptimisticLockingException\\. Giving up\\.","errorType":"exception","errorClass":"ActivitiException","httpStatus":null,"severity":"warning","filePath":"activiti-core/activiti-engine/src/main/java/org/activiti/engine/impl/interceptor/RetryInterceptor.java","lineNumber":57,"sourceCode":"\n        do {\n            if (failedAttempts > 0) {\n                log.info(\"Waiting for {}ms before retrying the command.\", waitTime);\n                waitBeforeRetry(waitTime);\n                waitTime *= waitIncreaseFactor;\n            }\n\n            try {\n                // try to execute the command\n                return next.execute(config, command);\n            } catch (ActivitiOptimisticLockingException e) {\n                log.info(\"Caught optimistic locking exception: \" + e);\n            }\n\n            failedAttempts++;\n        } while (failedAttempts <= numOfRetries);\n\n        throw new ActivitiException(\n            numOfRetries + \" retries failed with ActivitiOptimisticLockingException. Giving up.\"\n        );\n    }\n\n    protected void waitBeforeRetry(long waitTime) {\n        try {\n            Thread.sleep(waitTime);\n        } catch (InterruptedException e) {\n            log.debug(\"I am interrupted while waiting for a retry.\");\n        }\n    }\n\n    public void setNumOfRetries(int numOfRetries) {\n        this.numOfRetries = numOfRetries;\n    }\n\n    public void setWaitIncreaseFactor(int waitIncreaseFactor) {\n        this.waitIncreaseFactor = waitIncreaseFactor;","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/Activiti/Activiti/blob/56435b1a97deeafdc09dd40074b056c89fba5a8a/activiti-core/activiti-engine/src/main/java/org/activiti/engine/impl/interceptor/RetryInterceptor.java#L39-L75","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: default single-attempt behavior colliding in a cluster\nProcessEngineConfiguration.createProcessEngineConfigurationFromResource(\"activiti.cfg.xml\");\n// after: configure more optimistic-lock retries\nProcessEngineConfiguration cfg = ProcessEngineConfiguration\n    .createProcessEngineConfigurationFromResource(\"activiti.cfg.xml\");\ncfg.getProcessEngineConfiguration().setAsyncExecutorActivate(true);\n// in config XML: <property name=\"numberOfRetries\" value=\"5\"/>","handlingStrategy":"retry","validationCode":"// Detect hot rows before executing: check for an exclusive job lock or pending job on the same execution\nJob pendingJob = managementService.createJobQuery()\n    .executionId(executionId).singleResult();\nif (pendingJob != null) {\n    // another worker is likely processing this execution — defer your command\n}","typeGuard":null,"tryCatchPattern":"try {\n    runtimeService.setVariable(executionId, \"status\", \"done\");\n} catch (org.activiti.engine.ActivitiOptimisticLockingException e) {\n    // single conflict: safe to retry once after short backoff\n    Thread.sleep(200);\n    runtimeService.setVariable(executionId, \"status\", \"done\");\n} catch (org.activiti.engine.ActivitiException e) {\n    if (e.getMessage().contains(\"retries failed with ActivitiOptimisticLockingException\")) {\n        // persistent contention: re-read state and re-apply with fresh data\n    }\n}","preventionTips":["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."],"tags":["optimistic-locking","concurrency","retry-exhausted","database"],"backgroundTag":"optimistic-locking-conflict","analyzedSha":"56435b1a97deeafdc09dd40074b056c89fba5a8a","analyzedAt":"2026-09-09T21:00:06.703Z","contentChangedAt":"2026-09-09T21:00:06.703Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}