elastic/elasticsearch · error · IllegalStateException
{} can only be applied to the root project.
Error message
{} can only be applied to the root project. What it means
TestClustersHookPlugin.apply() refuses to be applied to any project other than the root. This plugin registers build-wide services (TestClustersRegistry provider, TaskEventsService) via `getEventsListenerRegistry()` and the gradle shared-services API, which only make sense once at the build root. Applying it to a subproject would create duplicate/conflicting build services.
Source
Thrown at build-tools/src/main/java/org/elasticsearch/gradle/testclusters/TestClustersPlugin.java:220
task.setGroup("ES cluster formation");
task.setDescription("Lists all ES clusters configured for this project");
task.doLast(
(Task t) -> container.forEach(cluster -> logger.lifecycle(" * {}: {}", cluster.getName(), cluster.getNumberOfNodes()))
);
});
}
static abstract class TestClustersHookPlugin implements Plugin<Project> {
@Inject
public abstract BuildEventsListenerRegistry getEventsListenerRegistry();
@SuppressWarnings("checkstyle:RedundantModifier")
@Inject
public TestClustersHookPlugin() {}
public void apply(Project project) {
if (project != project.getRootProject()) {
throw new IllegalStateException(this.getClass().getName() + " can only be applied to the root project.");
}
Provider<TestClustersRegistry> registryProvider = GradleUtils.getBuildService(
project.getGradle().getSharedServices(),
REGISTRY_SERVICE_NAME
);
Provider<TaskEventsService> testClusterTasksService = project.getGradle()
.getSharedServices()
.registerIfAbsent(TEST_CLUSTER_TASKS_SERVICE, TaskEventsService.class, spec -> {
spec.getParameters().getRegistry().set(registryProvider);
});
TestClustersRegistry registry = registryProvider.get();
// When we know what tasks will run, we claim the clusters of those task to differentiate between clusters
// that are defined in the build script and the ones that will actually be used in this invocation of gradle
// we use this information to determine when the last task that required the cluster executed so that we can
// terminate the cluster right away and free up resources.
configureClaimClustersHook(project.getGradle(), registry);View on GitHub (pinned to db6a809a66)
Solutions
- Apply the hook plugin ONLY in the root project's build.gradle (or via a plugin that itself checks `project == rootProject`).
- If writing a convention, guard with `if (project == project.rootProject) { apply(plugin) }`.
- Confirm the plugin isn't transitively pulled by another plugin applied to subprojects — check `./gradlew <sub>:dependencies` or plugin application logging.
Example fix
// before (subproject build.gradle)
apply plugin: 'elasticsearch.testclusters'
// after: move to root build.gradle, or guard
if (project == rootProject) {
apply plugin: 'elasticsearch.testclusters'
} Defensive patterns
Strategy: validation
Validate before calling
if (project != project.getRootProject()) {
throw new IllegalStateException(
"Apply " + pluginId + " only in the root project, not " + project.getPath());
} Prevention
- Apply the testclusters hook plugin only in the root build.gradle.
- In convention plugins, guard application with `project == rootProject`.
- Audit `subprojects { apply ... }` blocks for plugins that are root-only.
When it happens
Trigger: Explicitly `apply plugin: 'elasticsearch.testclusters'` (or the hook variant) in a subproject's build.gradle, or a convention plugin that applies it unconditionally to every project including non-root ones.
Common situations: A convention plugin authored without a root-project guard; applying the plugin inside a `subprojects { ... }` block; manually wiring it during plugin migration.
Related errors
- Task {} can't use test cluster from another project {}
- Task {} is not configured to use any clusters. Be sure to ca
- Elasticsearch cluster died
- Can not use {} with {}
- Cannot specify more than one trust method (CA=%s, trustStore
AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12).
Data as JSON: /api/errors/572da79c04b41d11.
Report an issue: GitHub.