mybatis/mybatis-3 · error · TooManyResultsException

Expected one result (or null) to be returned by selectOne(),

Error message

Expected one result (or null) to be returned by selectOne(), but found: " + list.size()

What it means

DefaultSqlSession.selectOne() runs the query as a list and, by explicit design decision (the comment records 'popular vote'), returns null for zero rows but throws TooManyResultsException when more than one row comes back. The contract is at-most-one: the statement was expected to match a single record but the database returned several.

Source

Thrown at src/main/java/org/apache/ibatis/session/defaults/DefaultSqlSession.java:79

  public DefaultSqlSession(Configuration configuration, Executor executor) {
    this(configuration, executor, false);
  }

  @Override
  public <T> T selectOne(String statement) {
    return this.selectOne(statement, null);
  }

  @Override
  public <T> T selectOne(String statement, Object parameter) {
    // Popular vote was to return null on 0 results and throw exception on too many.
    List<T> list = this.selectList(statement, parameter);
    if (list.size() == 1) {
      return list.get(0);
    }
    if (list.size() > 1) {
      throw new TooManyResultsException(
          "Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
    } else {
      return null;
    }
  }

  @Override
  public <K, V> Map<K, V> selectMap(String statement, String mapKey) {
    return this.selectMap(statement, null, mapKey, RowBounds.DEFAULT);
  }

  @Override
  public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey) {
    return this.selectMap(statement, parameter, mapKey, RowBounds.DEFAULT);
  }

  @Override
  public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {

View on GitHub (pinned to 008069adb1)

Solutions

  1. Tighten the WHERE clause with a unique key or add missing conditions so at most one row matches.
  2. If multiple rows are legitimate, switch to selectList() and take the first row explicitly (or return List<T> from the mapper).
  3. Clean duplicate rows and add a unique constraint where business rules say the field is unique.
  4. Add LIMIT 1 / FETCH FIRST 1 ROWS ONLY only if any-row semantics are acceptable.

Example fix

// before
User findByName(String name);  <!-- SELECT * FROM users WHERE name = #{name} -->
// after
List<User> findByName(String name);  <!-- same SQL, caller decides -->
// or ensure uniqueness:
<!-- SELECT * FROM users WHERE id = #{id} -->
Defensive patterns

Strategy: try-catch

Try / catch

try { User u = mapper.findByName(name); }
catch (TooManyResultsException e) {
  List<User> all = mapper.findByNameList(name); // fall back to list variant
  return all.get(0); // or apply real selection logic
}

Prevention

When it happens

Trigger: selectOne()/sqlSession.selectOne(statement, param) where the WHERE clause matches multiple rows: non-unique filter column, missing AND condition, LIKE filters, joins fan-out duplicating rows; also mapper interface methods returning a single object whose SQL lacks a LIMIT/unique key.

Common situations: Querying by a non-unique column (username, status); data drift inserting duplicate rows where uniqueness was assumed; dynamic <if> conditions all skipping so the query degrades to SELECT ... without WHERE; pagination removed during refactoring.

Related errors


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