{"record":{"id":"9e0a9987a0a19e00","repo":"mybatis/mybatis-3","slug":"the-expression-expression-evaluated-to-a","errorCode":null,"errorMessage":"The expression '\" + expression + \"' evaluated to a null value.","messagePattern":"The expression '\" \\+ expression \\+ \"' evaluated to a null value\\.","errorType":"exception","errorClass":"BuilderException","httpStatus":null,"severity":"error","filePath":"src/main/java/org/apache/ibatis/scripting/xmltags/ExpressionEvaluator.java","lineNumber":61,"sourceCode":"\n  /**\n   * @deprecated Since 3.5.9, use the {@link #evaluateIterable(String, Object, boolean)}.\n   */\n  @Deprecated\n  public Iterable<?> evaluateIterable(String expression, Object parameterObject) {\n    return evaluateIterable(expression, parameterObject, false);\n  }\n\n  /**\n   * @since 3.5.9\n   */\n  public Iterable<?> evaluateIterable(String expression, Object parameterObject, boolean nullable) {\n    Object value = OgnlCache.getValue(expression, parameterObject);\n    if (value == null) {\n      if (nullable) {\n        return null;\n      }\n      throw new BuilderException(\"The expression '\" + expression + \"' evaluated to a null value.\");\n    }\n    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();","sourceCodeStart":43,"sourceCodeEnd":79,"githubUrl":"https://github.com/mybatis/mybatis-3/blob/008069adb1b089579b5dcba87ee591908b263274/src/main/java/org/apache/ibatis/scripting/xmltags/ExpressionEvaluator.java#L43-L79","documentation":"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.","triggerScenarios":"<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.","commonSituations":"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.","solutions":["Guard the foreach: <if test=\"items != null\"> ... <foreach collection=\"items\"...> ... </if>","Default the collection to empty in the service/DTO: List<Item> items = Collections.emptyList(); or ids != null ? ids : Collections.emptyList()","Fix the expression so it points at the real property name (@Param name, field name, or map key)","For truly optional iterations, keep the whole SQL fragment conditional rather than passing null to foreach"],"exampleFix":"<!-- before -->\n<foreach collection=\"ids\" item=\"id\">#{id}</foreach> <!-- ids is null -->\n\n<!-- after -->\n<if test=\"ids != null\">\n  <foreach collection=\"ids\" item=\"id\">#{id}</foreach>\n</if>","handlingStrategy":"validation","validationCode":"// in the service layer, before the call\nList<Long> ids = Optional.ofNullable(req.getIds()).orElseGet(java.util.Collections::emptyList);\nif (!ids.isEmpty()) { query.setIdList(ids); } // and skip the foreach branch otherwise","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Wrap every <foreach> in <if test=\"collection != null\">","Default collection parameters to empty lists in DTOs and @Param arguments","Verify foreach collection names match @Param/property names exactly"],"tags":["dynamic-sql","foreach","null-safety","ognl","mybatis"],"backgroundTag":null,"analyzedSha":"008069adb1b089579b5dcba87ee591908b263274","analyzedAt":"2026-08-14T13:07:10.264Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}