mybatis/mybatis-3 · error · UnsupportedOperationException
Cannot remove element from Cursor
Error message
Cannot remove element from Cursor
What it means
The Iterator returned by a MyBatis Cursor is read-only: remove() always throws UnsupportedOperationException. A Cursor streams rows directly from a JDBC ResultSet over an already-executed query, so there is no backing collection from which an element could be removed. This matches the java.util.Iterator contract that makes remove() optional.
Source
Thrown at src/main/java/org/apache/ibatis/cursor/defaults/DefaultCursor.java:222
// Fill next with object fetched from hasNext()
T next = object;
if (!objectWrapperResultHandler.fetched) {
next = fetchNextUsingRowBound();
}
if (objectWrapperResultHandler.fetched) {
objectWrapperResultHandler.fetched = false;
object = null;
iteratorIndex++;
return next;
}
throw new NoSuchElementException();
}
@Override
public void remove() {
throw new UnsupportedOperationException("Cannot remove element from Cursor");
}
}
}
View on GitHub (pinned to 008069adb1)
Solutions
- Do not call remove(); build a filtered output collection instead while streaming
- If you need removal semantics, materialize into a List first and use its iterator or removeIf
- Filter in SQL (WHERE clause) so unwanted rows are never streamed
Example fix
// before
Iterator<User> it = cursor.iterator();
while (it.hasNext()) {
if (!it.next().isActive()) it.remove(); // UnsupportedOperationException
}
// after
List<User> active = new ArrayList<>();
for (User u : cursor) {
if (u.isActive()) active.add(u);
} Defensive patterns
Strategy: validation
Validate before calling
// Iterator.remove() is never valid on a Cursor;
// use a filter loop instead of mutation.
List<User> kept = new ArrayList<>();
for (User u : cursor) {
if (accept(u)) kept.add(u);
} Try / catch
try {
it.remove();
} catch (UnsupportedOperationException e) {
// expected for Cursor: switch to filtered-collection approach
} Prevention
- Filter while streaming into a new collection; never mutate through a Cursor
- Push filtering into the SQL WHERE clause when possible
- Materialize to List and use removeIf if you truly need removal semantics
When it happens
Trigger: Calling cursor.iterator().remove(); using the Cursor in any API that invokes Iterator.remove() (e.g., some collection constructors, Iterator-based removal utilities, or hand-rolled filtering loops that remove rejected items); calling remove() on the Cursor in a while(hasNext())/next() loop.
Common situations: Porting List-based iteration code (that used iterator.remove() to filter) to Cursor-based streaming; generic framework code that assumes a mutable iterator; unit tests that exercise remove() on iterators.
Related errors
- Cannot open more than one iterator on a Cursor
- Cursor results cannot be mapped to multiple resultMaps
- Remove is not supported, as it has no meaning in the context
- A Cursor is already closed.
- Mapped Statements with nested result mappings cannot be safe
AI-assisted analysis of mybatis/mybatis-3@008069adb1 (2026-08-14).
Data as JSON: /api/errors/fc7ccb8b0b6fc66c.
Report an issue: GitHub.