{"record":{"id":"ba02cbf901dea3d0","repo":"flowable/flowable-engine","slug":"entity-was-updated-by-another-transaction-concu","errorCode":null,"errorMessage":"${entity} was updated by another transaction concurrently","messagePattern":"(.+?) was updated by another transaction concurrently","errorType":"exception","errorClass":"FlowableOptimisticLockingException","httpStatus":null,"severity":"error","filePath":"modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/db/DbSqlSession.java","lineNumber":690,"sourceCode":"                bulkDeleteOperation.execute(sqlSession, entityClass);\n            }\n        }\n    }\n\n    protected void flushDeleteEntities(Class<? extends Entity> entityClass, Collection<Entity> entitiesToDelete) {\n        for (Entity entity : entitiesToDelete) {\n            String deleteStatement = dbSqlSessionFactory.getDeleteStatement(entity.getClass());\n            deleteStatement = dbSqlSessionFactory.mapStatement(deleteStatement);\n            if (deleteStatement == null) {\n                throw new FlowableException(\"no delete statement for \" + entity.getClass() + \" in the ibatis mapping files\");\n            }\n\n            // It only makes sense to check for optimistic locking exceptions\n            // for objects that actually have a revision\n            if (entity instanceof HasRevision) {\n                int nrOfRowsDeleted = sqlSession.delete(deleteStatement, entity);\n                if (nrOfRowsDeleted == 0) {\n                    throw new FlowableOptimisticLockingException(entity + \" was updated by another transaction concurrently\");\n                }\n            } else {\n                sqlSession.delete(deleteStatement, entity);\n            }\n        }\n    }\n\n    @Override\n    public void close() {\n        sqlSession.close();\n    }\n\n    public void commit() {\n        sqlSession.commit();\n    }\n\n    public void rollback() {\n        sqlSession.rollback();","sourceCodeStart":672,"sourceCodeEnd":708,"githubUrl":"https://github.com/flowable/flowable-engine/blob/d6d39ce1c69ff244f2d9dc6af756a9b95e865586/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/db/DbSqlSession.java#L672-L708","documentation":"FlowableOptimisticLockingException thrown when a versioned DELETE removes zero rows: the row was already modified (revision bumped) or deleted by another transaction, so the revision-guarded delete matched nothing.","triggerScenarios":"Concurrent transactions: one deletes or updates a HasRevision entity while another tries to delete it; the second delete executes WHERE REV_ = staleRevision and deletes 0 rows.","commonSituations":"Two users deleting the same task/history row; async job racing a user-initiated cancel; stale in-memory entity held across transactions; clustered engines on a shared database.","solutions":["Catch FlowableOptimisticLockingException and treat the delete as already done, or reload the entity and retry the command.","Check first (query) whether the entity still exists before deleting in interactive flows.","Use exclusive jobs/locking for async work touching the same rows.","Keep transactions short to reduce stale-revision windows."],"exampleFix":"// before\ntaskService.deleteTask(taskId); // races with concurrent modification -> exception\n// after\ntry { taskService.deleteTask(taskId); }\ncatch (FlowableOptimisticLockingException e) { /* already deleted/changed; verify and continue */ }","handlingStrategy":"validation","validationCode":"String stmt = dbSqlSessionFactory.getDeleteStatement(MyEntityImpl.class);\nif (dbSqlSessionFactory.mapStatement(stmt) == null) {\n  throw new IllegalStateException(\"No delete statement mapped for \" + MyEntityImpl.class);\n}","typeGuard":null,"tryCatchPattern":"try { /* delete entity */ }\ncatch (FlowableException e) { if (e.getMessage().startsWith(\"no delete statement\")) { /* add mapping and redeploy */ } throw e; }","preventionTips":["Test CRUD (insert/update/delete) for every custom entity in an in-memory DB test.","Register all three statement names in DbSqlSessionFactory when introducing an entity.","Verify mapper XMLs are packaged and listed in MyBatis mapper locations.","After upgrades, diff custom mappers against new built-in ones."],"tags":["database","concurrency","optimistic-locking","delete"],"backgroundTag":"optimistic-locking-conflict","analyzedSha":"d6d39ce1c69ff244f2d9dc6af756a9b95e865586","analyzedAt":"2026-09-11T06:41:19.413Z","contentChangedAt":"2026-09-11T06:41:19.413Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}