ReactiveX/RxJava · warning · UnsupportedOperationException

Read-only iterator.

Error message

Read-only iterator.

What it means

Thrown by Iterator.remove() on the iterator returned from Observable.blockingLatest(). The iterator surfaces the most recent Notification from a push source and is read-only by contract, so remove() always throws UnsupportedOperationException("Read-only iterator."). There is no mutable backing collection to alter.

Source

Thrown at src/main/java/io/reactivex/rxjava4/internal/operators/observable/BlockingObservableLatest.java:111

                    throw ExceptionHelper.wrapOrThrow(n.getError());
                }
            }
            return iteratorNotification.isOnNext();
        }

        @Override
        public T next() {
            if (hasNext()) {
                T v = iteratorNotification.getValue();
                iteratorNotification = null;
                return v;
            }
            throw new NoSuchElementException();
        }

        @Override
        public void remove() {
            throw new UnsupportedOperationException("Read-only iterator.");
        }
    }
}

View on GitHub (pinned to a8ab535614)

Solutions

  1. Drop the it.remove() call; blockingLatest() iterators are read-only.
  2. Filter upstream with Observable.filter(...) to remove unwanted items before blocking iteration.
  3. Accumulate the items you want to keep in a separate list rather than mutating the iterator.
  4. Wrap with a forwarding Iterator whose remove() is a no-op if a dependency mandates remove().

Example fix

// before
Iterator<T> it = observable.blockingLatest().iterator();
while (it.hasNext()) { T v = it.next(); if (skip(v)) it.remove(); }
// after
Iterator<T> it = observable.blockingLatest().iterator();
while (it.hasNext()) { T v = it.next(); if (!skip(v)) { /* keep v */ } }
Defensive patterns

Strategy: validation

Validate before calling

// Read-only iterator: never call remove().
Iterator<T> it = observable.blockingLatest().iterator();
while (it.hasNext()) {
    T v = it.next();
    // process v; it.remove() is unsupported
}

Prevention

When it happens

Trigger: Calling it.remove() on the iterator from observable.blockingLatest().iterator(); a generic cleanup routine invoking remove(); reusing collection-based drain code.

Common situations: Imperative drain loops ported from List iteration that included remove(); libraries that call remove() on consumed iterators; misunderstanding blockingLatest() as a mutable buffer.

Related errors


AI-assisted analysis of ReactiveX/RxJava@a8ab535614 (2026-08-13). Data as JSON: /api/errors/69328b9145b0499d. Report an issue: GitHub.