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

  1. Read the embedded 'output:' and 'error:' sections — they contain the actual JVM error (e.g. 'Unrecognized option').
  2. Verify the bundled JDK: $ES_HOME/jdk/bin/java -version runs cleanly.
  3. If using a custom JAVA_HOME, point it at a compatible JDK (see ES supported JDK matrix).
  4. 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

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


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