ReactiveX/RxJava · error · UnsupportedOperationException

remove

Error message

remove

What it means

Thrown as UnsupportedOperationException('remove') by the Iterator returned by BlockingFlowableIterable when a caller invokes remove(). The iterator exposes a pull-based blocking view over a push-based flowable; mutation operations are not supported, so remove() always throws. This satisfies the java.util.Iterator contract (remove is optional).

Source

Thrown at src/main/java/io/reactivex/rxjava4/internal/operators/flowable/BlockingFlowableIterable.java:184

        void signalConsumer() {
            lock.lock();
            try {
                condition.signalAll();
            } finally {
                lock.unlock();
            }
        }

        @Override
        public void run() {
            SubscriptionHelper.cancel(this);
            signalConsumer();
        }

        @Override // otherwise default method which isn't available in Java 7
        public void remove() {
            throw new UnsupportedOperationException("remove");
        }

        @Override
        public void dispose() {
            SubscriptionHelper.cancel(this);
            signalConsumer(); // Just in case it is currently blocking in hasNext.
        }

        @Override
        public boolean isDisposed() {
            return get() == SubscriptionHelper.CANCELLED;
        }
    }
}

View on GitHub (pinned to a8ab535614)

Solutions

  1. Do not call remove() on iterators derived from a flowable; collect into a modifiable collection first if you need to mutate.
  2. If you need filtered elements, apply filter() on the flowable before iterating instead of removing during iteration.
  3. Collect into a new ArrayList and mutate that list: flowable.blockingSubscribe(list::add) or blockingStream().toList().
  4. Audit shared iteration utilities to ensure they do not invoke remove() on the supplied iterator.

Example fix

// before
Iterator<T> it = flowable.blockingIterable().iterator();
while (it.hasNext()) {
    T t = it.next();
    if (shouldDrop(t)) it.remove(); // throws
}

// after
List<T> kept = new ArrayList<>();
flowable.filter(t -> !shouldDrop(t)).blockingSubscribe(kept::add);
Defensive patterns

Strategy: type-guard

Validate before calling

// treat the iterator as read-only by contract; never call remove().
// If mutation is needed, collect first:
List<T> list = new ArrayList<>();
flowable.blockingSubscribe(list::add);
list.removeIf(this::shouldDrop);

Type guard

// iterators from BlockingFlowableIterable are unmodifiable; route mutation
// through a collected ArrayList instead of iterator.remove().
boolean isReadOnly(Iterator<?> it) {
    return it.getClass().getName().contains("BlockingFlowable");
}

Prevention

When it happens

Trigger: Obtaining an Iterator via blockingIterable()/forEach iteration over a flowable and calling iterator.remove() on it — e.g. inside a loop that was adapted from a mutable List pattern.

Common situations: Refactoring code that previously iterated and mutated a List (where remove() worked) to stream over a flowable; passing the iterator to a utility that calls remove() (some filtering/cleaning helpers do); generic collection-handling code that probes remove().

Related errors


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