elastic/elasticsearch · error · TestClustersException

Invalid system property `{}`. Use `requiresFeature` instead.

Error message

Invalid system property `{}`. Use `requiresFeature` instead.

What it means

Thrown by getESEnvironment() while building ES_JAVA_OPTS when a user-supplied system property key contains the substring 'feature_flag'. The build reserves feature-flag activation for the requiresFeature API (which versions flags by from/until), so passing a raw -D...feature_flag... system property is rejected to prevent flag drift across versions and to keep flags version-gated.

Source

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

        }
        // Older distributions ship with openjdk versions that are not compatible with newer kernels of ubuntu 24.04 and later
        // Therefore we pass explicitly the runtime java to use the adoptium jdk that is maintained longer and compatible
        // with newer kernels.
        // 8.10.4 is the last version shipped with jdk < 21. We configure these cluster to run with jdk 17 adoptium as 17 was
        // the last LTS release before 21
        else if (jdkIsIncompatibleWithOS(getVersion())) {
            defaultEnv.put(
                "ES_JAVA_HOME",
                jdk17FallbackLauncher.map(j -> j.getMetadata().getInstallationPath().getAsFile().getAbsolutePath()).get()
            );
        }
        defaultEnv.put("ES_PATH_CONF", configFile.getParent().toString());

        String systemPropertiesString = "";
        if (systemProperties.isEmpty() == false) {
            systemPropertiesString = " " + systemProperties.entrySet().stream().peek(entry -> {
                if (entry.getKey().contains("feature_flag")) {
                    throw new TestClustersException("Invalid system property `" + entry.getKey() + "`. Use `requiresFeature` instead.");
                }
            })
                .map(entry -> "-D" + entry.getKey() + "=" + entry.getValue())
                // ES_PATH_CONF is also set as an environment variable and for a reference to ${ES_PATH_CONF}
                // to work ES_JAVA_OPTS, we need to make sure that ES_PATH_CONF before ES_JAVA_OPTS. Instead,
                // we replace the reference with the actual value in other environment variables
                .map(p -> p.replace("${ES_PATH_CONF}", configFile.getParent().toString()))
                .collect(Collectors.joining(" "));
        }
        if (systemProperties.containsKey("io.netty.leakDetection.level") == false) {
            systemPropertiesString = systemPropertiesString + " -Dio.netty.leakDetection.level=paranoid";
        }

        String featureFlagsString = "";
        if (featureFlags.isEmpty() == false && isReleasedVersion.apply(getVersion())) {
            featureFlagsString = featureFlags.stream()
                .filter(f -> getVersion().onOrAfter(f.getFrom()) && (f.getUntil() == null || getVersion().before(f.getUntil())))
                .map(f -> "-D" + f.getFeature() + "=true")

View on GitHub (pinned to db6a809a66)

Solutions

  1. Replace the system property with node.requiresFeature('es.feature_flag.foo', Version.fromString(...)).
  2. Use requiresFeature(feature, from) or requiresFeature(feature, from, until) to version-gate the flag.
  3. If the property genuinely is not a feature flag, rename the key so it does not contain 'feature_flag'.
  4. Audit existing systemProperty calls for the 'feature_flag' substring and migrate them.

Example fix

// before: forbidden raw system property
systemProperty('es.feature_flag.foo', 'true')
// after: version-gated feature flag
requiresFeature('es.feature_flag.foo', Version.fromString('8.15.0'))
Defensive patterns

Strategy: validation

Validate before calling

static String assertNotFeatureFlagSystemProperty(String key) {
    if (key.contains("feature_flag")) {
        throw new IllegalArgumentException("Use requiresFeature for feature-flag key: " + key);
    }
    return key;
}
// Use: node.systemProperty(assertNotFeatureFlagSystemProperty(key), value);

Type guard

static boolean isFeatureFlagKey(String key) {
    return key != null && key.contains("feature_flag");
}

Prevention

When it happens

Trigger: Calling node.systemProperty('feature.flag.xyz', 'true') or any key containing 'feature_flag' (note: the check is case-sensitive on the literal 'feature_flag', and looks for that substring, not 'feature.'). The throw fires lazily when getESEnvironment() runs during start() or bin-script exec.

Common situations: Copy-pasting a -Des.feature_flag.foo=true from production docs into a test cluster. Migrating an old test that pre-dates requiresFeature. Misunderstanding that feature flags must be version-scoped through requiresFeature.

Related errors


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