flawopen.com/SQL Injection in Java/MyBatis
#{value} is safe — it compiles to a bound JDBC parameter. ${value} is raw text substitution before the SQL is even parsed — it's exactly as dangerous as string concatenation, despite looking almost identical in the XML mapper.
<select id="getUser">
SELECT * FROM users
WHERE id = ${id}
</select>
// id is spliced into the SQL text
// before the statement is built
<select id="getUser">
SELECT * FROM users
WHERE id = #{id}
</select>
// compiles to a PreparedStatement
// with id bound as a parameter
${} isn't a bug in MyBatis — it exists for cases where you genuinely need to substitute something a bind parameter can't represent, like a table name or a column name in dynamic sorting (ORDER BY ${column}). The problem is exclusively when ${} is used for a value that could instead have been a bound parameter.
Since ${} is unavoidable for identifiers, validate the value against a fixed allow-list of known-legitimate table/column names before it ever reaches the mapper — never accept an arbitrary string for this position.
grep -rn '\$\{' --include="*.xml" . | grep -i mapper
Yes — @Select("... WHERE id = ${id}") on a Java interface method has the exact same risk as the XML equivalent.
Not a dedicated one in common use — a CI grep for ${} in mapper files, reviewed manually against the allow-list rule above, is currently the practical check.