mybatis/mybatis-3 · error · ExecutorException

'argNameBasedConstructorAutoMapping' is enabled and the clas

Error message

'argNameBasedConstructorAutoMapping' is enabled and the class ''{0}'' has multiple constructors, so @AutomapConstructor must be added to one of the constructors.

What it means

Thrown when the configuration setting 'argNameBasedConstructorAutoMapping' is enabled and MyBatis needs to auto-map a result type by constructor, but the class declares more than one constructor and none is annotated with @AutomapConstructor. Because argument-name-based matching cannot decide which constructor to use, MyBatis refuses to guess. The annotated-constructor check happens first; only when no constructor carries the annotation does this exception fire.

Source

Thrown at src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java:874

            "No constructor found in " + resultType.getName() + " matching " + rsw.getClassNames())));
  }

  private Optional<Constructor<?>> findConstructorForAutomapping(final Class<?> resultType, ResultSetWrapper rsw) {
    Constructor<?>[] constructors = resultType.getDeclaredConstructors();
    if (constructors.length == 1) {
      return Optional.of(constructors[0]);
    }
    Optional<Constructor<?>> annotated = Arrays.stream(constructors)
        .filter(x -> x.isAnnotationPresent(AutomapConstructor.class)).reduce((x, y) -> {
          throw new ExecutorException("@AutomapConstructor should be used in only one constructor.");
        });
    if (annotated.isPresent()) {
      return annotated;
    }
    if (configuration.isArgNameBasedConstructorAutoMapping()) {
      // Finding-best-match type implementation is possible,
      // but using @AutomapConstructor seems sufficient.
      throw new ExecutorException(MessageFormat.format(
          "'argNameBasedConstructorAutoMapping' is enabled and the class ''{0}'' has multiple constructors, so @AutomapConstructor must be added to one of the constructors.",
          resultType.getName()));
    } else {
      return Arrays.stream(constructors).filter(x -> findUsableConstructorByArgTypes(x, rsw.getJdbcTypes())).findAny();
    }
  }

  private boolean findUsableConstructorByArgTypes(final Constructor<?> constructor, final List<JdbcType> jdbcTypes) {
    final Class<?>[] parameterTypes = constructor.getParameterTypes();
    if (parameterTypes.length != jdbcTypes.size()) {
      return false;
    }
    for (int i = 0; i < parameterTypes.length; i++) {
      if (!typeHandlerRegistry.hasTypeHandler(parameterTypes[i], jdbcTypes.get(i))) {
        return false;
      }
    }
    return true;

View on GitHub (pinned to 008069adb1)

Solutions

  1. Add org.apache.ibatis.annotations.@AutomapConstructor to exactly one constructor of the result class (the one MyBatis should use for auto-mapping).
  2. Alternatively remove constructors so the class has exactly one constructor, letting MyBatis pick constructors[0].
  3. Alternatively disable argNameBasedConstructorAutoMapping so MyBatis falls back to matching constructors by JDBC type arity (findUsableConstructorByArgTypes).
  4. Alternatively specify the constructor explicitly in the resultMap with a <constructor> node instead of relying on auto-mapping.

Example fix

// before
public class User {
  public User() {}
  public User(Long id, String name) { ... }
}
// mybatis-config.xml: <setting name="argNameBasedConstructorAutoMapping" value="true"/>

// after
import org.apache.ibatis.annotations.AutomapConstructor;
public class User {
  public User() {}
  @AutomapConstructor
  public User(Long id, String name) { ... }
}
Defensive patterns

Strategy: validation

Validate before calling

// at startup, verify every constructor-mapped result type is unambiguous
private static final Predicate<Class<?>> HAS_AUTOMAP =
    c -> Arrays.stream(c.getDeclaredConstructors())
               .anyMatch(x -> x.isAnnotationPresent(AutomapConstructor.class));

void checkConstructorAutomapping(Configuration cfg, Class<?>... resultTypes) {
  if (!cfg.isArgNameBasedConstructorAutoMapping()) return;
  for (Class<?> t : resultTypes) {
    Constructor<?>[] ctors = t.getDeclaredConstructors();
    if (ctors.length > 1 && !HAS_AUTOMAP.test(t)) {
      throw new IllegalStateException(t.getName()
          + " has multiple constructors but no @AutomapConstructor;"
          + " argNameBasedConstructorAutoMapping will fail at query time.");
    }
  }
}

Prevention

When it happens

Trigger: configuration.setArgNameBasedConstructorAutoMapping(true) (or <settings argNameBasedConstructorAutoMapping="true"/>) plus a result type with multiple constructors (e.g. a class with both an all-args and a no-args or partial constructor) mapped via constructor auto-mapping (no explicit <constructor> node, no @AutomapConstructor anywhere). Also requires the class not to have exactly one constructor (constructors[0] shortcut only applies to a single-constructor class).

Common situations: Enabling argNameBasedConstructorAutoMapping to use -parameters javac flag with Java 8+ parameter names, then mapping DTOs that legitimately overload constructors (Lombok @AllArgsConstructor + @NoArgsConstructor). Also hits after adding a convenience/telescoping constructor to a previously single-constructor DTO.

Related errors


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