mybatis/mybatis-3 · error · ExecutorException

Constructor auto-mapping of ''{0}'' failed. The constructor

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.

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)

Solutions

  1. Add the missing columns to the SELECT clause so the result set has at least as many columns as the constructor takes.
  2. Switch to a constructor whose parameter count matches the result set columns (or use @AutomapConstructor on the right one).
  3. Remove unused parameters from the constructor, or supply them via <arg> with a literal/value rather than a column.
  4. 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

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


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