elastic/elasticsearch · error · IllegalStateException

Cannot add nodes to test cluster after is has been frozen

Error message

Cannot add nodes to test cluster after is has been frozen

What it means

Thrown from a whenObjectAdded callback registered in ElasticsearchCluster.freeze(): once a test cluster is frozen (start of execution), any addition to its nodes container triggers this. freeze() is called by the testclusters infrastructure before booting nodes, so post-freeze mutation is a programming error.

Source

Thrown at build-tools/src/main/java/org/elasticsearch/gradle/testclusters/ElasticsearchCluster.java:431

    public void jvmArgs(String... values) {
        nodes.all(each -> each.jvmArgs(values));
    }

    @Internal
    public boolean isPreserveDataDir() {
        return nodes.stream().anyMatch(node -> node.isPreserveDataDir());
    }

    @Override
    public void setPreserveDataDir(boolean preserveDataDir) {
        nodes.all(each -> each.setPreserveDataDir(preserveDataDir));
    }

    @Override
    public void freeze() {
        nodes.forEach(ElasticsearchNode::freeze);
        configurationFrozen.set(true);
        nodes.whenObjectAdded(node -> { throw new IllegalStateException("Cannot add nodes to test cluster after is has been frozen"); });
    }

    private void checkFrozen() {
        if (configurationFrozen.get()) {
            throw new IllegalStateException("Configuration for " + this + " can not be altered, already locked");
        }
    }

    @Override
    public void start() {
        commonNodeConfig();
        nodes.forEach(ElasticsearchNode::start);
    }

    private void commonNodeConfig() {
        final String nodeNames;
        if (nodes.stream().map(ElasticsearchNode::getName).anyMatch(name -> name == null)) {
            nodeNames = null;

View on GitHub (pinned to db6a809a66)

Solutions

  1. Perform all node configuration in the configuration phase (testClusters { ... } blocks), not in task actions.
  2. If dynamic sizing is needed, compute it eagerly with providers so the value is fixed before freeze.
  3. Move the mutating call earlier — before the integTest/bwc test task that triggers freeze.

Example fix

// before
tasks.named('integTest').configure {
  doFirst { testClusters.cluster.numberOfNodes = 5 }  // freeze already ran
}

// after
testClusters {
  cluster { numberOfNodes = 5 }
}
Defensive patterns

Strategy: validation

Validate before calling

// Guard: only mutate in the configuration phase
if (cluster.getState().equals('EXECUTED') /* or check taskGraph */) {
    throw new IllegalStateException("Cluster frozen; cannot add nodes");
}
// Prefer: do all mutation in testClusters { } blocks, not in task actions.

Prevention

When it happens

Trigger: Code adds a node (e.g. nodes.create(...) or an internal setNumberOfNodes growth) after cluster.freeze() has executed. freeze() sets configurationFrozen and installs the callback that throws.

Common situations: A doFirst/doLast hook or a task action that mutates testClusters after Gradle has configured and frozen them; calling cluster APIs from a thread that races with freeze().

Related errors


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