elastic/elasticsearch · error · UnsupportedOperationException

${className} does not support retainAll()

Error message

${className} does not support retainAll()

What it means

LazyPropertyList overrides List.retainAll(Collection) to throw UnsupportedOperationException. retainAll is the complement of removeAll and would force eager evaluation of every lazy supplier to compute the intersection, defeating the deferred-evaluation design. clear() is supported as a full reset.

Source

Thrown at build-tools/src/main/java/org/elasticsearch/gradle/LazyPropertyList.java:108

    }

    @Override
    public boolean addAll(int index, Collection<? extends T> c) {
        int i = index;
        for (T item : c) {
            this.add(i++, item);
        }
        return true;
    }

    @Override
    public boolean removeAll(Collection<?> c) {
        throw new UnsupportedOperationException(this.getClass().getName() + " does not support removeAll()");
    }

    @Override
    public boolean retainAll(Collection<?> c) {
        throw new UnsupportedOperationException(this.getClass().getName() + " does not support retainAll()");
    }

    @Override
    public void clear() {
        delegate.clear();
    }

    @Override
    public T get(int index) {
        PropertyListEntry<T> entry = delegate.get(index);
        validate(entry);
        return entry.getValue();
    }

    @Override
    public T set(int index, T element) {
        return delegate.set(index, new PropertyListEntry<>(() -> element, PropertyNormalization.DEFAULT)).getValue();
    }

View on GitHub (pinned to db6a809a66)

Solutions

  1. Compute the retained subset into a new list, clear(), then re-add: lazyList.clear(); kept.forEach(lazyList::add).
  2. Move the retainAll logic upstream so the LazyPropertyList only ever receives its final contents.
  3. If intersection semantics are essential, maintain a plain List<T> alongside and sync on build.

Example fix

// before
lazyList.retainAll(allowed); // UnsupportedOperationException
// after
List<T> kept = lazyList.stream().filter(allowed::contains).toList();
lazyList.clear();
kept.forEach(lazyList::add);
Defensive patterns

Strategy: validation

Validate before calling

if (list instanceof LazyPropertyList) {
    throw new IllegalStateException("retainAll() not supported on LazyPropertyList");
}

Type guard

static boolean isLazyPropertyList(List<?> l) { return l instanceof LazyPropertyList; }

Try / catch

try { list.retainAll(c); } catch (UnsupportedOperationException e) { /* rebuild: intersect and clear+addAll */ }

Prevention

When it happens

Trigger: Calling .retainAll(Collection) on a LazyPropertyList. Surfaces when intersecting lists, deduplicating against an allow-list, or using collection frameworks that invoke retainAll for filtering.

Common situations: Migrating intersection/dedup logic from ArrayList to LazyPropertyList; Gradle task code that narrows a configured list to a subset at configuration time.

Related errors


AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12). Data as JSON: /api/errors/efcabef589dbb627. Report an issue: GitHub.