elastic/elasticsearch · critical · RuntimeException
starting java failed with [%d] output: %s error: %s
Error message
starting java failed with [%d] output: %s error: %s
What it means
Thrown by JvmOption's helper that spawns a short-lived Java subprocess (to resolve final JVM flags) when the process exits with a non-zero status. The message embeds the exit code, stdout, and stderr of the failed Java invocation so the underlying JVM error is visible.
Source
Thrown at distribution/tools/server-cli/src/main/java/org/elasticsearch/server/cli/JvmOption.java:125
Stream.of(java),
userDefinedJvmOptions.stream(),
Stream.of("-XX:+PrintFlagsFinal", "-version")
).flatMap(Function.identity()).toList();
final ProcessBuilder builder = new ProcessBuilder().command(command);
setWorkingDir(builder);
final Process process = builder.start();
final List<String> output = readLinesFromInputStream(process.getInputStream());
final List<String> error = readLinesFromInputStream(process.getErrorStream());
final int status = process.waitFor();
if (status != 0) {
final String message = String.format(
Locale.ROOT,
"starting java failed with [%d]\noutput:\n%s\nerror:\n%s",
status,
String.join("\n", output),
String.join("\n", error)
);
throw new RuntimeException(message);
} else {
return output;
}
}
@SuppressForbidden(reason = "ProcessBuilder takes File")
private static void setWorkingDir(ProcessBuilder builder) throws IOException {
// The real ES process uses the logs dir as the working directory. Since we don't
// have the logs dir yet, here we use a temp directory for calculating jvm options.
final Path tmpDir = Files.createTempDirectory("final-flags");
builder.directory(tmpDir.toFile());
}
private static List<String> readLinesFromInputStream(final InputStream is) throws IOException {
try (InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8); BufferedReader br = new BufferedReader(isr)) {
return br.lines().toList();
}
}View on GitHub (pinned to db6a809a66)
Solutions
- Read the embedded 'output:' and 'error:' sections — they contain the actual JVM error (e.g. 'Unrecognized option').
- Verify the bundled JDK: $ES_HOME/jdk/bin/java -version runs cleanly.
- If using a custom JAVA_HOME, point it at a compatible JDK (see ES supported JDK matrix).
- Remove or fix the offending JVM flag identified in the error output.
Defensive patterns
Strategy: try-catch
Validate before calling
Path javaBin = Path.of(installDir, "jdk", "bin", "java");
if (Files.notExists(javaBin) || !Files.isExecutable(javaBin)) {
throw new IllegalStateException("Bundled JDK not executable: " + javaBin);
} Try / catch
try {
List<String> out = JvmOption.runJavaForFlags(builder);
} catch (RuntimeException e) {
// message already contains exit code + stdout + stderr; surface to operator.
log.error("Subprocess java failed: {}", e.getMessage());
throw e;
} Prevention
- Verify the bundled JDK runs: $ES_HOME/jdk/bin/java -version.
- If overriding JAVA_HOME, use a JDK from the ES supported matrix.
- Validate jvm.options flags against the bundled JDK version before deploy.
When it happens
Trigger: ES launches java -version (or an equivalent flag-resolving command) as a subprocess and it exits non-zero — typically because the bundled JDK is broken, a JVM flag is invalid, or the java binary itself fails to start.
Common situations: JAVA_HOME points to a corrupt/incompatible JDK; a jvm.options line passes a flag the bundled Java doesn't understand; the java binary is missing or not executable; container image has a broken JDK.
Related errors
- Invalid jvm argument `{}` configure as systemProperty instea
- 78
- Negative memory size specified in [{option}]
- Unable to parse number of bytes from [${totalMemoryBytesOpti
- CONFIG
AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12).
Data as JSON: /api/errors/4750cdff6ef04d45.
Report an issue: GitHub.