{"record":{"id":"f92d4ec542384771","repo":"mybatis/mybatis-3","slug":"methodname-cannot-have-multiple-paramtype-para","errorCode":null,"errorMessage":"{methodName} cannot have multiple {paramType} parameters","messagePattern":"(.+?) cannot have multiple (.+?) parameters","errorType":"exception","errorClass":"BindingException","httpStatus":null,"severity":"error","filePath":"src/main/java/org/apache/ibatis/binding/MapperMethod.java","lineNumber":361,"sourceCode":"\n    /**\n     * return whether return type is {@code java.util.Optional}.\n     *\n     * @return return {@code true}, if return type is {@code java.util.Optional}\n     *\n     * @since 3.5.0\n     */\n    public boolean returnsOptional() {\n      return returnsOptional;\n    }\n\n    private Integer getUniqueParamIndex(Method method, Class<?> paramType) {\n      Integer index = null;\n      final Class<?>[] argTypes = method.getParameterTypes();\n      for (int i = 0; i < argTypes.length; i++) {\n        if (paramType.isAssignableFrom(argTypes[i])) {\n          if (index != null) {\n            throw new BindingException(\n                method.getName() + \" cannot have multiple \" + paramType.getSimpleName() + \" parameters\");\n          }\n          index = i;\n        }\n      }\n      return index;\n    }\n\n    public String getMapKey() {\n      return mapKey;\n    }\n\n    private String getMapKey(Method method) {\n      String mapKey = null;\n      if (Map.class.isAssignableFrom(method.getReturnType())) {\n        final MapKey mapKeyAnnotation = method.getAnnotation(MapKey.class);\n        if (mapKeyAnnotation != null) {\n          mapKey = mapKeyAnnotation.value();","sourceCodeStart":343,"sourceCodeEnd":379,"githubUrl":"https://github.com/mybatis/mybatis-3/blob/008069adb1b089579b5dcba87ee591908b263274/src/main/java/org/apache/ibatis/binding/MapperMethod.java#L343-L379","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":"// before\nList<User> find(RowBounds page, MyRowBounds fallback);\n// after\nList<User> find(RowBounds page);","handlingStrategy":"validation","validationCode":"int rowBounds = 0, handlers = 0;\nfor (Class<?> p : UserMapper.class.getMethod(\"find\", ...).getParameterTypes()) {\n  if (RowBounds.class.isAssignableFrom(p)) rowBounds++;\n  if (ResultHandler.class.isAssignableFrom(p)) handlers++;\n}\n// assert rowBounds <= 1 && handlers <= 1","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep RowBounds/ResultHandler as trailing parameters, at most one each","Never let business parameter types extend RowBounds or ResultHandler"],"tags":["mybatis","binding","rowbounds","result-handler","parameters"],"backgroundTag":null,"analyzedSha":"008069adb1b089579b5dcba87ee591908b263274","analyzedAt":"2026-08-14T13:07:10.264Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}