{"record":{"id":"d2c3179d45f3e85f","repo":"mybatis/mybatis-3","slug":"constructor-auto-mapping-of-0-failed-the-co","errorCode":null,"errorMessage":"Constructor auto-mapping of ''{0}'' failed. The constructor takes ''{1}'' arguments, but there are only ''{2}'' columns in the result set.","messagePattern":"Constructor auto-mapping of ''(.+?)'' failed\\. The constructor takes ''(.+?)'' arguments, but there are only ''(.+?)'' columns in the result set\\.","errorType":"exception","errorClass":"ExecutorException","httpStatus":null,"severity":"error","filePath":"src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java","lineNumber":915,"sourceCode":"      throws SQLException {\n    boolean foundValues = false;\n    if (configuration.isArgNameBasedConstructorAutoMapping()) {\n      foundValues = applyArgNameBasedConstructorAutoMapping(rsw, resultMap, columnPrefix, constructorArgTypes,\n          constructorArgs, constructor, foundValues);\n    } else {\n      foundValues = applyColumnOrderBasedConstructorAutomapping(rsw, constructorArgTypes, constructorArgs, constructor,\n          foundValues);\n    }\n    return foundValues || configuration.isReturnInstanceForEmptyRow()\n        ? objectFactory.create(resultType, constructorArgTypes, constructorArgs) : null;\n  }\n\n  private boolean applyColumnOrderBasedConstructorAutomapping(ResultSetWrapper rsw, List<Class<?>> constructorArgTypes,\n      List<Object> constructorArgs, Constructor<?> constructor, boolean foundValues) throws SQLException {\n    Class<?>[] parameterTypes = constructor.getParameterTypes();\n\n    if (parameterTypes.length > rsw.getClassNames().size()) {\n      throw new ExecutorException(MessageFormat.format(\n          \"Constructor auto-mapping of ''{0}'' failed. The constructor takes ''{1}'' arguments, but there are only ''{2}'' columns in the result set.\",\n          constructor, parameterTypes.length, rsw.getClassNames().size()));\n    }\n\n    for (int i = 0; i < parameterTypes.length; i++) {\n      Class<?> parameterType = parameterTypes[i];\n      String columnName = rsw.getColumnNames().get(i);\n      TypeHandler<?> typeHandler = rsw.getTypeHandler(parameterType, columnName);\n      Object value = typeHandler.getResult(rsw.getResultSet(), columnName);\n      constructorArgTypes.add(parameterType);\n      constructorArgs.add(value);\n      foundValues = value != null || foundValues;\n    }\n    return foundValues;\n  }\n\n  private boolean applyArgNameBasedConstructorAutoMapping(ResultSetWrapper rsw, ResultMap resultMap,\n      String columnPrefix, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, Constructor<?> constructor,","sourceCodeStart":897,"sourceCodeEnd":933,"githubUrl":"https://github.com/mybatis/mybatis-3/blob/008069adb1b089579b5dcba87ee591908b263274/src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java#L897-L933","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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)."],"exampleFix":"// before\npublic User(Long id, String name, String email) { ... }\n// query: SELECT id, name FROM users\n\n// after\npublic User(Long id, String name, String email) { ... }\n// query: SELECT id, name, email FROM users","handlingStrategy":"validation","validationCode":"// before opening the cursor you cannot count columns; validate after query setup\ntry (ResultSet dummy = statement.executeQuery(\"SELECT * FROM (\" + sql + \") q WHERE 1=0\")) {\n  int columnCount = dummy.getMetaData().getColumnCount();\n  if (columnCount < ctor.getParameterCount()) {\n    throw new IllegalStateException(\"Query returns \" + columnCount\n        + \" columns but constructor needs \" + ctor.getParameterCount());\n  }\n}","typeGuard":null,"tryCatchPattern":"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.","preventionTips":["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."],"tags":["mybatis","constructor-automapping","resultset","sql"],"backgroundTag":null,"analyzedSha":"008069adb1b089579b5dcba87ee591908b263274","analyzedAt":"2026-08-14T13:07:10.264Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}