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

  1. Do not call remove(); build a filtered output collection instead while streaming
  2. If you need removal semantics, materialize into a List first and use its iterator or removeIf
  3. 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

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


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