{"record":{"id":"27f24063e3ad7d1d","repo":"mybatis/mybatis-3","slug":"error-evaluating-expression-expression","errorCode":null,"errorMessage":"Error evaluating expression '\" + expression + \"'.  Return value (\" + value + \") was not iterable.","messagePattern":"Error evaluating expression '\" \\+ expression \\+ \"'\\.  Return value \\(\" \\+ value \\+ \"\\) was not iterable\\.","errorType":"exception","errorClass":"BuilderException","httpStatus":null,"severity":"error","filePath":"src/main/java/org/apache/ibatis/scripting/xmltags/ExpressionEvaluator.java","lineNumber":81,"sourceCode":"    if (value instanceof Iterable) {\n      return (Iterable<?>) value;\n    }\n    if (value.getClass().isArray()) {\n      // the array may be primitive, so Arrays.asList() may throw\n      // a ClassCastException (issue 209). Do the work manually\n      // Curse primitives! :) (JGB)\n      int size = Array.getLength(value);\n      List<Object> answer = new ArrayList<>();\n      for (int i = 0; i < size; i++) {\n        Object o = Array.get(value, i);\n        answer.add(o);\n      }\n      return answer;\n    }\n    if (value instanceof Map) {\n      return ((Map) value).entrySet();\n    }\n    throw new BuilderException(\n        \"Error evaluating expression '\" + expression + \"'.  Return value (\" + value + \") was not iterable.\");\n  }\n\n}\n","sourceCodeStart":63,"sourceCodeEnd":86,"githubUrl":"https://github.com/mybatis/mybatis-3/blob/008069adb1b089579b5dcba87ee591908b263274/src/main/java/org/apache/ibatis/scripting/xmltags/ExpressionEvaluator.java#L63-L86","documentation":"MyBatis throws this BuilderException when ExpressionEvaluator.evaluateIterable() is asked to iterate a value whose runtime type is neither an array, a java.lang.Iterable, nor a java.util.Map. It is raised from the <foreach> dynamic SQL construct: the expression given in the 'collection' (or 'item' list source) attribute must resolve to something walkable. Any scalar (Integer, String, single POJO) result makes the SQL node unbuildable at execution time.","triggerScenarios":"A <foreach item=\"x\" collection=\"ids\" ...> where the parameter object's 'ids' property is a single Integer/String/POJO instead of a List/Set/array/Map; an OGNL expression in collection=\"...\" that evaluates to a scalar (e.g. collection=\"user.id\" instead of \"user.ids\"); passing a single object where the mapper method signature declares a collection.","commonSituations":"Mapper method takes Object/POJO but XML assumes a list; property name typo so OGNL falls back to a scalar getter; refactoring a List parameter to a single item without updating the XML; passing a String of comma-separated ids instead of a List.","solutions":["Make the collection attribute point at an actual List/Set/array/Map property (e.g. collection=\"ids\" where ids is List<Long>).","If the input is naturally scalar or null, wrap it before the call: Collections.singletonList(value).","Check the OGNL path spelling against the parameter object's getters; a wrong path can resolve to an unrelated scalar property.","For comma-separated input, split it in the mapper/service layer into a List before passing to MyBatis."],"exampleFix":"// before\n<select id=\"findByIds\" parameterType=\"map\">\n  SELECT * FROM t WHERE id IN\n  <foreach item=\"id\" collection=\"id\">#{id}</foreach>\n</select>\n// after\n<select id=\"findByIds\" parameterType=\"map\">\n  SELECT * FROM t WHERE id IN\n  <foreach item=\"id\" collection=\"ids\">#{id}</foreach>\n</select>\n// with map.put(\"ids\", Arrays.asList(1,2,3))","handlingStrategy":"validation","validationCode":"Object v = ((Map<String, Object>) paramMap).get(\"ids\");\nboolean ok = v == null || v instanceof Iterable || v.getClass().isArray() || v instanceof Map;\nif (!ok) throw new IllegalArgumentException(\"ids must be List/Set/array/Map, was \" + (v == null ? \"null\" : v.getClass()));","typeGuard":null,"tryCatchPattern":"try { sqlSession.selectList(\"findByIds\", paramMap); }\ncatch (BuilderException e) { /* check 'was not iterable' in message; fix collection attribute */ throw e; }","preventionTips":["Type mapper parameters as List<T>/Set<T>/T[] explicitly instead of Object.","Prefer @Param names that mirror the collection attribute string in XML.","Wrap scalar or null inputs with Collections.singletonList()/emptyList before the call."],"tags":["mybatis","dynamic-sql","foreach","ognl","builder-exception"],"backgroundTag":null,"analyzedSha":"008069adb1b089579b5dcba87ee591908b263274","analyzedAt":"2026-08-14T13:07:10.264Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}