gradle/gradle · error · UnsupportedOperationException

Cannot add '%s' to '%s' as it is a filtered collection

Error message

Cannot add '%s' to '%s' as it is a filtered collection

What it means

Resolution must happen while the owning project's state is exclusively locked. resolveGraphIfRequired checks hasMutableState() on the project model and throws IllegalResolutionException when resolution is attempted without that lock, because the graph could be mutated concurrently underneath the resolver.

Source

Thrown at platforms/core-configuration/domain-object-collections/src/main/java/org/gradle/api/internal/collections/FilteredIndexedElementSource.java:30

 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.gradle.api.internal.collections;

import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

// TODO make this work with pending elements
public class FilteredIndexedElementSource<T, S extends T> extends FilteredElementSource<T, S> implements IndexedElementSource<S> {
    public FilteredIndexedElementSource(ElementSource<T> collection, CollectionFilter<S> filter) {
        super(collection, filter);
    }

    @Override
    public void add(int index, S element) {
        throw new UnsupportedOperationException(String.format("Cannot add '%s' to '%s' as it is a filtered collection", element, this));
    }

    @Override
    public S get(int index) {
        int nextIndex = 0;
        for (T t : collection) {
            S s = filter.filter(t);
            if (s != null) {
                if (nextIndex == index) {
                    return s;
                }
                nextIndex++;
            }
        }
        throw new IndexOutOfBoundsException();
    }

    @Override

View on GitHub (pinned to 534f27719b)

Solutions

  1. Register the configuration as a task input (inputs.files(...)) so Gradle resolves it with proper locking and task dependencies
  2. Resolve only configurations of the project whose code is executing
  3. For cross-project data, consume artifacts/publications or task outputs instead of the other project's model

Example fix

// before
tasks.register('aggregate') {
    doLast {
        def files = project(':lib').configurations.runtimeClasspath.resolve() // IllegalResolutionException
    }
}

// after
tasks.register('aggregate') {
    def libCp = project(':lib').configurations.runtimeClasspath
    inputs.files(libCp) // Gradle resolves up-front, with the lock and task dependencies
    doLast { /* use the snapshot input files */ }
}
Defensive patterns

Strategy: validation

Validate before calling

void safeResolve(Project current, Configuration c) {
    if (!current.configurations.names.contains(c.name)) {
        throw new GradleException('Cross-project resolution is unsafe; register the configuration as a task input instead')
    }
    c.resolve()
}

Prevention

When it happens

Trigger: Resolving another project's configuration from a task or thread that does not own that project's lock: project(':lib').configurations.runtimeClasspath.resolve() inside a task of project A; resolving in background executors or worker threads; cross-project model queries during task execution.

Common situations: Custom tasks that reach into subproject configurations directly; migration to parallel execution or the configuration cache exposing previously hidden unsafe resolution; build logic resolving during doLast.

Related errors


AI-assisted analysis of gradle/gradle@534f27719b (2026-08-22). Data as JSON: /api/errors/6023bdee58975efd. Report an issue: GitHub.