elastic/elasticsearch · error · IllegalStateException

testcluster does not allow overwriting the following env var

Error message

testcluster does not allow overwriting the following env vars {} for {}

What it means

Thrown by getESEnvironment() (as IllegalStateException, not TestClustersException) when the user-set environment map shares keys with the build's reserved default environment. The node owns ES_JAVA_HOME, ES_PATH_CONF, ES_JAVA_OPTS, ES_TMPDIR, TMP, HOSTNAME, COMPUTERNAME, and (when set) PATH; overwriting them would break startup reproducibility, so the intersection is rejected wholesale.

Source

Thrown at build-tools/src/main/java/org/elasticsearch/gradle/testclusters/ElasticsearchNode.java:899

        defaultEnv.put("ES_TMPDIR", tmpDir.toString());
        // Windows requires this as it defaults to `c:\windows` despite ES_TMPDIR
        defaultEnv.put("TMP", tmpDir.toString());

        // Override the system hostname variables for testing
        defaultEnv.put("HOSTNAME", HOSTNAME_OVERRIDE);
        defaultEnv.put("COMPUTERNAME", COMPUTERNAME_OVERRIDE);

        // Propagate PATH so shell scripts (e.g. elasticsearch-keystore) can locate standard utilities
        // such as `dirname`. This is essential on systems like NixOS where PATH is non-standard.
        String systemPath = System.getenv("PATH");
        if (systemPath != null) {
            defaultEnv.put("PATH", systemPath);
        }

        Set<String> commonKeys = new HashSet<>(environment.keySet());
        commonKeys.retainAll(defaultEnv.keySet());
        if (commonKeys.isEmpty() == false) {
            throw new IllegalStateException("testcluster does not allow overwriting the following env vars " + commonKeys + " for " + this);
        }

        environment.forEach((key, value) -> defaultEnv.put(key, value.toString()));
        return defaultEnv;
    }

    private void startElasticsearchProcess() {
        final ProcessBuilder processBuilder = new ProcessBuilder();
        Path effectiveDistroDir = getDistroDir();
        List<String> command = OS.<List<String>>conditional()
            .onUnix(() -> List.of(effectiveDistroDir.resolve("./bin/elasticsearch").toString()))
            .onWindows(() -> Arrays.asList("cmd", "/c", effectiveDistroDir.resolve("bin\\elasticsearch.bat").toString()))
            .supply();
        processBuilder.command(command);
        processBuilder.directory(workingDir.toFile());
        Map<String, String> environment = processBuilder.environment();
        // Don't inherit anything from the environment for as that would lack reproducibility
        environment.clear();

View on GitHub (pinned to db6a809a66)

Solutions

  1. Remove the conflicting key(s) printed in the message from your environment(...) calls.
  2. For JVM tuning, use -Dtests.heap.size or node.jvmArgs rather than ES_JAVA_OPTS.
  3. For ES_PATH_CONF / ES_TMPDIR / TMP, rely on the build's own management; do not set them.
  4. If you need a custom PATH on a system where System.getenv('PATH') is null, set it via the OS or a Gradle init script rather than node.environment.

Example fix

// before: conflicts with reserved ES_JAVA_OPTS
environment('ES_JAVA_OPTS', '-Xmx4g')
// after: tune heap via the supported knob
systemProperty('tests.heap.size', '4g') // or pass -Dtests.heap.size=4g on the Gradle CLI
Defensive patterns

Strategy: validation

Validate before calling

static final Set<String> RESERVED_ENV = Set.of(
    "ES_JAVA_HOME", "ES_PATH_CONF", "ES_JAVA_OPTS", "ES_TMPDIR", "TMP", "HOSTNAME", "COMPUTERNAME", "PATH"
);
static void assertNoReservedEnv(Map<String,String> env) {
    Set<String> clash = new HashSet<>(env.keySet());
    clash.retainAll(RESERVED_ENV);
    if (!clash.isEmpty()) {
        throw new IllegalArgumentException("Refusing reserved env keys " + clash
            + "; use tests.heap.size / jvmArgs / OS config instead.");
    }
}
// Use: assertNoReservedEnv(myEnv); node.environment(myEnv);

Type guard

static boolean isReservedEnvKey(String k) {
    return RESERVED_ENV.contains(k);
}

Prevention

When it happens

Trigger: Calling node.environment('ES_JAVA_OPTS', '...') or any of the reserved keys. The check computes the intersection of user keys with defaultEnv keys (after defaults are populated) and throws if non-empty. Fires lazily at start()/exec time.

Common situations: Trying to tune JVM heap via ES_JAVA_OPTS (use tests.heap.size or jvmArgs instead). Setting TMP/ES_TMPDIR explicitly. Forwarding HOSTNAME/COMPUTERNAME from CI. Setting PATH on a system that already propagates one (the code copies the system PATH into defaults when non-null).

Related errors


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