flowable/flowable-engine · error · ActivitiOptimisticLockingException
<persistentObject> was updated by another transaction concur
Error message
<persistentObject> was updated by another transaction concurrently
What it means
Thrown by DbSqlSession's delete operation as ActivitiOptimisticLockingException when the DELETE affected zero rows for an entity implementing HasRevision. Zero rows means the row's revision no longer matches — another transaction already updated (or deleted) the entity concurrently, so this delete is based on stale state.
Source
Thrown at modules/flowable5-engine/src/main/java/org/activiti/engine/impl/db/DbSqlSession.java:240
@Override
public void clearCache() {
cacheRemove(persistentObject.getClass(), persistentObject.getId());
}
@Override
public void execute() {
String deleteStatement = dbSqlSessionFactory.getDeleteStatement(persistentObject.getClass());
deleteStatement = dbSqlSessionFactory.mapStatement(deleteStatement);
if (deleteStatement == null) {
throw new ActivitiException("no delete statement for " + persistentObject.getClass() + " in the ibatis mapping files");
}
// It only makes sense to check for optimistic locking exceptions for objects that actually have a revision
if (persistentObject instanceof HasRevision) {
int nrOfRowsDeleted = sqlSession.delete(deleteStatement, persistentObject);
if (nrOfRowsDeleted == 0) {
throw new ActivitiOptimisticLockingException(persistentObject + " was updated by another transaction concurrently");
}
} else {
sqlSession.delete(deleteStatement, persistentObject);
}
}
public PersistentObject getPersistentObject() {
return persistentObject;
}
@Override
public String toString() {
return "delete " + persistentObject;
}
}
/**
* A bulk version of the {@link CheckedDeleteOperation}.View on GitHub (pinned to d6d39ce1c6)
Solutions
- Catch ActivitiOptimisticLockingException and retry the whole command with fresh data — re-fetch the entity, re-apply, and delete again.
- Serialize conflicting work: use exclusive jobs (async continuations with exclusive=true) or optimistic-lock-aware queues so one actor mutates an entity at a time.
- Reduce transaction scope so entities are read and deleted within one short transaction.
- If the entity was already deleted by the other transaction, treat the delete as idempotent and skip it.
Example fix
// before
taskService.complete(taskId); // fails when another actor already updated the task
// after
try {
taskService.complete(taskId);
} catch (ActivitiOptimisticLockingException e) {
Task fresh = taskService.createTaskQuery().taskId(taskId).singleResult();
if (fresh != null) {
taskService.complete(taskId); // retry with fresh revision
}
} Defensive patterns
Strategy: retry
Validate before calling
Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
if (task == null) {
return; // already removed concurrently — nothing to delete
} Try / catch
int attempts = 3;
while (attempts-- > 0) {
try {
doWorkWithEntity(id);
break;
} catch (ActivitiOptimisticLockingException e) {
if (attempts == 0) throw e;
// backoff, then re-fetch entity so the next attempt uses a fresh revision
sleep(50 * (3 - attempts));
}
} Prevention
- Keep transactions short — re-read entities inside the same transaction that mutates them
- Use exclusive async jobs for operations that contend on one process instance
- Design deletes to be idempotent so a lost race is harmless
- Set reasonable retry limits with backoff; alert if optimistic-lock retries spike
When it happens
Trigger: Two concurrent transactions modifying the same entity (e.g. the same task, execution, or job) where one commits a new revision before the other issues its delete; deleting an entity that was concurrently updated; retries of stale command contexts.
Common situations: High-concurrency workflows with many users acting on the same task; parallel job executions touching the same process instance; long-running commands holding stale entity revisions across user think time; message/async executor contention on one execution.
Related errors
- ${updatedObject} was updated by another transaction concurre
- ${entity} was updated by another transaction concurrently
- One of the entities <persistentObjectClass> was updated by a
- <updatedObject> was updated by another transaction concurren
- Could not lock case instance
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/d174dcc571a0ddb4.
Report an issue: GitHub.