elastic/elasticsearch · critical · IllegalStateException

ExtractForeignApiTask worker must run on JDK 21 (found JDK $

Error message

ExtractForeignApiTask worker must run on JDK 21 (found JDK ${jdkVersion}). The Foreign Function & Memory API is preview in JDK 21 only; on JDK 22+ it is standard and this stub JAR is unnecessary.

What it means

Thrown as IllegalStateException by checkRuntimeJava21() at the start of ExtractionWorkAction.execute() when Runtime.version().feature() != 21. The task exists solely because the Foreign Function & Memory API is @PreviewFeature in JDK 21; on JDK 22+ it is standard and the stub JAR is pointless. The guard prevents producing a stub from the wrong class-file version or failing silently on a runtime that lacks the preview API.

Source

Thrown at build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/ExtractForeignApiTask.java:196

                    String entryName = FOREIGN_PACKAGE_PREFIX + file.getFileName().toString();
                    JarEntry entry = new JarEntry(entryName);
                    entry.setTime(0);
                    jar.putNextEntry(entry);
                    jar.write(stubBytes);
                    jar.closeEntry();
                    count++;
                }
            } catch (IOException e) {
                throw new UncheckedIOException("Failed to extract foreign API stubs", e);
            }

            LOGGER.info("Generated {} with {} class(es)", outputPath, count);
        }

        private static void checkRuntimeJava21() {
            int jdkVersion = Runtime.version().feature();
            if (jdkVersion != 21) {
                throw new IllegalStateException(
                    "ExtractForeignApiTask worker must run on JDK 21 (found JDK "
                        + jdkVersion
                        + "). "
                        + "The Foreign Function & Memory API is preview in JDK 21 only; on JDK 22+ it is standard and this stub JAR is unnecessary."
                );
            }
        }

        static byte[] createStub(InputStream classStream) throws IOException {
            ClassReader reader = new ClassReader(classStream);
            ClassNode cn = new ClassNode();
            reader.accept(cn, 0);
            checkJava21(cn);

            if ((cn.access & Opcodes.ACC_PUBLIC) == 0) {
                return null;
            }

View on GitHub (pinned to db6a809a66)

Solutions

  1. Run the Gradle daemon on JDK 21 (JAVA_HOME points to JDK 21) so the in-process noIsolation path is correct.
  2. Ensure a JDK 21 toolchain is auto-detected by Gradle so ForeignApiPlugin populates getJdk21Launcher() and forks the worker with it.
  3. If the project has moved past JDK 21 entirely, remove/skip the ExtractForeignApiTask since the stub JAR is unnecessary on JDK 22+.
  4. Check ~/.gradle/gradle.properties and org.gradle.java.installations.* paths include a JDK 21 install.

Example fix

// before: daemon on JDK 22, no launcher supplied → throws
workQueue = workerExecutor.noIsolation();

// after: force a JDK 21 toolchain launcher so the worker forks on 21
javaToolchainService.launcherFor { it.languageVersion.set(JavaLanguageVersion.of(21)) }
  .get().let { task.getJdk21Launcher().set(it) }
Defensive patterns

Strategy: validation

Validate before calling

int feature = Runtime.version().feature();
if (feature != 21) {
    System.err.println("WARNING: ExtractForeignApiTask requires JDK 21, found JDK " + feature);
    // supply a JDK 21 toolchain launcher or skip the task
}

Prevention

When it happens

Trigger: The worker action calls checkRuntimeJava21() unconditionally (line 150). If getJdk21Launcher() was absent (daemon assumed to be JDK 21) but the daemon is actually JDK 22+, or if the forked process launcher points at a non-21 JDK, this throws. Also hit in unit tests that construct the action without a real JDK 21 runtime.

Common situations: Developer upgraded their default JAVA_HOME to JDK 22+ but ForeignApiPlugin's toolchain detection didn't supply a JDK 21 launcher; CI image rolled forward to a newer JDK; running the worker in-process via noIsolation() on a daemon started with JDK 17 or 25.

Related errors


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