elide jextract reads C headers and generates Java source files using Java’s Foreign Function and Memory
(FFM) API. It embeds OpenJDK jextract and shares Elide’s Clang/LLVM native library with
the embedded C compiler and linker, so generation needs no separate jextract, JDK or
libclang installation.
Distribution support
Native tools are part of Elide 1.6.2. They are included on macOS arm64 and dynamically
linked Linux amd64/arm64, and omitted on Windows, fully static Elide builds and custom builds using
--no-jvm-toolchain.
Use this command when you want generated .java files to inspect, distribute or compile separately.
For an Elide Java project, C header bindings in the Java compiler
can generate and compile bindings in memory from Pkl declarations or ad hoc elide javac options.
Generate Java source
Save a small library interface as native/answer.h:
int answer(void);Generate its Java bindings:
elide jextract -- --output generated \
--target-package example.ffi \
--header-class-name NativeApi \
native/answer.hThe output includes generated/example/ffi/NativeApi.java and any supporting sources needed by the
bindings. The header’s answer declaration becomes a Java accessor such as NativeApi.answer().
Generation produces source code, not a native library, class files or JNI wrappers. It does not load
the application library, so its implementation does not need to be present at this step.
Without --output, sources go in the current directory. Without --target-package, they use the unnamed
package. Without --header-class-name, jextract derives a class name from the header filename, such as
answer_h for answer.h. Supply an explicit header class name when passing multiple headers.
Configure parsing and select declarations
Pass jextract’s arguments after --; its own help lists the full interface:
elide jextract -- --help| Option | Purpose |
|---|---|
--output PATH | Directory for generated Java sources. |
-t PACKAGE, --target-package PACKAGE | Package for generated classes. |
--header-class-name NAME | Generated header class name. |
-I DIR, --include-dir DIR | Add a C header search directory; repeat for multiple directories. |
-D NAME=VALUE, --define-macro NAME= | Define a preprocessor macro; repeat as needed. |
--include-function NAME | Select a function for generation. |
--include-constant NAME | Select a macro or enum constant. |
--include-struct NAME, --include-union NAME | Select a struct or union. |
--include-typedef NAME, --include-var NAME | Select a typedef or global variable. |
-l LIBSPEC, --library LIBSPEC | Add a shared library to the generated runtime lookup. |
--use-system-load-library | Use System.loadLibrary or System.load for those libraries. |
For example, add a library’s include directory, set a macro and select one function:
elide jextract -- --output generated -t example.ffi \
--header-class-name NativeApi \
-I native/include -D ANSWER_API_VERSION=1 \
--include-function answer native/answer.hThe standalone command also reads Clang arguments from compile_flags.txt in the current working directory,
one argument per line. This differs from the integrated Java compilation path, which uses its declared
binding configuration. Elide passes jextract’s diagnostics and exit status through; a failed extraction
does not yield usable bindings.
Call a shared library
Suppose native/libanswer.so on Linux, or native/libanswer.dylib on macOS, exports the implementation:
int answer(void) {
return 42;
}The library must be built for the machine running the Java application. Obtain it from the library’s build
or package, or compile native/answer.c with a separate full C compiler:
# Linux
clang -shared -fPIC native/answer.c -o native/libanswer.so
# macOS
clang -dynamiclib native/answer.c -o native/libanswer.dylibThese are external compiler commands. elide cc exposes object/IR output and in-memory JIT linking;
its current CLI does not expose shared-library output.
Regenerate the bindings with an explicit library path. On Linux:
elide jextract -- --output generated -t example.ffi \
--header-class-name NativeApi \
-l :./native/libanswer.so native/answer.hOn macOS, use -l :./native/libanswer.dylib instead. The leading : marks a path rather than a logical
library name. A relative library path is resolved when the generated code runs, so run this example from
the directory containing native/.
Save this caller as Main.java:
import example.ffi.NativeApi;
public class Main {
public static void main(String[] args) {
System.out.println(NativeApi.answer());
}
}Compile the generated sources and caller with Elide’s bundled Java 25 compiler, then enable native access when running them:
elide javac -- -d out generated/example/ffi/*.java Main.java
elide java -- --enable-native-access=ALL-UNNAMED -cp out Main42ALL-UNNAMED applies to this classpath example. For a named module, enable native access for the module
containing the generated bindings. Native access is needed by the generated FFM code at runtime;
successful generation and Java compilation do not establish that the shared library can be loaded.
Library discovery and memory ownership
By default, -l answer generates a SymbolLookup.libraryLookup for the platform-mapped library name, such
as libanswer.so or libanswer.dylib. Its discovery follows the platform loader’s rules.
-l :PATH embeds the path instead. Repeat -l for multiple libraries. The generated lookup also falls back
to the class loader’s symbols and the native linker’s default symbols.
For Java’s java.library.path lookup, generate with --use-system-load-library -l answer, then supply
-Djava.library.path=./native to the Java launcher. This emits System.loadLibrary("answer"); a path
library specification instead uses System.load and requires an absolute path. Header include directories
set with -I do not configure runtime library discovery.
Elide does not package the application library with generated sources or JARs. Keep the matching shared library and its dependencies available at runtime. Generating FFM bindings also does not automatically register those calls for an arbitrary Native Image application.
Generated bindings expose native pointers through MemorySegment and provide layouts for supported C
types. They do not infer ownership rules: use an appropriate Arena for Java-managed native allocations,
keep allocations and callback stubs alive while C uses them, and release library-owned memory through the
library’s documented API. Native code runs with the process’s privileges, and incorrect lifetimes or ABI
mismatches can crash the process.
Headers and platform limits
Elide supplies jextract’s builtin Clang headers. Library-specific headers and their dependencies still need to be available through the supplied paths and include directories. This is a C header generator; C++ interfaces need a C-compatible API. Review warnings about unsupported declarations rather than assuming every header construct becomes a Java type. Opaque structs can remain usable as pointer handles even when their layouts cannot be generated.
The macOS distribution does not include an Apple SDK. SDK-dependent headers require a compatible installed SDK and appropriate include configuration. Elide’s packaged Native Image compiler/sysroot configuration is a separate facility; it does not make every host header available to jextract. The self-contained header above requires no system SDK.