mybatis/mybatis-3 · error · ExecutorException

Statement returned more than one row, where no more than one

Error message

Statement returned more than one row, where no more than one was expected.

What it means

ResultExtractor converts a query's result list into the method's declared return type. When the target type is neither a Collection nor an array but the JDBC result set produced more than one row, there is no valid way to squeeze multiple rows into a single-object return, so an ExecutorException is thrown. This is the internal machinery behind SqlSession.selectOne()'s 'returned more than one result' behavior for mapped statements with scalar/single-object result types.

Source

Thrown at src/main/java/org/apache/ibatis/executor/ResultExtractor.java:57

    if (targetType != null && targetType.isAssignableFrom(list.getClass())) {
      value = list;
    } else if (targetType != null && objectFactory.isCollection(targetType)) {
      value = objectFactory.create(targetType);
      MetaObject metaObject = configuration.newMetaObject(value);
      metaObject.addAll(list);
    } else if (targetType != null && targetType.isArray()) {
      Class<?> arrayComponentType = targetType.getComponentType();
      Object array = Array.newInstance(arrayComponentType, list.size());
      if (arrayComponentType.isPrimitive()) {
        for (int i = 0; i < list.size(); i++) {
          Array.set(array, i, list.get(i));
        }
        value = array;
      } else {
        value = list.toArray((Object[]) array);
      }
    } else if (list != null && list.size() > 1) {
      throw new ExecutorException("Statement returned more than one row, where no more than one was expected.");
    } else if (list != null && list.size() == 1) {
      value = list.get(0);
    }
    return value;
  }
}

View on GitHub (pinned to 008069adb1)

Solutions

  1. Fix the query so it returns at most one row: tighten the WHERE clause or add LIMIT 1 / FETCH FIRST 1 ROWS ONLY
  2. Add a unique constraint on the queried column(s) if uniqueness is the intent
  3. If multiple rows are legitimately possible, change the mapper return type to List<T> or an array type

Example fix

// before
User selectByEmail(String email); // two users share the email -> exception

// after
List<User> selectByEmail(String email); // or ensure email is unique in DB
Defensive patterns

Strategy: validation

Validate before calling

// If more than one row is possible, query the collection and pick safely
List<User> rows = sqlSession.selectList("selectByEmail", email);
if (rows.size() > 1) { throw new IllegalStateException("email not unique: " + rows.size()); }
User u = rows.isEmpty() ? null : rows.get(0);

Try / catch

try { mapper.selectById(id); } catch (PersistenceException e) { if (e.getMessage() != null && e.getMessage().contains("more than one row")) { /* dedupe data or switch to selectList */ } else throw e; }

Prevention

When it happens

Trigger: Calling a mapper method whose return type is a single object (e.g. User selectById(...)) whose SQL matches more than one row; using selectOne semantics on a statement whose ResultMap maps to a non-collection type while the query returns 2+ rows.

Common situations: A WHERE clause that was assumed unique (bad data, missing unique constraint), missing LIMIT 1 / FETCH FIRST 1 ROWS ONLY, date-range queries that accidentally match duplicates, or a join that fans out rows.

Related errors


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