mybatis/mybatis-3 · error · BindingException

{methodName} cannot have multiple {paramType} parameters

Error message

{methodName} cannot have multiple {paramType} parameters

What it means

getUniqueParamIndex() scans a mapper method's parameters for special types (RowBounds and ResultHandler). At most one of each is allowed because MyBatis cannot decide which to use when two are present; a second match triggers this BindingException naming the offending type.

Source

Thrown at src/main/java/org/apache/ibatis/binding/MapperMethod.java:361

    /**
     * return whether return type is {@code java.util.Optional}.
     *
     * @return return {@code true}, if return type is {@code java.util.Optional}
     *
     * @since 3.5.0
     */
    public boolean returnsOptional() {
      return returnsOptional;
    }

    private Integer getUniqueParamIndex(Method method, Class<?> paramType) {
      Integer index = null;
      final Class<?>[] argTypes = method.getParameterTypes();
      for (int i = 0; i < argTypes.length; i++) {
        if (paramType.isAssignableFrom(argTypes[i])) {
          if (index != null) {
            throw new BindingException(
                method.getName() + " cannot have multiple " + paramType.getSimpleName() + " parameters");
          }
          index = i;
        }
      }
      return index;
    }

    public String getMapKey() {
      return mapKey;
    }

    private String getMapKey(Method method) {
      String mapKey = null;
      if (Map.class.isAssignableFrom(method.getReturnType())) {
        final MapKey mapKeyAnnotation = method.getAnnotation(MapKey.class);
        if (mapKeyAnnotation != null) {
          mapKey = mapKeyAnnotation.value();

View on GitHub (pinned to 008069adb1)

Solutions

  1. Keep exactly one RowBounds and/or one ResultHandler parameter, placed at the end of the signature
  2. If a business parameter happens to extend RowBounds/ResultHandler, change it to composition (hold an instance as a field) instead of inheritance

Example fix

// before
List<User> find(RowBounds page, MyRowBounds fallback);
// after
List<User> find(RowBounds page);
Defensive patterns

Strategy: validation

Validate before calling

int rowBounds = 0, handlers = 0;
for (Class<?> p : UserMapper.class.getMethod("find", ...).getParameterTypes()) {
  if (RowBounds.class.isAssignableFrom(p)) rowBounds++;
  if (ResultHandler.class.isAssignableFrom(p)) handlers++;
}
// assert rowBounds <= 1 && handlers <= 1

Prevention

When it happens

Trigger: Declaring 'List<User> find(RowBounds a, RowBounds b, ...)' or 'void handle(ResultHandler<X> h1, ResultHandler<X> h2, ...)'; passing subtypes of RowBounds/ResultHandler as ordinary business parameters (they are auto-detected via isAssignableFrom, so a user class extending RowBounds counts too).

Common situations: Wrapping RowBounds in a custom pagination class that itself extends RowBounds and also passing RowBounds; DSL-style APIs that bundle a ResultHandler inside a parameter object that implements ResultHandler.

Related errors


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