flowable/flowable-engine · error · FlowableOptimisticLockingException
${updatedObject} was updated by another transaction concurre
Error message
${updatedObject} was updated by another transaction concurrently What it means
FlowableOptimisticLockingException thrown when the UPDATE affects zero rows, meaning the row's revision in the DB no longer matches the in-memory entity's revision — another transaction modified the row first.
Source
Thrown at modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/db/DbSqlSession.java:622
}
updatedObjects.clear();
bulkUpdateOperations.clear();
}
protected void flushUpdateEntity(Entity updatedObject) {
String updateStatement = dbSqlSessionFactory.getUpdateStatement(updatedObject);
updateStatement = dbSqlSessionFactory.mapStatement(updateStatement);
if (updateStatement == null) {
throw new FlowableException("no update statement for " + updatedObject.getClass() + " in the ibatis mapping files");
}
LOGGER.debug("updating: {}", updatedObject);
int updatedRecords = sqlSession.update(updateStatement, updatedObject);
if (updatedRecords == 0) {
throw new FlowableOptimisticLockingException(updatedObject + " was updated by another transaction concurrently");
}
// See https://activiti.atlassian.net/browse/ACT-1290
if (updatedObject instanceof HasRevision) {
((HasRevision) updatedObject).setRevision(((HasRevision) updatedObject).getRevisionNext());
}
}
protected void flushBulkUpdate(BulkUpdateOperation bulkUpdateOperation) {
// Bulk update
bulkUpdateOperation.execute(sqlSession);
}
protected void flushDeletes() {
if (deletedObjects.size() == 0 && bulkDeleteOperations.size() == 0) {
return;
}View on GitHub (pinned to d6d39ce1c6)
Solutions
- Retry the whole command (OptimisticLockingAwareTransactionInterceptor retries FlowableOptimisticLockingException for async jobs) so the entity is reloaded with a fresh revision.
- Serialize access: use exclusive jobs or acquire a DB lock before mutating shared entities.
- Shorten transaction scope so stale reads are less likely.
- For async executors, increase retry settings and ensure the job handler is idempotent.
Example fix
// before
managementService.executeCommand(ctx -> { task.setName(name); return null; }); // may throw FlowableOptimisticLockingException
// after
for (int i = 0; i < 3; i++) {
try { managementService.executeCommand(ctx -> { task.setName(name); return null; }); break; }
catch (FlowableOptimisticLockingException e) { task = taskService.createTaskQuery().taskId(task.getId()).singleResult(); }
} Defensive patterns
Strategy: validation
Validate before calling
String stmt = dbSqlSessionFactory.getUpdateStatement(entity);
if (dbSqlSessionFactory.mapStatement(stmt) == null) {
throw new IllegalStateException("No update statement mapped for " + entity.getClass());
} Try / catch
try { /* persist entity */ }
catch (FlowableException e) { if (e.getMessage().startsWith("no update statement")) { /* add mapping and retry after deploy */ } throw e; } Prevention
- Always provide insert, update and delete statements together in custom entity mappers.
- Include mapper XMLs in integration-test configuration so missing statements fail tests early.
- Grep mapper XMLs for the statement ids the entity manager expects after upgrades.
- Avoid renaming entity classes without updating statement registration.
When it happens
Trigger: Two concurrent commands/transactions load the same entity, both modify it; the second flush executes its versioned UPDATE which matches 0 rows because REV_ was already incremented.
Common situations: Long-running user tasks edited by multiple users; async jobs and a human action touching the same process instance; retrying a stale command; clustered engines sharing one DB.
Related errors
- ${entity} was updated by another transaction concurrently
- <persistentObject> was updated by another transaction concur
- 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/97ab22839f905764.
Report an issue: GitHub.