mybatis/mybatis-3 · error · ExecutorException

Expected result object to be a pending constructor creation!

Error message

Expected result object to be a pending constructor creation!

What it means

Internal invariant of collection constructor injection: when the resultMap uses constructor-based collection mapping (hasResultMapsUsingConstructorCollection), getRowValue must return a PendingConstructorCreation placeholder so the handler can defer building the object until all child rows are seen. If the row value is not that placeholder type, MyBatis cannot proceed and throws this ExecutorException — it indicates an inconsistent mapping configuration, not user data.

Source

Thrown at src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java:410

              + "or ensure your statement returns ordered data and set resultOrdered=true on it.");
    }
  }

  private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap,
      ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
    final boolean useCollectionConstructorInjection = resultMap.hasResultMapsUsingConstructorCollection();

    DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
    ResultSet resultSet = rsw.getResultSet();
    skipRows(resultSet, rowBounds);
    while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
      ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw, resultMap, null);
      Object rowValue = getRowValue(rsw, discriminatedResultMap, null, null);
      if (!useCollectionConstructorInjection) {
        storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
      } else {
        if (!(rowValue instanceof PendingConstructorCreation)) {
          throw new ExecutorException("Expected result object to be a pending constructor creation!");
        }

        createAndStorePendingCreation(resultHandler, resultSet, resultContext, (PendingConstructorCreation) rowValue);
      }
    }
  }

  private void storeObject(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue,
      ResultMapping parentMapping, ResultSet rs) throws SQLException {
    if (parentMapping != null) {
      linkToParents(rs, parentMapping, rowValue);
      return;
    }

    if (pendingPccRelations.containsKey(rowValue)) {
      createPendingConstructorCreations(rowValue);
    }

View on GitHub (pinned to 008069adb1)

Solutions

  1. Make the resultMap consistent: map collection constructor args entirely through <constructor><arg javaType='...list-type...' resultMap='nested'/> and do not mix them with automapped collection properties
  2. Simplify to classic property-based <collection> mapping (no constructor arg collection) and remove the constructor-collection setup
  3. If it reproduces on valid mappings, report a bug to MyBatis with the resultMap XML and result classes

Example fix

<!-- before: mixed styles -> inconsistent internal state -->
<resultMap id="orderMap" type="Order">
  <constructor><arg column="id" javaType="long"/></constructor>
  <collection property="items" ofType="Item"/>  <!-- property collection alongside ctor flags -->
</resultMap>
<!-- after: single style -->
<resultMap id="orderMap" type="Order">
  <id property="id" column="id"/>
  <collection property="items" ofType="Item"/>
</resultMap>
Defensive patterns

Strategy: validation

Validate before calling

// Keep mappings single-style: either constructor-collection or property collections
ResultMap rm = session.getConfiguration().getResultMap("orderMap");
if (rm.hasResultMapsUsingConstructorCollection() && rm.getResultMappings().stream().anyMatch(m -> m.getNestedResultMapId() != null && m.getProperty() != null)) {
  log.warn("Mixed constructor-collection and property collection mapping in {}", rm.getId());
}

Prevention

When it happens

Trigger: handleRowValuesForSimpleResultMap iterates rows on a resultMap where hasResultMapsUsingConstructorCollection() is true, but getRowValue returned null or a fully built object instead of PendingConstructorCreation — typically a mixing of constructor-arg collection mapping with plain property automapping in the same resultMap.

Common situations: A resultMap that declares <constructor><arg> referencing a nested collection in some branches while a discriminator or automapping path returns a normal instance; custom ResultMap builds via ResultMap.Builder that set the constructor-collection flag inconsistently; edge-case mappings after upgrading MyBatis.

Related errors


AI-assisted analysis of mybatis/mybatis-3@008069adb1 (2026-08-14). Data as JSON: /api/errors/90e59a0e70ec9ea0. Report an issue: GitHub.