Native Library Loading
Axyra Sheets is a native engine behind a Java API, so there is one operational concern that a pure-Java library does not have: getting the right binary loaded. This page covers how that works and what goes wrong.
How resolution works
On first use of any API that touches the engine, the native loader:
- Checks the
axyra.native.pathsystem property. If set, it loads that file directly and stops. - Otherwise reads
os.nameandos.archand derives a platform key —osx,linux, orwindows, plusaarch64orx86_64. - Looks up
/native/<platform-key>/<library>on the classpath, where the library islibaxyra_jni.dylib,libaxyra_jni.so, oraxyra_jni.dll. - Extracts it to a process-private temporary directory and loads it. The extraction is cached for the life of the process.
arm64 and aarch64 are both mapped to aarch64, so a JVM reporting either
resolves correctly. Every other architecture is mapped to x86_64, so an
unsupported platform such as Windows on ARM finds the x86-64 library and fails
to load it.
The development override
java -Daxyra.native.path=/abs/path/to/libaxyra_jni.dylib -jar app.jar
This bypasses the classpath lookup and the extraction entirely, and is how you run against a library you just built yourself. The engine’s own test suite uses it, and it is the fastest edit-build-run loop when you are working on the Rust side.
Do not use it in production: it hard-codes an absolute path and bypasses the per-platform lookup, so an architecture mismatch surfaces only as a load error.
Failure modes
| Symptom | Cause |
|---|---|
UnsatisfiedLinkError: axyra native lib: native lib not found on classpath: /native/osx-aarch64/libaxyra_jni.dylib |
The Axyra Sheets JAR does not contain a native library for this platform, or a repackaging step dropped it. |
UnsatisfiedLinkError without the native lib not found text |
A library was found but the OS refused to load it — usually the wrong architecture, or a missing system dependency such as an old glibc. |
NoClassDefFoundError: Could not initialize class io.keikai.axyra.sheets.Workbook |
A later symptom of a failed load. The first error in the log is the UnsatisfiedLinkError that explains it. |
| Works locally, fails in the container | The classic one. Your machine’s platform is bundled; the container’s is not. |
| Works in tests, fails in the shaded jar | The shading step dropped one or more native/** resources. |
Shading and fat jars
native/** are resources, and resource filtering is the usual culprit. When
building a shaded or shadowed jar:
- Do not exclude
native/**. - Do not filter binary resources through a text-substitution step — it corrupts
them silently, and the failure surfaces as
UnsatisfiedLinkErrorrather than as anything mentioning filtering. - Preserve the library for every platform you need. Check the shaded jar rather
than assuming all
native/**resources survived the merge.
Containers
Build the image for the architecture it will run on. An arm64 image built on an
Apple Silicon laptop and deployed to amd64 hosts will fail on load — or, worse,
run under emulation at a fraction of the speed. Set the platform explicitly in
your build.
A slim base image may also lack a suitable glibc. The Linux builds target glibc; Alpine and other musl distributions are not supported, and neither is Windows on ARM.
Temp directory
The library is extracted to a process-private temp directory, so the process
needs a writable temp location. In a hardened container with a read-only
filesystem and no writable /tmp, set java.io.tmpdir to a writable volume, or
use axyra.native.path to point at a library already on disk.
Each process extracts its own copy. Many short-lived processes therefore pay the
extraction cost repeatedly — if that matters, axyra.native.path avoids it.
Verifying at startup
Fail fast rather than at the first user request:
public static void main(String[] args) {
try (Workbook probe = Workbook.create()) {
log.info("Axyra Sheets engine loaded; license {}", Workbook.licenseStatus());
} catch (Throwable t) {
log.error("Axyra Sheets failed to initialise", t);
throw t;
}
...
}
Threading and memory
- One
Workbookbelongs to one thread at a time. Using the same workbook (or itsSheet,Range, andCellviews) from two threads concurrently throwsAxyraException: workbook is already in userather than blocking. Separate workbooks run in parallel, so give each thread or task its own workbook — for example, read a template into bytes once and open it per task withWorkbook.openBytes. - A
Workbookowns native memory that the JVM’s garbage collector cannot reclaim. Always close it. In a request-scoped service, a workbook that escapes its try-with-resources is a leak no heap tuning will fix. Sheet,Range, andCellare views, valid only while their workbook is open. Using one afterwards throwsIllegalStateException.- The native heap is outside
-Xmx. A container memory limit must leave room for it above the JVM heap.