mybatis/mybatis-3 · error · ExecutorException
Constructor auto-mapping of
Error message
Constructor auto-mapping of ''{0}'' failed. The constructor takes ''{1}'' arguments, but there are only ''{2}'' columns in the result set. What it means
Thrown during column-order-based constructor auto-mapping when the chosen constructor's parameter count exceeds the number of columns in the result set. MyBatis maps constructor arguments positionally (column i -> parameter i), so if the constructor needs more arguments than there are columns, mapping is impossible and fails fast with the constructor, its arity, and the column count.
Solutions
- Add the missing columns to the SELECT clause so the result set has at least as many columns as the constructor takes.
- Switch to a constructor whose parameter count matches the result set columns (or use @AutomapConstructor on the right one).
- Remove unused parameters from the constructor, or supply them via <arg> with a literal/value rather than a column.
- If columns exist but names do not line up positionally, use argNameBasedConstructorAutoMapping with -parameters so matching is by name, and ensure column names match parameter names (mind mapUnderscoreToCamelCase).
Example fix
// before
public User(Long id, String name, String email) { ... }
// query: SELECT id, name FROM users
// after
public User(Long id, String name, String email) { ... }
// query: SELECT id, name, email FROM users Defensive patterns
Strategy: validation
Validate before calling
// before opening the cursor you cannot count columns; validate after query setup
try (ResultSet dummy = statement.executeQuery("SELECT * FROM (" + sql + ") q WHERE 1=0")) {
int columnCount = dummy.getMetaData().getColumnCount();
if (columnCount < ctor.getParameterCount()) {
throw new IllegalStateException("Query returns " + columnCount
+ " columns but constructor needs " + ctor.getParameterCount());
}
} Try / catch
catch (ExecutorException e) when e.getMessage().contains("Constructor auto-mapping") && e.getMessage().contains("columns in the result set") -> log column list vs constructor signature and fail the surrounding test/call explicitly. Prevention
- Keep SELECT column lists explicit (no SELECT *) so constructor arity and columns evolve together.
- Add an integration test per mapped query asserting the DTO deserializes, catching arity drift on schema change.
When it happens
Trigger: A result map or auto-mapping selects a constructor with N parameters while the executed query returns fewer than N columns (SELECT with fewer columns than the constructor needs, or a constructor with extra parameters). Happens on the non-argNameBased path where a constructor was selected by type/arity via findUsableConstructorByArgTypes, or with @AutomapConstructor on an oversized constructor.
Common situations: Adding a new field/parameter to a constructor without updating the SELECT column list; using SELECT * against a view that dropped columns; explicit <constructor> arg entries exceeding returned columns; joining to a projection with fewer columns than the DTO constructor expects.
Related errors
- 'argNameBasedConstructorAutoMapping' is enabled and the…
- Constructor auto-mapping of
- Could not commit transaction. Cause
- Error attempting to get column #" + columnIndex + " from…
- Error attempting to get column '" + columnName + "' from…
AI-assisted analysis of mybatis/mybatis-3@008069adb1 (2026-08-14).
Data as JSON: /api/errors/d2c3179d45f3e85f.
Report an issue: GitHub.
Appendix: source
Thrown at src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java:915
throws SQLException {
boolean foundValues = false;
if (configuration.isArgNameBasedConstructorAutoMapping()) {
foundValues = applyArgNameBasedConstructorAutoMapping(rsw, resultMap, columnPrefix, constructorArgTypes,
constructorArgs, constructor, foundValues);
} else {
foundValues = applyColumnOrderBasedConstructorAutomapping(rsw, constructorArgTypes, constructorArgs, constructor,
foundValues);
}
return foundValues || configuration.isReturnInstanceForEmptyRow()
? objectFactory.create(resultType, constructorArgTypes, constructorArgs) : null;
}
private boolean applyColumnOrderBasedConstructorAutomapping(ResultSetWrapper rsw, List<Class<?>> constructorArgTypes,
List<Object> constructorArgs, Constructor<?> constructor, boolean foundValues) throws SQLException {
Class<?>[] parameterTypes = constructor.getParameterTypes();
if (parameterTypes.length > rsw.getClassNames().size()) {
throw new ExecutorException(MessageFormat.format(
"Constructor auto-mapping of ''{0}'' failed. The constructor takes ''{1}'' arguments, but there are only ''{2}'' columns in the result set.",
constructor, parameterTypes.length, rsw.getClassNames().size()));
}
for (int i = 0; i < parameterTypes.length; i++) {
Class<?> parameterType = parameterTypes[i];
String columnName = rsw.getColumnNames().get(i);
TypeHandler<?> typeHandler = rsw.getTypeHandler(parameterType, columnName);
Object value = typeHandler.getResult(rsw.getResultSet(), columnName);
constructorArgTypes.add(parameterType);
constructorArgs.add(value);
foundValues = value != null || foundValues;
}
return foundValues;
}
private boolean applyArgNameBasedConstructorAutoMapping(ResultSetWrapper rsw, ResultMap resultMap,
String columnPrefix, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, Constructor<?> constructor,View on GitHub (pinned to 008069adb1)