elastic/elasticsearch · error · IllegalStateException

testcluster does not allow overwriting the following env…

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.

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.

Appendix: 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)