{"record":{"id":"9015951a879da5ec","repo":"flowable/flowable-engine","slug":"no-bulk-delete-statement-for-persistentobjectclas","errorCode":null,"errorMessage":"no bulk delete statement for <persistentObjectClass> in the mapping files","messagePattern":"no bulk delete statement for <persistentObjectClass> in the mapping files","errorType":"exception","errorClass":"ActivitiException","httpStatus":null,"severity":"error","filePath":"modules/flowable5-engine/src/main/java/org/activiti/engine/impl/db/DbSqlSession.java","lineNumber":300,"sourceCode":"\n        @Override\n        public void clearCache() {\n            for (PersistentObject persistentObject : persistentObjects) {\n                cacheRemove(persistentObject.getClass(), persistentObject.getId());\n            }\n        }\n\n        @Override\n        public void execute() {\n\n            if (persistentObjects.isEmpty()) {\n                return;\n            }\n\n            String bulkDeleteStatement = dbSqlSessionFactory.getBulkDeleteStatement(persistentObjectClass);\n            bulkDeleteStatement = dbSqlSessionFactory.mapStatement(bulkDeleteStatement);\n            if (bulkDeleteStatement == null) {\n                throw new ActivitiException(\"no bulk delete statement for \" + persistentObjectClass + \" in the mapping files\");\n            }\n\n            // It only makes sense to check for optimistic locking exceptions for objects that actually have a revision\n            if (persistentObjects.get(0) instanceof HasRevision) {\n                int nrOfRowsDeleted = sqlSession.delete(bulkDeleteStatement, persistentObjects);\n                if (nrOfRowsDeleted < persistentObjects.size()) {\n                    throw new ActivitiOptimisticLockingException(\"One of the entities \" + persistentObjectClass\n                            + \" was updated by another transaction concurrently while trying to do a bulk delete\");\n                }\n            } else {\n                sqlSession.delete(bulkDeleteStatement, persistentObjects);\n            }\n        }\n\n        @Override\n        public Class<? extends PersistentObject> getPersistentObjectClass() {\n            return persistentObjectClass;\n        }","sourceCodeStart":282,"sourceCodeEnd":318,"githubUrl":"https://github.com/flowable/flowable-engine/blob/d6d39ce1c69ff244f2d9dc6af756a9b95e865586/modules/flowable5-engine/src/main/java/org/activiti/engine/impl/db/DbSqlSession.java#L282-L318","documentation":"DbSqlSession.execute() performs a bulk delete of persistent objects by looking up a mapped iBatis/MyBatis statement via dbSqlSessionFactory.getBulkDeleteStatement(persistentObjectClass). When the resolved statement name maps to null, meaning no XML mapping file defines a bulk delete for that entity class, it throws this ActivitiException before touching the database. It is a mapping-configuration error: the entity participates in bulk delete but has no corresponding SQL defined.","triggerScenarios":"Calling ContextCommandContext or DbSqlSession.execute() with a bulk-delete operation whose persistentObjectClass (e.g. a custom entity, or one registered via a custom SessionFactory) has no 'bulkDelete' statement registered in dbSqlSessionFactory's statement mappings and no matching id in any Activiti/Flowable XML mapping file.","commonSituations":"Adding a custom PersistentObject entity and registering it for deletion without authoring the mapping XML; upgrading the engine while using a custom DbSqlSessionFactory that lost statement mappings; typo in the mapped statement name so mapStatement() returns null.","solutions":["Add a bulk delete mapped statement (e.g. deleteXxx with the expected id) for the entity's class in the entity's MyBatis XML mapping file and ensure that XML is loaded into the process engine configuration's mapping resources.","Verify the entity class is correctly registered in DbSqlSessionFactory's getBulkDeleteStatement/mapStatement logic (custom subclasses must override/extend the statement mapping).","If the entity should not be bulk-deleted, change the calling code to delete entities individually (sqlSession.delete per object) instead of the bulk path."],"exampleFix":"// before: custom entity with no mapping\nprocessEngineConfiguration.setMyBatisMappingFiles(...); // CustomTask.xml missing\n\n// after: CustomTask.xml\n<mapper namespace=\"org.activiti.persistence.impl.CustomTaskEntityImpl\">\n  <delete id=\"bulkDeleteCustomTask\" parameterType=\"java.util.List\">\n    DELETE FROM ACT_CUSTOM_TASK WHERE ID_ IN (...)\n  </delete>\n</mapper>","handlingStrategy":"validation","validationCode":"// Verify a bulk delete statement is mapped before executing\nString stmt = dbSqlSessionFactory.getBulkDeleteStatement(MyEntity.class);\nif (stmt == null || dbSqlSessionFactory.mapStatement(stmt) == null) {\n    throw new IllegalStateException(\n        \"Add a bulk delete statement for \" + MyEntity.class.getName() + \" to the MyBatis mapping XML\");\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["For every custom PersistentObject, author insert, update, delete AND bulk-delete statements before wiring it into the engine.","Keep a smoke test that runs a full CRUD cycle per custom entity at startup or in CI.","Register all custom mapping XML files in the process engine configuration's mapping resources."],"tags":["database","mybatis","mapping","bulk-delete"],"backgroundTag":"resource-not-found","analyzedSha":"d6d39ce1c69ff244f2d9dc6af756a9b95e865586","analyzedAt":"2026-09-11T06:41:19.413Z","contentChangedAt":"2026-09-11T06:41:19.413Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}