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
- Fix the query so it returns at most one row: tighten the WHERE clause or add LIMIT 1 / FETCH FIRST 1 ROWS ONLY
- Add a unique constraint on the queried column(s) if uniqueness is the intent
- 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
- Add unique constraints for columns used in single-row lookups
- Prefer selectList + explicit pick when uniqueness is not guaranteed by the schema
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
- SelectKey returned more than one value.
- Constructor auto-mapping of ''{0}'' failed. The constructor
- Could not commit transaction. Cause: {}
- Line missing end-of-line terminator ({}) => {}
- Statement returned {} results where exactly one (1) was expe
AI-assisted analysis of mybatis/mybatis-3@008069adb1 (2026-08-14).
Data as JSON: /api/errors/e7b75f26d1323f3b.
Report an issue: GitHub.