flowable/flowable-engine · error · ActivitiException

no insert statement for <persistentObjectClass> in the ibati

Error message

no insert statement for <persistentObjectClass> in the ibatis mapping files

What it means

flushRegularInsert() resolves the insert statement name for a single persistent object via dbSqlSessionFactory.getInsertStatement(persistentObject) and mapStatement(). If the resolved statement is null — no 'insert' mapping exists for that entity class in the loaded iBatis/MyBatis mapping files — the flush throws this ActivitiException before executing SQL. It signals a persistence-mapping gap for the entity being inserted.

Source

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

    protected void flushPersistentObjects(Class<? extends PersistentObject> persistentObjectClass, List<PersistentObject> persistentObjectsToInsert) {
        if (persistentObjectsToInsert.size() == 1) {
            flushRegularInsert(persistentObjectsToInsert.get(0), persistentObjectClass);
        } else if (Boolean.FALSE.equals(dbSqlSessionFactory.isBulkInsertable(persistentObjectClass))) {
            for (PersistentObject persistentObject : persistentObjectsToInsert) {
                flushRegularInsert(persistentObject, persistentObjectClass);
            }
        } else {
            flushBulkInsert(insertedObjects.get(persistentObjectClass), persistentObjectClass);
        }
        insertedObjects.remove(persistentObjectClass);
    }

    protected void flushRegularInsert(PersistentObject persistentObject, Class<? extends PersistentObject> clazz) {
        String insertStatement = dbSqlSessionFactory.getInsertStatement(persistentObject);
        insertStatement = dbSqlSessionFactory.mapStatement(insertStatement);

        if (insertStatement == null) {
            throw new ActivitiException("no insert statement for " + persistentObject.getClass() + " in the ibatis mapping files");
        }

        LOGGER.debug("inserting: {}", persistentObject);
        sqlSession.insert(insertStatement, persistentObject);

        // See https://activiti.atlassian.net/browse/ACT-1290
        if (persistentObject instanceof HasRevision) {
            HasRevision revisionEntity = (HasRevision) persistentObject;
            if (revisionEntity.getRevision() == 0) {
                revisionEntity.setRevision(revisionEntity.getRevisionNext());
            }
        }
    }

    protected void flushBulkInsert(List<PersistentObject> persistentObjectList, Class<? extends PersistentObject> clazz) {
        String insertStatement = dbSqlSessionFactory.getBulkInsertStatement(clazz);
        insertStatement = dbSqlSessionFactory.mapStatement(insertStatement);

View on GitHub (pinned to d6d39ce1c6)

Solutions

  1. Add the insert mapped statement for the entity class (correct id per the naming convention, e.g. 'insert.selectKey'+'insert' pattern used by DbSqlSessionFactory) to its mapping XML and register the XML in the engine configuration's mapping resources.
  2. Check DbSqlSessionFactory.getInsertStatement/mapStatement for how the statement name is derived and ensure a custom subclass returns a valid, mapped name for the entity.
  3. Confirm the statement id in XML matches exactly (case-sensitive) what getInsertStatement returns; fix typos in the id.

Example fix

// before
// CustomEntity.xml has only <select>, no <insert> → null statement

// after
<insert id="insertCustomEntity" parameterType="org.example.CustomEntityEntityImpl">
  INSERT INTO ACT_CUSTOM_ENTITY (ID_, NAME_) VALUES (#{id}, #{name})
</insert>
Defensive patterns

Strategy: validation

Validate before calling

// Check the insert statement resolves before inserting
String stmt = dbSqlSessionFactory.getInsertStatement(entity);
if (stmt == null || dbSqlSessionFactory.mapStatement(stmt) == null) {
    throw new IllegalStateException("No insert statement mapped for " + entity.getClass().getName());
}

Try / catch

try {
    taskService.saveTask(customTask); // triggers flush insert
} catch (ActivitiException e) {
    if (e.getMessage().contains("no insert statement")) {
        // mapping file missing/not loaded — fail fast with a clear message
        throw new ConfigurationException("CustomTask mapping XML not registered", e);
    }
    throw e;
}

Prevention

When it happens

Trigger: Flushing a DbSqlSession that contains a cached/inserted PersistentObject whose class has no insert statement mapping, e.g. a custom entity added via a custom Session/EntityManager without registering its insert statement in the mapping XML.

Common situations: Extending the engine with custom entities and forgetting to add the <insert> statement or to load the new XML in the configuration; class rename/refactor breaking the statement-name convention; old custom mappings incompatible after an engine upgrade.

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/15ec7b2d1d04d986. Report an issue: GitHub.