{"record":{"id":"fd6546f043479d2a","repo":"flowable/flowable-engine","slug":"no-delete-statement-for-persistentobjectclass-in","errorCode":null,"errorMessage":"no delete statement for <persistentObjectClass> in the ibatis mapping files","messagePattern":"no delete statement for <persistentObjectClass> in the ibatis 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":233,"sourceCode":"        }\n\n        @Override\n        public boolean sameIdentity(PersistentObject other) {\n            return persistentObject.getClass().equals(other.getClass())\n                    && persistentObject.getId().equals(other.getId());\n        }\n\n        @Override\n        public void clearCache() {\n            cacheRemove(persistentObject.getClass(), persistentObject.getId());\n        }\n\n        @Override\n        public void execute() {\n            String deleteStatement = dbSqlSessionFactory.getDeleteStatement(persistentObject.getClass());\n            deleteStatement = dbSqlSessionFactory.mapStatement(deleteStatement);\n            if (deleteStatement == null) {\n                throw new ActivitiException(\"no delete statement for \" + persistentObject.getClass() + \" in the ibatis mapping files\");\n            }\n\n            // It only makes sense to check for optimistic locking exceptions for objects that actually have a revision\n            if (persistentObject instanceof HasRevision) {\n                int nrOfRowsDeleted = sqlSession.delete(deleteStatement, persistentObject);\n                if (nrOfRowsDeleted == 0) {\n                    throw new ActivitiOptimisticLockingException(persistentObject + \" was updated by another transaction concurrently\");\n                }\n            } else {\n                sqlSession.delete(deleteStatement, persistentObject);\n            }\n        }\n\n        public PersistentObject getPersistentObject() {\n            return persistentObject;\n        }\n\n        @Override","sourceCodeStart":215,"sourceCodeEnd":251,"githubUrl":"https://github.com/flowable/flowable-engine/blob/d6d39ce1c69ff244f2d9dc6af756a9b95e865586/modules/flowable5-engine/src/main/java/org/activiti/engine/impl/db/DbSqlSession.java#L215-L251","documentation":"Thrown by DbSqlSession's delete operation as ActivitiException when the DbSqlSessionFactory returns no 'delete' statement mapped for the persistent object's class in the iBATIS/MyBatis mapping files. Every persistable entity must have a mapped delete statement; a missing mapping means the class is not a recognized persistable entity.","triggerScenarios":"Calling persistence APIs (e.g. managementService.executeCommand deleting a PersistentObject) for a custom or unrecognized entity class without registering a delete statement; deleting a stale/foreign entity class through the generic dbSqlSession.executeDelete; engine jar/version mismatch where entity classes exist without matching mappings.","commonSituations":"Custom entities added to the engine without corresponding MyBatis XML mapping and insert/delete registration in DbSqlSessionFactory; mixing engine jar versions where a class's mapping file changed names; passing the wrong object (e.g. a DTO) to a delete path.","solutions":["Use the proper service API for deletion (e.g. managementService.deleteJob, taskService.deleteTask) instead of generic PersistentObject deletion.","If deleting a custom entity, add a delete<ShortClassName> statement to the MyBatis mapping XML and register it via dbSqlSessionFactory.getDeleteStatements()/statementMappings.","Verify engine jar versions are consistent — all Flowable/Activiti modules on the same version so mappings and classes match.","Ensure you pass the actual entity class, not a subclass/DTO, to the delete path."],"exampleFix":"// before\ncommandContext.getDbSqlSession().delete(myCustomDto); // no mapping for DTO class\n// after\nMyCustomEntity entity = commandContext.getDbSqlSession()\n    .selectById(MyCustomEntity.class, id); // mapped entity with deleteMyCustomEntity statement\ncommandContext.getDbSqlSession().delete(entity);","handlingStrategy":"validation","validationCode":"if (!(obj instanceof KnownActivitiEntity)) {\n    throw new IllegalArgumentException(\n        \"Class \" + obj.getClass() + \" has no engine delete mapping; use a mapped entity\");\n}","typeGuard":"boolean hasDeleteMapping(Object persistentObject) {\n    return persistentObject instanceof Entity; // engine-mapped persistable entities\n}","tryCatchPattern":"try {\n    dbSqlSession.delete(persistentObject);\n} catch (ActivitiException e) {\n    if (e.getMessage() != null && e.getMessage().startsWith(\"no delete statement for\")) {\n        throw new PersistenceMappingException(persistentObject.getClass(), e);\n    }\n    throw e;\n}","preventionTips":["Only pass engine entities (Entity implementors) to generic delete paths","For custom entities, always ship both mapping XML and DbSqlSessionFactory statement registration","Keep all engine module versions aligned so classes and mappings match","Prefer service-level delete APIs over raw DbSqlSession access"],"tags":["database","ibatis","mapping","persistence"],"backgroundTag":"missing-dependency","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"}