mybatis/mybatis-3 · error · BuilderException

The expression '" + expression + "' evaluated to a null valu

Error message

The expression '" + expression + "' evaluated to a null value.

What it means

ExpressionEvaluator.evaluateIterable evaluates the collection expression of a <foreach> via OGNL. When nullable is false (the default foreach path) and the expression evaluates to null, there is nothing to iterate, so a BuilderException is thrown naming the expression. The nullable=true variant (used by <if>-style null-tolerant paths since 3.5.9) returns null instead; plain <foreach> does not.

Source

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

  /**
   * @deprecated Since 3.5.9, use the {@link #evaluateIterable(String, Object, boolean)}.
   */
  @Deprecated
  public Iterable<?> evaluateIterable(String expression, Object parameterObject) {
    return evaluateIterable(expression, parameterObject, false);
  }

  /**
   * @since 3.5.9
   */
  public Iterable<?> evaluateIterable(String expression, Object parameterObject, boolean nullable) {
    Object value = OgnlCache.getValue(expression, parameterObject);
    if (value == null) {
      if (nullable) {
        return null;
      }
      throw new BuilderException("The expression '" + expression + "' evaluated to a null value.");
    }
    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();

View on GitHub (pinned to 008069adb1)

Solutions

  1. Guard the foreach: <if test="items != null"> ... <foreach collection="items"...> ... </if>
  2. Default the collection to empty in the service/DTO: List<Item> items = Collections.emptyList(); or ids != null ? ids : Collections.emptyList()
  3. Fix the expression so it points at the real property name (@Param name, field name, or map key)
  4. For truly optional iterations, keep the whole SQL fragment conditional rather than passing null to foreach

Example fix

<!-- before -->
<foreach collection="ids" item="id">#{id}</foreach> <!-- ids is null -->

<!-- after -->
<if test="ids != null">
  <foreach collection="ids" item="id">#{id}</foreach>
</if>
Defensive patterns

Strategy: validation

Validate before calling

// in the service layer, before the call
List<Long> ids = Optional.ofNullable(req.getIds()).orElseGet(java.util.Collections::emptyList);
if (!ids.isEmpty()) { query.setIdList(ids); } // and skip the foreach branch otherwise

Prevention

When it happens

Trigger: <foreach collection="items" ...> when the parameter's items field is null; expression typo referencing a property that does not exist resolves to null; @Param("ids") list argument left null by the caller; optional query filters represented as null lists.

Common situations: Service methods with optional collection filters defaulting to null; API callers omitting list parameters; map-based parameters missing the foreach key; conditional building of query objects where some branches never set the list.

Related errors


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