baomidou/mybatis-plus · error · BindingException

method %s needs either a @ResultMap annotation, a @ResultTyp

Error message

method %s needs either a @ResultMap annotation, a @ResultType annotation, or a resultType attribute in XML so a ResultHandler can be used as a parameter.

What it means

executeWithResultHandler runs when a mapper method takes a ResultHandler parameter. MyBatis streams rows into the handler instead of materializing results, so it must know the row type; if the statement's result map resolves to void.class (no resultType/resultMap) and it is not a CALLABLE statement, execution is refused.

Source

Thrown at mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/override/MybatisMapperMethod.java:166

        if (method.returnsVoid()) {
            result = null;
        } else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) {
            result = rowCount;
        } else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) {
            result = (long) rowCount;
        } else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) {
            result = rowCount > 0;
        } else {
            throw new BindingException("Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType());
        }
        return result;
    }

    private void executeWithResultHandler(SqlSession sqlSession, Object[] args) {
        MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(command.getName());
        if (!StatementType.CALLABLE.equals(ms.getStatementType())
            && void.class.equals(ms.getResultMaps().get(0).getType())) {
            throw new BindingException("method " + command.getName()
                + " needs either a @ResultMap annotation, a @ResultType annotation,"
                + " or a resultType attribute in XML so a ResultHandler can be used as a parameter.");
        }
        Object param = this.convertArgsToSqlCommandParam(args);
        if (method.hasRowBounds()) {
            RowBounds rowBounds = method.extractRowBounds(args);
            sqlSession.select(command.getName(), param, rowBounds, method.extractResultHandler(args));
        } else {
            sqlSession.select(command.getName(), param, method.extractResultHandler(args));
        }
    }

    private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
        List<E> result;
        Object param = this.convertArgsToSqlCommandParam(args);
        if (method.hasRowBounds()) {
            RowBounds rowBounds = method.extractRowBounds(args);
            result = sqlSession.selectList(command.getName(), param, rowBounds);

View on GitHub (pinned to bf67d90747)

Solutions

  1. Add resultType (or resultMap) to the statement: @Select(value = "...", resultType = User.class) or <select id="findAll" resultType="User">
  2. For annotation mappers use @ResultType(User.class) on the method or the statement's resultType attribute
  3. Alternatively drop the ResultHandler parameter and return a typed List if streaming is not required

Example fix

// before
@Select("SELECT * FROM user")
void scanUsers(ResultHandler<User> handler);
// after
@Select(value = "SELECT * FROM user", resultType = User.class)
void scanUsers(ResultHandler<User> handler);
Defensive patterns

Strategy: validation

Validate before calling

// Before exposing a ResultHandler method, verify the statement declares a row type
MappedStatement ms = configuration.getMappedStatement("com.example.UserMapper.scanUsers");
Class<?> rowType = ms.getResultMaps().get(0).getType();
if (void.class.equals(rowType)) throw new IllegalStateException("scanUsers needs resultType/@ResultType");

Try / catch

catch BindingException on first streaming call and fail with guidance to add resultType; do not swallow — every call would fail identically.

Prevention

When it happens

Trigger: Mapper method like 'void findAll(ResultHandler<User> handler)' whose statement (annotation or XML) declares no resultType/@ResultMap — e.g. a bare @Select without resultType, or a <select> element missing the attribute.

Common situations: Streaming large result sets with ResultHandler and forgetting resultType; annotation @Select("SELECT * FROM user") with no @ResultType; migrating from returning-list methods and dropping the resultType attribute.

Related errors


AI-assisted analysis of baomidou/mybatis-plus@bf67d90747 (2026-08-14). Data as JSON: /api/errors/baa4461bcdb0ab90. Report an issue: GitHub.