{"record":{"id":"594147d967a6d080","repo":"elastic/elasticsearch","slug":"failed-to-extract-foreign-api-stubs","errorCode":null,"errorMessage":"Failed to extract foreign API stubs","messagePattern":"Failed to extract foreign API stubs","errorType":"exception","errorClass":"UncheckedIOException","httpStatus":null,"severity":"error","filePath":"build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/ExtractForeignApiTask.java","lineNumber":187,"sourceCode":"                        continue;\n                    }\n                    byte[] stubBytes;\n                    try (InputStream is = Files.newInputStream(file)) {\n                        stubBytes = createStub(is);\n                    }\n                    if (stubBytes == null) {\n                        continue;\n                    }\n                    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 {","sourceCodeStart":169,"sourceCodeEnd":205,"githubUrl":"https://github.com/elastic/elasticsearch/blob/db6a809a667c081ca1dc7500389d26975573215f/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/ExtractForeignApiTask.java#L169-L205","documentation":"Thrown as an UncheckedIOException inside ExtractForeignApiTask.ExtractionWorkAction.execute() when an IOException occurs while walking the JDK's jrt:/ filesystem (java.lang.foreign classes) or writing the stub JAR via JarOutputStream. The worker reads class files from jrt:/modules/java.base/java/lang/foreign, strips @PreviewFeature annotations, and writes a patched JAR for --patch-module use. Any I/O failure (disk full, broken jrt fs, output dir locked) is wrapped into this message.","triggerScenarios":"The try-with-resources block at line 163-188 opens a JarOutputStream on outputPath and walks foreignRoot via Files.walk(). An IOException from Files.walk, Files.newOutputStream, jar.putNextEntry, or jar.write propagates to the catch at line 186 and is rethrown wrapped. Common triggers: output directory not creatable, disk full, jrt:/ filesystem unavailable (non-OpenJDK JVM), concurrent write to the same outputPath.","commonSituations":"Running the extractForeignApi task under a JRE that lacks the jrt:/ filesystem (some bundled JREs), build output directory on a network filesystem with locking issues, disk-full CI agents, antivirus locking the output JAR on Windows, or a misconfigured getOutputJar() pointing to a read-only location.","solutions":["Verify the output path (getOutputJar()) is writable and its parent directory can be created; check disk space on the build volume.","Confirm the worker JVM is a full OpenJDK 21 (not a JRE/stripped runtime) so jrt:/ is available — check getJdk21Launcher() resolves to a real JDK 21.","Re-run with --info --stacktrace to capture the wrapped IOException's cause, which names the failing path or operation.","If running on Windows with antivirus, exclude the build directory from scanning, or move the build output to a local non-synced path."],"exampleFix":"// before: output jar resolved to a read-only or network path\ngetOutputJar().set(project.getLayout().getBuildDirectory().file(\"libs/native/build/jdk21-foreign-api.jar\"));\n\n// after: ensure parent is a local writable dir, force-create before submit\nFile out = getOutputJar().get().getAsFile();\nFiles.createDirectories(out.getParentFile().toPath());","handlingStrategy":"try-catch","validationCode":"Path out = task.getOutputJar().get().getAsFile().toPath();\nif (Files.isWritable(out.getParent()) == false) {\n    throw new IllegalStateException(\"Output dir not writable: \" + out.getParent());\n}\nif (FileSystems.getFileSystem(URI.create(\"jrt:/\")) == null) {\n    throw new IllegalStateException(\"jrt:/ filesystem unavailable; not a full JDK\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    task.extract();\n} catch (UncheckedIOException e) {\n    // inspect e.getCause() for the underlying IOException\n    throw new GradleException(\"Foreign API stub extraction failed: \" + e.getCause().getMessage(), e.getCause());\n}","preventionTips":["Ensure getOutputJar() resolves to a local writable path with a creatable parent directory.","Run the worker on a full OpenJDK 21 (not a JRE) so jrt:/ is available.","Exclude the build directory from antivirus/backup tools on Windows.","Verify disk space before running the task on CI agents."],"tags":["build-tooling","foreign-api","io","jdk21","gradle"],"analyzedSha":"db6a809a667c081ca1dc7500389d26975573215f","analyzedAt":"2026-08-12T01:39:14.192Z","schemaVersion":2},"datasetVersion":"2026-08-12T12:17:08.281Z"}