{"record":{"id":"3b87fa8ed3fea575","repo":"mybatis/mybatis-3","slug":"cannot-add-a-collection-result-to-non-collection-b","errorCode":null,"errorMessage":"Cannot add a collection result to non-collection based resultMapping: {}","messagePattern":"Cannot add a collection result to non-collection based resultMapping: (.+?)","errorType":"exception","errorClass":"ReflectionException","httpStatus":null,"severity":"error","filePath":"src/main/java/org/apache/ibatis/executor/resultset/PendingConstructorCreation.java","lineNumber":63,"sourceCode":"  PendingConstructorCreation(Class<?> resultType, List<Class<?>> types, List<Object> args) {\n    // since all our keys are based on result map id, we know we will never go over args size\n    final int maxSize = types.size();\n\n    this.linkedCollectionMetaInfo = new HashMap<>(maxSize);\n    this.linkedCollectionsByKey = new HashMap<>(maxSize);\n    this.linkedCreationsByKey = new HashMap<>(maxSize);\n\n    this.resultType = resultType;\n    this.constructorArgTypes = types;\n    this.constructorArgs = args;\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  Collection<Object> initializeCollectionForResultMapping(ObjectFactory objectFactory, ResultMap resultMap,\n      ResultMapping constructorMapping, Integer index) {\n    final Class<?> parameterType = constructorMapping.getJavaType();\n    if (!objectFactory.isCollection(parameterType)) {\n      throw new ReflectionException(\n          \"Cannot add a collection result to non-collection based resultMapping: \" + constructorMapping);\n    }\n\n    return linkedCollectionsByKey.computeIfAbsent(new PendingCreationKey(constructorMapping), k -> {\n      // this will allow us to verify the types of the collection before creating the final object\n      linkedCollectionMetaInfo.put(index, new PendingCreationMetaInfo(resultMap.getType(), k));\n\n      // will be checked before we finally create the object) as we cannot reliably do that here\n      return (Collection<Object>) objectFactory.create(parameterType);\n    });\n  }\n\n  void linkCreation(ResultMapping constructorMapping, PendingConstructorCreation pcc) {\n    final PendingCreationKey creationKey = new PendingCreationKey(constructorMapping);\n    final List<PendingConstructorCreation> pendingConstructorCreations = linkedCreationsByKey\n        .computeIfAbsent(creationKey, k -> new ArrayList<>());\n\n    if (pendingConstructorCreations.contains(pcc)) {","sourceCodeStart":45,"sourceCodeEnd":81,"githubUrl":"https://github.com/mybatis/mybatis-3/blob/008069adb1b089579b5dcba87ee591908b263274/src/main/java/org/apache/ibatis/executor/resultset/PendingConstructorCreation.java#L45-L81","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Change the constructor parameter to a collection type (List<Item>, Collection<Item>, or a concrete class) matching the nested rows.","Or set javaType on the <arg> to a collection type (e.g. javaType=\"java.util.ArrayList\") so the mapping declares the collection.","If only one child is expected, use an <association> nested in the constructor instead of a collection mapping.","Keep setter-based mapping for that property if the constructor must keep a singular parameter."],"exampleFix":"// before\npublic Order(Long id, Item item) { ... } // mapped as collection arg\n<constructor><arg property=\"items\" resultMap=\"itemMap\" javaType=\"item\"/></constructor>\n\n// after\npublic Order(Long id, List<Item> items) { ... }\n<constructor><arg property=\"items\" resultMap=\"itemMap\" javaType=\"java.util.ArrayList\"/></constructor>","handlingStrategy":"validation","validationCode":"// verify each constructor arg mapped as a collection is a collection type\nfor (ResultMapping rm : resultMap.getConstructorResultMappings()) {\n  if (rm.getNestedResultMapId() != null && rm.getJavaType() != null\n      && !Collection.class.isAssignableFrom(rm.getJavaType())) {\n    throw new IllegalStateException(rm.getProperty() + \" is collection-mapped but javaType \"\n        + rm.getJavaType() + \" is not a collection\");\n  }\n}","typeGuard":"static boolean isCollectionArg(ResultMapping rm) {\n  return rm.getNestedQueryId() == null && rm.getNestedResultMapId() != null\n      && rm.getJavaType() != null && Collection.class.isAssignableFrom(rm.getJavaType());\n}","tryCatchPattern":null,"preventionTips":["When moving collection properties from setters to constructor args, change the parameter type to List/Set in the same commit.","Add explicit javaType on constructor <arg> elements so mapping intent is machine-checkable."],"tags":["mybatis","constructor-mapping","collections","resultmap"],"backgroundTag":null,"analyzedSha":"008069adb1b089579b5dcba87ee591908b263274","analyzedAt":"2026-08-14T13:07:10.264Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}