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
- Keep exactly one RowBounds and/or one ResultHandler parameter, placed at the end of the signature
- 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
- Keep RowBounds/ResultHandler as trailing parameters, at most one each
- Never let business parameter types extend RowBounds or ResultHandler
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
- method {name} needs either a @ResultMap annotation, a @Resul
- Parameter '{key}' not found. Available parameters are {keySe
- Unknown execution method for: {name}
- Mapper method '{name}' attempted to return null from a metho
- Mapper method '{name}' has an unsupported return type: {retu
AI-assisted analysis of mybatis/mybatis-3@008069adb1 (2026-08-14).
Data as JSON: /api/errors/f92d4ec542384771.
Report an issue: GitHub.