flowable/flowable-engine · error · ActivitiException

no bulk delete statement for <persistentObjectClass> in the

Error message

no bulk delete statement for <persistentObjectClass> in the mapping files

What it means

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.

Source

Thrown at modules/flowable5-engine/src/main/java/org/activiti/engine/impl/db/DbSqlSession.java:300

        @Override
        public void clearCache() {
            for (PersistentObject persistentObject : persistentObjects) {
                cacheRemove(persistentObject.getClass(), persistentObject.getId());
            }
        }

        @Override
        public void execute() {

            if (persistentObjects.isEmpty()) {
                return;
            }

            String bulkDeleteStatement = dbSqlSessionFactory.getBulkDeleteStatement(persistentObjectClass);
            bulkDeleteStatement = dbSqlSessionFactory.mapStatement(bulkDeleteStatement);
            if (bulkDeleteStatement == null) {
                throw new ActivitiException("no bulk delete statement for " + persistentObjectClass + " in the mapping files");
            }

            // It only makes sense to check for optimistic locking exceptions for objects that actually have a revision
            if (persistentObjects.get(0) instanceof HasRevision) {
                int nrOfRowsDeleted = sqlSession.delete(bulkDeleteStatement, persistentObjects);
                if (nrOfRowsDeleted < persistentObjects.size()) {
                    throw new ActivitiOptimisticLockingException("One of the entities " + persistentObjectClass
                            + " was updated by another transaction concurrently while trying to do a bulk delete");
                }
            } else {
                sqlSession.delete(bulkDeleteStatement, persistentObjects);
            }
        }

        @Override
        public Class<? extends PersistentObject> getPersistentObjectClass() {
            return persistentObjectClass;
        }

View on GitHub (pinned to d6d39ce1c6)

Solutions

  1. 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.
  2. Verify the entity class is correctly registered in DbSqlSessionFactory's getBulkDeleteStatement/mapStatement logic (custom subclasses must override/extend the statement mapping).
  3. 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.

Example fix

// before: custom entity with no mapping
processEngineConfiguration.setMyBatisMappingFiles(...); // CustomTask.xml missing

// after: CustomTask.xml
<mapper namespace="org.activiti.persistence.impl.CustomTaskEntityImpl">
  <delete id="bulkDeleteCustomTask" parameterType="java.util.List">
    DELETE FROM ACT_CUSTOM_TASK WHERE ID_ IN (...)
  </delete>
</mapper>
Defensive patterns

Strategy: validation

Validate before calling

// Verify a bulk delete statement is mapped before executing
String stmt = dbSqlSessionFactory.getBulkDeleteStatement(MyEntity.class);
if (stmt == null || dbSqlSessionFactory.mapStatement(stmt) == null) {
    throw new IllegalStateException(
        "Add a bulk delete statement for " + MyEntity.class.getName() + " to the MyBatis mapping XML");
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


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