elastic/elasticsearch · error · TestClustersException
Invalid jvm argument `{}` configure as systemProperty instea
Error message
Invalid jvm argument `{}` configure as systemProperty instead for {} What it means
Thrown by getESEnvironment() while building the jvmArgs portion of ES_JAVA_OPTS when a user-supplied JVM argument starts with '-D'. The build reserves -D system properties for the dedicated systemProperty API (which applies the feature_flag guard, ES_PATH_CONF substitution, and netty leak detection append); mixing them into jvmArgs bypasses those safeguards and is rejected.
Source
Thrown at build-tools/src/main/java/org/elasticsearch/gradle/testclusters/ElasticsearchNode.java:857
.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")
.collect(Collectors.joining(" "));
}
String jvmArgsString = "";
if (jvmArgs.isEmpty() == false) {
jvmArgsString = " " + jvmArgs.stream().peek(argument -> {
if (argument.toString().startsWith("-D")) {
throw new TestClustersException(
"Invalid jvm argument `" + argument + "` configure as systemProperty instead for " + this
);
}
}).collect(Collectors.joining(" "));
}
String heapSize = System.getProperty("tests.heap.size", "512m");
defaultEnv.put(
"ES_JAVA_OPTS",
"-Xms"
+ heapSize
+ " -Xmx"
+ heapSize
+ " -ea -esa "
+ systemPropertiesString
+ " "
+ featureFlagsString
+ " "
+ jvmArgsStringView on GitHub (pinned to db6a809a66)
Solutions
- Move any -D... argument to node.systemProperty(key, value) (stripping the -D prefix).
- Keep in jvmArgs only non-property flags: -Xmx, -Xms, -XX:..., -ea, etc.
- If you need ES_PATH_CONF interpolation, note that systemProperty supports the ${ES_PATH_CONF} placeholder; jvmArgs does not.
- Audit existing jvmArgs calls and split them by prefix.
Example fix
// before: -D passed as jvm arg
jvmArgs('-Des.foo=bar', '-Xmx2g')
// after: split into systemProperty + jvm arg
systemProperty('es.foo', 'bar')
jvmArgs('-Xmx2g') Defensive patterns
Strategy: validation
Validate before calling
static List<CharSequence> splitJvmAndSystemProperties(List<CharSequence> raw) {
List<CharSequence> jvm = new ArrayList<>();
for (CharSequence a : raw) {
String s = a.toString();
if (s.startsWith("-D") && s.contains("=")) {
// route to systemProperty instead; caller should handle
throw new IllegalArgumentException("Use systemProperty for -D arg: " + s);
}
jvm.add(a);
}
return jvm;
}
// Use: node.jvmArgs(splitJvmAndSystemProperties(args).toArray(new CharSequence[0])); Type guard
static boolean isPureJvmArg(CharSequence a) {
return a != null && !a.toString().startsWith("-D");
} Prevention
- Pass -D... via node.systemProperty(key, value).
- Keep jvmArgs to -X, -XX, and non-property flags.
- Split existing jvmArgs lists by the '-D' prefix when migrating.
When it happens
Trigger: Calling node.jvmArgs('-Des.foo=bar') or any argument whose string form starts with '-D'. The check is a prefix match, so even '-Dfoo' (no '=') is rejected. Fires lazily when getESEnvironment() runs.
Common situations: Copy-pasting JAVA_OPTS from production into the test cluster. Migrating from a generic Gradle exec to the testcluster API. Assuming jvmArgs and systemProperty are interchangeable.
Related errors
- Can't create extra config file from {} for {} as it does not
- extra jar file {} doesn't appear to be a JAR
- Not a valid module {} for {}
- extra config file destination can't be relative, was {} for
- Unknown keys in user definition {} for {}
AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12).
Data as JSON: /api/errors/a007d37a89afdac0.
Report an issue: GitHub.