{"record":{"id":"98fcc75217897dc2","repo":"elastic/elasticsearch","slug":"extractforeignapitask-worker-must-run-on-jdk-21-f","errorCode":null,"errorMessage":"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.","messagePattern":"ExtractForeignApiTask worker must run on JDK 21 \\(found JDK (.+?)\\)\\. The Foreign Function & Memory API is preview in JDK 21 only; on JDK 22\\+ it is standard and this stub JAR is unnecessary\\.","errorType":"validation","errorClass":"IllegalStateException","httpStatus":null,"severity":"critical","filePath":"build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/ExtractForeignApiTask.java","lineNumber":196,"sourceCode":"                    String entryName = FOREIGN_PACKAGE_PREFIX + file.getFileName().toString();\n                    JarEntry entry = new JarEntry(entryName);\n                    entry.setTime(0);\n                    jar.putNextEntry(entry);\n                    jar.write(stubBytes);\n                    jar.closeEntry();\n                    count++;\n                }\n            } catch (IOException e) {\n                throw new UncheckedIOException(\"Failed to extract foreign API stubs\", e);\n            }\n\n            LOGGER.info(\"Generated {} with {} class(es)\", outputPath, count);\n        }\n\n        private static void checkRuntimeJava21() {\n            int jdkVersion = Runtime.version().feature();\n            if (jdkVersion != 21) {\n                throw new IllegalStateException(\n                    \"ExtractForeignApiTask worker must run on JDK 21 (found JDK \"\n                        + jdkVersion\n                        + \"). \"\n                        + \"The Foreign Function & Memory API is preview in JDK 21 only; on JDK 22+ it is standard and this stub JAR is unnecessary.\"\n                );\n            }\n        }\n\n        static byte[] createStub(InputStream classStream) throws IOException {\n            ClassReader reader = new ClassReader(classStream);\n            ClassNode cn = new ClassNode();\n            reader.accept(cn, 0);\n            checkJava21(cn);\n\n            if ((cn.access & Opcodes.ACC_PUBLIC) == 0) {\n                return null;\n            }\n","sourceCodeStart":178,"sourceCodeEnd":214,"githubUrl":"https://github.com/elastic/elasticsearch/blob/db6a809a667c081ca1dc7500389d26975573215f/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/ExtractForeignApiTask.java#L178-L214","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Run the Gradle daemon on JDK 21 (JAVA_HOME points to JDK 21) so the in-process noIsolation path is correct.","Ensure a JDK 21 toolchain is auto-detected by Gradle so ForeignApiPlugin populates getJdk21Launcher() and forks the worker with it.","If the project has moved past JDK 21 entirely, remove/skip the ExtractForeignApiTask since the stub JAR is unnecessary on JDK 22+.","Check ~/.gradle/gradle.properties and org.gradle.java.installations.* paths include a JDK 21 install."],"exampleFix":"// before: daemon on JDK 22, no launcher supplied → throws\nworkQueue = workerExecutor.noIsolation();\n\n// after: force a JDK 21 toolchain launcher so the worker forks on 21\njavaToolchainService.launcherFor { it.languageVersion.set(JavaLanguageVersion.of(21)) }\n  .get().let { task.getJdk21Launcher().set(it) }","handlingStrategy":"validation","validationCode":"int feature = Runtime.version().feature();\nif (feature != 21) {\n    System.err.println(\"WARNING: ExtractForeignApiTask requires JDK 21, found JDK \" + feature);\n    // supply a JDK 21 toolchain launcher or skip the task\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Pin the Gradle daemon JAVA_HOME to JDK 21, or configure a JDK 21 toolchain so ForeignApiPlugin forks the worker.","Add org.gradle.java.installations.paths pointing to a JDK 21 install in gradle.properties.","On JDK 22+, remove or disable the ExtractForeignApiTask since the stub is unnecessary.","Document the JDK 21 requirement in CI setup scripts."],"tags":["build-tooling","foreign-api","jdk21","toolchain","gradle"],"backgroundTag":null,"analyzedSha":"db6a809a667c081ca1dc7500389d26975573215f","analyzedAt":"2026-08-12T01:39:14.192Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}