elastic/elasticsearch · error · IllegalStateException
Configuration for {} can not be altered, already locked
Error message
Configuration for {} can not be altered, already locked What it means
Thrown by ElasticsearchCluster.checkFrozen() when any mutating API is invoked after the cluster is locked. checkFrozen() guards configuration setters (e.g. setNumberOfNodes, plugin additions) by reading configurationFrozen; once true, further changes are illegal.
Source
Thrown at build-tools/src/main/java/org/elasticsearch/gradle/testclusters/ElasticsearchCluster.java:436
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;
} else {
nodeNames = nodes.stream().map(ElasticsearchNode::getName).map(this::safeName).collect(Collectors.joining(","));
}
ElasticsearchNode firstNode = null;
for (ElasticsearchNode node : nodes) {View on GitHub (pinned to db6a809a66)
Solutions
- Move all configuration into the Gradle configuration phase (top-level testClusters {} / node {} blocks).
- Compute dynamic values with Provider/Property so they are resolved lazily but set before freeze.
- If you must branch on runtime state, use separate cluster definitions rather than mutating a frozen one.
Example fix
// before
tasks.named('integTest').configure {
doFirst { testClusters.cluster.setting('x', 'y') } // checkFrozen throws
}
// after
testClusters {
cluster { setting 'x', providers.gradleProperty('xValue').getOrElse('default') }
} Defensive patterns
Strategy: validation
Validate before calling
// Convention: assert frozen state before mutating
// if (cluster.isFrozen()) throw ...;
// In practice: keep all configuration in the testClusters {} DSL block. Prevention
- Reserve testClusters mutation for the configuration phase only.
- Use lazy providers for runtime-dependent values.
- Define separate cluster specs rather than reconfiguring a frozen one.
When it happens
Trigger: Calling a guarded mutator (setNumberOfNodes, plugin(...), module(...), setting(...), etc.) after freeze() has set configurationFrozen to true.
Common situations: Same shape as error 47: task-action-time configuration, late-binding overrides, or hooks running in the execution phase.
Related errors
- Cannot add nodes to test cluster after is has been frozen
- Number of nodes should be >= 1 but was {} for {}
- Cannot shrink {} to have {} nodes as it already has {}
- Can not start {}, missing: {}
- Ran out of nodes to take to the next version
AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12).
Data as JSON: /api/errors/4edbebaea0ffbef7.
Report an issue: GitHub.