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);
}
@OverrideView on GitHub (pinned to a8ab535614)
Solutions
- Delete the it.remove() call; these iterators are read-only and cannot mutate the source.
- If you must discard consumed elements, collect kept items into a new list instead of removing from the iterator.
- Wrap the iterator in a forwarding Iterator whose remove() is a no-op if a component requires the method to exist.
- 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
- Recognize blocking RxJava iterators as read-only views of a push source.
- Filter upstream (Flowable.filter) instead of removing downstream.
- Forward through a no-op remove() wrapper if a dependency calls remove().
- Keep consumed items in your own mutable list rather than mutating the iterator.
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.