mybatis/mybatis-3 · error · BuilderException

Error evaluating expression '" + expression + "'. Return va

Error message

Error evaluating expression '" + expression + "'.  Return value (" + value + ") was not iterable.

What it means

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.

Source

Thrown at src/main/java/org/apache/ibatis/scripting/xmltags/ExpressionEvaluator.java:81

    if (value instanceof Iterable) {
      return (Iterable<?>) value;
    }
    if (value.getClass().isArray()) {
      // the array may be primitive, so Arrays.asList() may throw
      // a ClassCastException (issue 209). Do the work manually
      // Curse primitives! :) (JGB)
      int size = Array.getLength(value);
      List<Object> answer = new ArrayList<>();
      for (int i = 0; i < size; i++) {
        Object o = Array.get(value, i);
        answer.add(o);
      }
      return answer;
    }
    if (value instanceof Map) {
      return ((Map) value).entrySet();
    }
    throw new BuilderException(
        "Error evaluating expression '" + expression + "'.  Return value (" + value + ") was not iterable.");
  }

}

View on GitHub (pinned to 008069adb1)

Solutions

  1. Make the collection attribute point at an actual List/Set/array/Map property (e.g. collection="ids" where ids is List<Long>).
  2. If the input is naturally scalar or null, wrap it before the call: Collections.singletonList(value).
  3. Check the OGNL path spelling against the parameter object's getters; a wrong path can resolve to an unrelated scalar property.
  4. For comma-separated input, split it in the mapper/service layer into a List before passing to MyBatis.

Example fix

// before
<select id="findByIds" parameterType="map">
  SELECT * FROM t WHERE id IN
  <foreach item="id" collection="id">#{id}</foreach>
</select>
// after
<select id="findByIds" parameterType="map">
  SELECT * FROM t WHERE id IN
  <foreach item="id" collection="ids">#{id}</foreach>
</select>
// with map.put("ids", Arrays.asList(1,2,3))
Defensive patterns

Strategy: validation

Validate before calling

Object v = ((Map<String, Object>) paramMap).get("ids");
boolean ok = v == null || v instanceof Iterable || v.getClass().isArray() || v instanceof Map;
if (!ok) throw new IllegalArgumentException("ids must be List/Set/array/Map, was " + (v == null ? "null" : v.getClass()));

Try / catch

try { sqlSession.selectList("findByIds", paramMap); }
catch (BuilderException e) { /* check 'was not iterable' in message; fix collection attribute */ throw e; }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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