mybatis/mybatis-3 · error · BuilderException

Unknown element <" + nodeName + "> in SQL statement.

Error message

Unknown element <" + nodeName + "> in SQL statement.

What it means

XMLScriptBuilder parses the body of a statement mapped to XMLLanguageDriver and only recognizes MyBatis' own dynamic SQL tags (if, choose, when, otherwise, trim, where, set, foreach, bind) plus text. When a child element's node name has no registered NodeHandler, this BuilderException is thrown at configuration/mapper-parsing time. It almost always means a misspelled or non-MyBatis XML element inside a SELECT/INSERT/UPDATE/DELETE (or sql fragment) block.

Source

Thrown at src/main/java/org/apache/ibatis/scripting/xmltags/XMLScriptBuilder.java:108

      XNode child = node.newXNode(children.item(i));
      if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
        String data = child.getStringBody("");
        if (data.trim().isEmpty()) {
          contents.add(emptyNodeCache.computeIfAbsent(data, EmptySqlNode::new));
          continue;
        }
        TextSqlNode textSqlNode = new TextSqlNode(data);
        if (textSqlNode.isDynamic()) {
          contents.add(textSqlNode);
          isDynamic = true;
        } else {
          contents.add(new StaticTextSqlNode(data));
        }
      } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
        String nodeName = child.getNode().getNodeName();
        NodeHandler handler = nodeHandlerMap.get(nodeName);
        if (handler == null) {
          throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
        }
        handler.handleNode(child, contents);
        isDynamic = true;
      }
    }
    return new MixedSqlNode(contents);
  }

  private interface NodeHandler {
    void handleNode(XNode nodeToHandle, List<SqlNode> targetContents);
  }

  private static class BindHandler implements NodeHandler {
    public BindHandler() {
      // Prevent Synthetic Access
    }

    @Override

View on GitHub (pinned to 008069adb1)

Solutions

  1. Open the mapper XML named in the stack trace and fix the element shown in <...> to one of: if, choose, when, otherwise, trim, where, set, foreach, bind.
  2. Remove non-MyBatis/JSTL tags from statement bodies; move that logic into OGNL test expressions or provider-based SQL.
  3. Re-run and let the parser confirm the next error until the file is clean (it fails fast on the first bad element).

Example fix

// before
<select id="find" resultMap="r">
  SELECT * FROM t
  <wher><if test="id != null">id = #{id}</if></wher>
</select>
// after
<select id="find" resultMap="r">
  SELECT * FROM t
  <where><if test="id != null">id = #{id}</if></where>
</select>
Defensive patterns

Strategy: validation

Validate before calling

// Fast-fail at startup: parse all mappers eagerly
SqlSessionFactory f = new SqlSessionFactoryBuilder().build(cfgXml);
// parsing happens at build(); any unknown element throws here, before serving traffic

Try / catch

try { factory = builder.build(in); }
catch (BuilderException e) { /* fail startup with mapper file + element name */ throw e; }

Prevention

When it happens

Trigger: A typo such as <wher>, <ifo>, <foreache> inside a statement body; using JSTL or custom tags like <c:if> inside MyBatis XML; a stray namespace prefix element; nesting a <select> or other top-level mapper element inside a statement body by mistake.

Common situations: Hand-editing mapper XML and misspelling a dynamic tag; copy-pasting from a JSP/JSTL template; XML IDE auto-completing the wrong tag; editing an included <sql> fragment with an element type not valid there.

Related errors


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