jextract-25 early-access build (25-jextract+2-4) on JDK 25.0.5+8 against SQLite 3.45.1: 25 functions and 13 constants generated into 4 committed files (the whole header would be 83 files), a small Db wrapper, the four arena kinds used wrongly on purpose, ADDRESS vs C_POINTER zero-length vs unbounded segments and the SIGSEGV from getString on a NULL pointer, sqlite3_open out-parameter, return codes to exceptions, a sqlite3_exec upcall and the VM exit when it throws, the JDK 25 native-access warning and --illegal-native-access=deny, library loading failures, and a JMH benchmark of FFM vs hand-written JNI vs plain Java (indicative only: shared 2-CPU machine). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_017ic4b6Kg9RWSTJzyz6KP9X
3.0 KiB
3.0 KiB
jextract: calling SQLite from Java with the FFM API
Companion code for the ankurm.com post "Calling C from Java with jextract and the FFM API: A Real Library, No JNI". No docs/ folder: the explanation lives in the article.
| Thing | Version |
|---|---|
| JDK | Temurin 25.0.5+8 (FFM API final since JDK 22, JEP 454) |
| jextract | openjdk-25-jextract+2-4 early-access build, 2025-11-25 (jdk.java.net/jextract), libclang 13.0.0 |
| SQLite | 3.45.1 (Ubuntu libsqlite3-dev 3.45.1-1ubuntu2.8) |
| JMH | 1.37 |
| gcc | 13.3.0 (only for the JNI comparison) |
Quickstart
sudo apt-get install libsqlite3-dev gcc # header + libsqlite3.so; gcc only for the JNI benchmark
export JAVA_HOME=/path/to/jdk-25
./scripts/fetch-jextract.sh # downloads and sha256-checks the jextract build into ./tools
export JEXTRACT=$PWD/tools/jextract-25/bin/jextract
./scripts/gen.sh # regenerate generated/ (the committed bindings)
./scripts/run-all.sh # compile everything, regenerate output/01..09 and 11
./scripts/fetch-jmh.sh && ./scripts/run-jmh.sh # the FFM vs JNI vs plain Java benchmark, about 4 minutes -> output/10-jmh.txt
Run any demo by hand: java --enable-native-access=ALL-UNNAMED -cp build/classes demo.Tour.
Layout
| Path | What |
|---|---|
scripts/gen.sh |
the jextract command, filtered to 25 functions and 13 constants |
generated/demo/sqlite/ |
what jextract produced from that command (4 files, committed so you can read them) |
src/demo/ |
hand-written code: Db (the wrapper), Hello, Tour, Arenas, Errors, Trap, NullText, Callbacks |
src/extra/V.java |
one-line program used by run-all.sh to test library loading with throwaway bindings |
jni/ |
the JNI twin used by the benchmark: SqliteJni.java and sqlite_jni.c |
bench/src/bench/SqliteBench.java |
JMH benchmark: plain Java vs FFM vs JNI |
Captured output (output/)
| File | What it is |
|---|---|
01-tools.txt |
versions of every tool used |
02-generate.txt |
the trimmed generation vs the whole header: file counts, line counts |
03-hello.txt |
first call, plus -Djextract.trace.downcalls=true |
04-native-access.txt |
JDK 25 with no --enable-native-access flag: the warning, and --illegal-native-access=deny |
05-tour.txt |
open, create, bind, insert, query |
06-arenas.txt |
confined, shared, auto and global arenas used wrongly |
07-pointer-traps.txt |
zero-length pointers, and the SIGSEGV from getString on a NULL pointer |
08-errors.txt |
return codes turned into exceptions |
09-callbacks.txt |
sqlite3_exec with an upcall, and an exception thrown inside it |
10-jmh.txt |
JMH numbers (machine dependent: shape, not result) |
11-library-loading.txt |
what -l produces, and the failure when the library name is wrong |
Timings and addresses change between runs; everything else is byte-stable. The Linux/x64 jextract build is used throughout; nothing here was run on macOS or Windows.