ReactiveX/RxJava · warning · UnsupportedOperationException

Read only iterator

Error message

Read only iterator

What it means

Thrown by Iterator.remove() from Flowable.blockingNext() when removal is attempted. The blockingNext() iterator yields Notifications one at a time from a push-based source, so it has no mutable backing store and remove() is permanently unsupported. The message 'Read only iterator' documents this intentional contract.

Source

Thrown at src/main/java/io/reactivex/rxjava4/internal/operators/flowable/BlockingFlowableNext.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 NextSubscriber<T> extends DisposableSubscriber<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; these iterators are read-only and cannot mutate the source.
  2. If you must discard consumed elements, collect kept items into a new list instead of removing from the iterator.
  3. Wrap the iterator in a forwarding Iterator whose remove() is a no-op if a component requires the method to exist.
  4. Filter upstream with Flowable.filter(...) before blockingNext() to shape the data rather than removing downstream.

Example fix

// before
Iterator<T> it = flowable.blockingNext().iterator();
while (it.hasNext()) { T v = it.next(); if (shouldDrop(v)) it.remove(); }
// after
List<T> kept = new ArrayList<>();
Iterator<T> it = flowable.blockingNext().iterator();
while (it.hasNext()) { T v = it.next(); if (!shouldDrop(v)) kept.add(v); }
Defensive patterns

Strategy: validation

Validate before calling

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

Prevention

When it happens

Trigger: Calling it.remove() on the iterator from Flowable.blockingNext().iterator(); feeding that iterator into a generic processor that invokes remove() after consumption; manual loop template that includes remove().

Common situations: Reusing imperative collection-walking code that paired next() with remove(); integration with a third-party library that calls remove() to drain iterators; forgetting that blocking RxJava iterators are views, not collections.

Related errors


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