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

  1. Catch ActivitiOptimisticLockingException and retry the whole command with fresh data — re-fetch the entity, re-apply, and delete again.
  2. 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.
  3. Reduce transaction scope so entities are read and deleted within one short transaction.
  4. 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

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


AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11). Data as JSON: /api/errors/d174dcc571a0ddb4. Report an issue: GitHub.