ReactiveX/RxJava · warning · UnsupportedOperationException

Read only iterator

Error message

Read only iterator

What it means

Thrown by Iterator.remove() on the iterator from Observable.blockingNext(). The iterator yields Notifications pulled one at a time from a push source and is permanently read-only, so remove() throws UnsupportedOperationException("Read only iterator"). No backing collection exists to remove from.

Source

Thrown at src/main/java/io/reactivex/rxjava4/internal/operators/observable/BlockingObservableNext.java:128

        @Override
        public T next() {
            if (error != null) {
                // If any error has already been thrown, throw it again.
                throw ExceptionHelper.wrapOrThrow(error);
            }
            if (hasNext()) {
                isNextConsumed = true;
                return next;
            }
            else {
                throw new NoSuchElementException("No more elements");
            }
        }

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

    static final class NextObserver<T> extends DisposableObserver<Notification<T>> {
        private final BlockingQueue<Notification<T>> buf = new ArrayBlockingQueue<>(1);
        final AtomicInteger waiting = new AtomicInteger();

        @Override
        public void onComplete() {
            // ignore
        }

        @Override
        public void onError(Throwable e) {
            RxJavaPlugins.onError(e);
        }

        @Override

View on GitHub (pinned to a8ab535614)

Solutions

  1. Delete the it.remove() call; the iterator is read-only.
  2. Filter upstream with Observable.filter(...) to drop items before blocking iteration.
  3. Collect retained values into your own list rather than mutating the iterator.
  4. Wrap with a forwarding Iterator whose remove() is a no-op if a component requires remove().

Example fix

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

Strategy: validation

Validate before calling

// Read-only iterator: never call remove().
Iterator<T> it = observable.blockingNext().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.blockingNext().iterator(); a generic drain routine that calls remove(); reusing imperative collection code on a blocking iterator.

Common situations: Ported List-walking code that paired next() with remove(); a dependency that calls remove() to drain; forgetting blocking iterators are views over a stream, not collections.

Related errors


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