Files
javademos/jextract
asmhatreandClaude Sonnet 5.5 ecceaa8b32 Add jextract module: SQLite bindings generated with jextract, FFM arenas, C strings, out-parameters, errors, upcalls, and a JMH comparison with JNI
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
2026-10-30 16:36:09 +00:00
..

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.