Library / SDK
openjdk/jextract avatar
openjdk/jextract

jextract turns a C header into Java bindings without writing the glue by hand

https://openjdk.org/projects/code-tools

563 stars87 forksJavaGPL-2.0

At a glance

What is it?
jextract is the OpenJDK tool that mechanically generates Java bindings from native library headers, using the clang C API to parse them and targeting the Foreign Function and Memory API. It suits a Java developer who needs to call a C library the platform does not already expose, and it is a binding generator rather than a native access layer of its own.
Who is it for?
jextract fits a Java developer who needs typed access to a C library that the platform does not already bind, since the generated code targets the Foreign Function and Memory API and covers a large set of headers. It does not fit a project that needs a stable, versioned API, because the tool and the bindings move together with the JDK, and it does not fit a team that cannot take a dependency on clang and libclang.
Can I use it commercially?
Yes, with conditions. GPL-2.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
Is it still maintained?
Yes. The repository last received commits 9 days ago.
What is it written in?
Mainly Java, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A binding generator, not a foreign function runtime

jextract is a tool which mechanically generates Java bindings from native library headers. It uses the clang C API in order to parse the headers associated with a given native library, and the generated Java bindings build upon the Foreign Function and Memory API.

That distinction is the one to hold on to. jextract does not give Java a way to call C. The Foreign Function and Memory API does that, and it is part of the JDK. jextract is the code generator that writes the Java side of that API for you, so you are not hand writing the struct layouts, the function pointer declarations and the downcall stubs that a foreign call needs.

The value of that is highest exactly where a C API is large and dull. A library with hundreds of functions and a dozen struct types is the case where hand writing bindings loses, and where a mechanical translation is both faster and less error prone than a person typing declarations from a header.

The tool was originally developed in the context of Project Panama, and then made available in the Project Panama Early Access binaries.

The project is part of the OpenJDK code tools, and the repository is under the openjdk organisation, so the licence is GPL-2.0 with the Classpath exception, the standard OpenJDK arrangement.

Prebuilt binaries, or a build against your own LLVM

There are two ways to get the tool, and the documentation recommends the first for most people.

Pre-built binaries for jextract are periodically released, and the documentation is precise about what they are: they are built from the master branch of this repo, and they target the foreign memory access and function API in the latest mainline JDK.

The alternative is to build from the latest sources, which include all the latest updates and fixes.

The build needs the C libclang API, and the easiest route is to download LLVM binaries for your platform, with version 13.0.0 recommended. Both the tool and the bindings it generates depend heavily on the Foreign Function and Memory API, and a suitable JDK 23 or higher distribution is also required.

That JDK 23 floor is worth noting. The generated code is not portable backwards to older JDKs, because it is written against an API that did not exist in them. The repository handles the older targets with branches: the master branch always tracks the latest version of the JDK, and to build a jextract tool that works against an earlier JDK you check out the appropriate branch, for example git checkout jdk21 to build a tool which works against JDK 21. Over time, new branches will be added, each targeting a specific JDK version.

So there are two axes to choose on, the JDK you target and whether you use a published binary or build your own, and the branches make the first axis explicit.

The build is a single gradle invocation with two properties

Building from source is one command, on Windows with gradlew.bat instead.

sh
$ sh ./gradlew -Pjdk_home=<jdk_home_dir> -Pllvm_home=<libclang_dir> clean verify

Two properties, the JDK home and the LLVM home, and the documentation explains what each is for. The JDK used to build, which it calls the toolchain JDK, is passed in separately as a property, and the gradle compatibility matrix is the reference for which java version needs to be in PATH or JAVA_HOME to run gradle at all. The gradle version used is 8.11.1, which the gradle wrapper fetches automatically.

The LLVM property can point at an extracted release, which is the recommended route, or at a local installation. The documentation gives the macOS cases explicitly.

The three macOS locations given are `/Library/Developer/CommandLineTools/usr/` if using Command Line Tools, `/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/` if using XCode, and `$(brew --prefix llvm)` if using the LLVM install from Homebrew.

After the build there is a new jextract folder under build, and to run the tool you invoke the jextract command in the bin folder.

sh
$ build/jextract/bin/jextract
Expected a header file

That expected output is a useful first check, because it tells you the tool started and is waiting for input rather than failing on a missing shared library.

The samples directory is the catalogue of what works

The samples/ directory is the most useful part of the repository for judging coverage, and it is long.

There are small ones first: helloworld, go, dlopen and cleanall.sh, compilesourceall.sh which suggests there is a script to build all the samples.

Then there are the real libraries: cblas, lapack, lp_solve, libcurl, libgit2, libjimage, libzstd, pcre2, python3, readline, sqlite, tcl, libclang itself, and libffmpeg.

The graphics and machine learning ones are the interesting cases: opengl, opengl-minimal-bindings, tensorflow and onnx. A working binding set for TensorFlow and for OpenGL covers a lot of ground, because those are two libraries with famously awkward ABIs, and the fact that both are in the samples directory is a reasonable proxy for how hard the generator copes with real headers.

There is also a libproc sample, which is a platform specific entry point, and that is a good reminder that a generated binding is only as portable as the library it wraps.

The presence of dlopen is worth a second look too, because it suggests the generated code is not limited to statically linked libraries, and the samples/README.md is where the details would be.

Tests run under jtreg, and they need cmake

The repository contains a comprehensive set of tests, written using the jtreg test framework, and they are run with the same gradle wrapper and the same two properties plus a third.

sh
$ sh ./gradlew -Pjdk_home=<jdk_home_dir> -Pllvm_home=<libclang_dir> -Pjtreg_home=<jtreg_home> jtreg

The jtreg_home property points at a jtreg installation, and on Windows the gradlew.bat form is used instead. One dependency is called out explicitly: running the jtreg task requires cmake to be available on the PATH.

The test story matters for this project more than for most, because the output of jextract is code, and generated code that does not compile is worthless. A test suite that builds bindings for a spread of libraries and compiles them is a much stronger signal than a unit test of the parser, and jtreg is the harness the OpenJDK projects use for exactly that kind of check.

The repository root also carries the pieces of an OpenJDK contribution. There is a .jcheck/ directory, which is the style checker the OpenJDK project runs over source and build files, a Makefile that is a thin wrapper around make/Init.gmk, an updateclang/ directory, a test.h header, and gradle/ with the wrapper. The Makefile even refuses to run on a non-GNU make, which tells you the build is expected to run inside an OpenJDK style environment rather than as a standalone Gradle project.

Where jextract is the wrong choice

There are honest limits, and the biggest one is that the generated bindings have no version of their own.

Because the prebuilt binaries are built from master and target the latest mainline JDK, a binding file generated today is tied to a specific JDK's Foreign Function and Memory API. There is no compatibility layer in the generated code, and the project's answer to targeting an older JDK is a separate branch, which is a sensible answer for a tool maintained by the JDK team but not one a downstream team can rely on for years.

The second limit is scope. jextract handles what a header describes, so a library with its own runtime conventions, a plugin system, a global initialiser or a callback scheme that has no C declaration still needs hand written glue. The dlopen sample hints that dynamic loading is considered, but the generator cannot invent an idiom the header does not express.

The third is the toolchain. Building the tool needs clang and libclang, and generating bindings at build time means your build now depends on a C header parser and on a JDK with the FFM API. A team that wanted to remove native dependencies has just added a different kind of one.

The alternative approach is to write the bindings by hand, or to use a runtime foreign function library that loads a library and calls it reflectively without generating source. Generated bindings win on type safety, on reviewability of the generated code, and on getting a large API surface right the first time. Hand written ones win on longevity, on the absence of a build dependency, and on the ability to express the parts of a library that have no C declaration.

Editorial conclusion

jextract fits a Java developer who needs typed access to a C library that the platform does not already bind, since the generated code targets the Foreign Function and Memory API and covers a large set of headers. It does not fit a project that needs a stable, versioned API, because the tool and the bindings move together with the JDK, and it does not fit a team that cannot take a dependency on clang and libclang. Before adopting it, build the tool from source with the documented gradle command and check that the headers you care about produce usable bindings, and pick a prebuilt binary or the jdk21 branch depending on which JDK you are targeting.

Frequently asked questions

What does jextract actually generate?

It mechanically generates Java bindings from native library headers. It uses the clang C API to parse the headers, and the generated bindings build upon the Foreign Function and Memory API, so the tool is a binding generator rather than a foreign function runtime of its own.

How do I build jextract from source?

With gradle and two properties, jdk_home and llvm_home: sh ./gradlew -Pjdk_home=<jdk_home_dir> -Pllvm_home=<libclang_dir> clean verify. You need the C libclang API, with LLVM 13.0.0 recommended, and a JDK 23 or higher distribution.

Can I use jextract with a JDK older than 23?

The master branch always tracks the latest JDK, so for an older target you check out the matching branch, for example git checkout jdk21 to build a tool that works against JDK 21. New branches are added over time, each targeting a specific JDK version.

How are the jextract tests run?

The tests use the jtreg framework and are run through the gradle wrapper with an extra jtreg_home property, for example sh ./gradlew -Pjdk_home=<jdk_home_dir> -Pllvm_home=<libclang_dir> -Pjtreg_home=<jtreg_home> jtreg. Running that task requires cmake to be on the PATH.

Official sources

  1. Issues
  2. License: GPL-2.0
  3. openjdk/jextract on GitHub
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/openjdk-jextract.svg)](https://hysenlabs.com/projects/openjdk-jextract)

Was this useful?