mybatis/mybatis-3 · error · ReflectionException

Cannot add a collection result to non-collection based resul

Error message

Cannot add a collection result to non-collection based resultMapping: {}

What it means

Thrown by PendingConstructorCreation when a constructor mapping is expected to receive a collection but the mapped constructor parameter's Java type is not a collection (objectFactory.isCollection(parameterType) is false). MyBatis cannot pour multiple nested rows into a non-collection constructor argument, so it fails with the offending resultMapping string.

Source

Thrown at src/main/java/org/apache/ibatis/executor/resultset/PendingConstructorCreation.java:63

  PendingConstructorCreation(Class<?> resultType, List<Class<?>> types, List<Object> args) {
    // since all our keys are based on result map id, we know we will never go over args size
    final int maxSize = types.size();

    this.linkedCollectionMetaInfo = new HashMap<>(maxSize);
    this.linkedCollectionsByKey = new HashMap<>(maxSize);
    this.linkedCreationsByKey = new HashMap<>(maxSize);

    this.resultType = resultType;
    this.constructorArgTypes = types;
    this.constructorArgs = args;
  }

  @SuppressWarnings("unchecked")
  Collection<Object> initializeCollectionForResultMapping(ObjectFactory objectFactory, ResultMap resultMap,
      ResultMapping constructorMapping, Integer index) {
    final Class<?> parameterType = constructorMapping.getJavaType();
    if (!objectFactory.isCollection(parameterType)) {
      throw new ReflectionException(
          "Cannot add a collection result to non-collection based resultMapping: " + constructorMapping);
    }

    return linkedCollectionsByKey.computeIfAbsent(new PendingCreationKey(constructorMapping), k -> {
      // this will allow us to verify the types of the collection before creating the final object
      linkedCollectionMetaInfo.put(index, new PendingCreationMetaInfo(resultMap.getType(), k));

      // will be checked before we finally create the object) as we cannot reliably do that here
      return (Collection<Object>) objectFactory.create(parameterType);
    });
  }

  void linkCreation(ResultMapping constructorMapping, PendingConstructorCreation pcc) {
    final PendingCreationKey creationKey = new PendingCreationKey(constructorMapping);
    final List<PendingConstructorCreation> pendingConstructorCreations = linkedCreationsByKey
        .computeIfAbsent(creationKey, k -> new ArrayList<>());

    if (pendingConstructorCreations.contains(pcc)) {

View on GitHub (pinned to 008069adb1)

Solutions

  1. Change the constructor parameter to a collection type (List<Item>, Collection<Item>, or a concrete class) matching the nested rows.
  2. Or set javaType on the <arg> to a collection type (e.g. javaType="java.util.ArrayList") so the mapping declares the collection.
  3. If only one child is expected, use an <association> nested in the constructor instead of a collection mapping.
  4. Keep setter-based mapping for that property if the constructor must keep a singular parameter.

Example fix

// before
public Order(Long id, Item item) { ... } // mapped as collection arg
<constructor><arg property="items" resultMap="itemMap" javaType="item"/></constructor>

// after
public Order(Long id, List<Item> items) { ... }
<constructor><arg property="items" resultMap="itemMap" javaType="java.util.ArrayList"/></constructor>
Defensive patterns

Strategy: validation

Validate before calling

// verify each constructor arg mapped as a collection is a collection type
for (ResultMapping rm : resultMap.getConstructorResultMappings()) {
  if (rm.getNestedResultMapId() != null && rm.getJavaType() != null
      && !Collection.class.isAssignableFrom(rm.getJavaType())) {
    throw new IllegalStateException(rm.getProperty() + " is collection-mapped but javaType "
        + rm.getJavaType() + " is not a collection");
  }
}

Type guard

static boolean isCollectionArg(ResultMapping rm) {
  return rm.getNestedQueryId() == null && rm.getNestedResultMapId() != null
      && rm.getJavaType() != null && Collection.class.isAssignableFrom(rm.getJavaType());
}

Prevention

When it happens

Trigger: A <constructor><arg property="..." resultMap="..."/></constructor> style mapping (constructor collection mapping) where the corresponding constructor parameter is a single object type (e.g. Item rather than List<Item>), or javaType on the arg is wrong.

Common situations: Refactoring a setter collection property into a constructor without changing the parameter from singular to List; copy-pasting an <arg> and forgetting javaType="list" or a concrete collection type; generics erased so MyBatis sees the raw parameter type incorrectly after signature changes.

Related errors


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