Starting with LWJGL 3.4.0, when the library runs on JDK 25 or later, a new backend takes over automatically. Instead of JNI for downcalls, libffi+JNI for upcalls, andDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/LWJGL/lwjgl3/llms.txt
Use this file to discover all available pages before exploring further.
sun.misc.Unsafe for off-heap memory access, LWJGL uses the JDK’s own Foreign Function and Memory (FFM) API throughout. The switch is invisible to most application code: the same memAlloc, glfwCreateWindow, and GLFWWindowSizeCallback APIs you already use continue to work; only the machinery underneath changes. The FFM backend also exposes a user-facing runtime bindings generator in org.lwjgl.system.ffm, letting you define custom native bindings in plain Java without a separate code-generation step.
Requirements
LWJGL 3.4.0 or later
The FFM backend was introduced in this release. Earlier versions use the JNI/Unsafe backend exclusively.
JDK 25 or later
FFM API became stable in JDK 22 (JEP 454), but LWJGL targets JDK 25+ for its backend. On older JDKs, LWJGL automatically falls back to JNI/Unsafe.
Opting out
If your application encounters compatibility or performance issues with the FFM backend, use theunsafe Maven classifier to select the JNI/Unsafe artifact instead:
- Maven
- Gradle
lwjgl artifact has an unsafe classifier. Other module artifacts (lwjgl-glfw, lwjgl-vulkan, etc.) are unchanged.
With the FFM backend active, LWJGL is fully functional when the JVM is started with
--sun-misc-unsafe-memory-access=deny, making it compatible with future JDK hardening.Using the runtime bindings generator
The FFM backend ships a runtime generator inorg.lwjgl.system.ffm. You describe a native API as a Java interface, annotate its methods, and call one of the bootstrap methods in FFM to get back a working implementation at runtime. No Kotlin, no offline build step.
It is recommended to use static imports for the FFM class:
Bootstrap methods
| Method | Purpose |
|---|---|
ffmGenerate(Class<T>) | Generate an implementation of a downcall interface |
ffmUpcall(Class<T>) | Create a binder for a single upcall type |
ffmStruct(Class<T>) | Create a binder for a struct type |
ffmUnion(Class<T>) | Create a binder for a union type |
Defining a downcall interface
Defining a struct binder
Nullable parameters
FFM represents null pointers asMemorySegment.NULL (not Java null). LWJGL generates wrapper logic based on annotations:
- No annotation → parameter is non-nullable; passing
nullorMemorySegment.NULLthrows. @FFMNullable→ null-restricted (Javanullthrows), butMemorySegment.NULLis accepted.- A configured
@Nullableannotation (e.g.,org.jspecify.annotations.Nullable) → LWJGL replaces JavanullwithMemorySegment.NULLautomatically.
Performance characteristics
The FFM backend is designed for zero overhead versus direct FFM API usage:- Lazy generation. A downcall
MethodHandleis not created until the first invocation of the corresponding interface method. Upcall and struct binders are not generated until their binder instances are first accessed. - Inlineable by the JIT. Implementations are created as hidden classes with trusted final fields (similar to Java records). Storing binder objects as
static finalfields enables the JIT to inline them aggressively. - Zero-allocation structs. Struct instance copies are inlined at bytecode level with hardcoded byte counts, eliminating intermediate allocations.
- Upcall throughput. Upcall invocation overhead is 3–4× lower than JNI upcalls. Instantiation is roughly 15× slower, so prefer long-lived upcall objects.
Known limitations
Maximum buffer size
Maximum buffer size
The FFM API limits
ByteBuffer instances to Integer.MAX_VALUE - 8 bytes (just under 2 GB). Creating a multi-byte buffer larger than this is no longer supported. Use MemorySegment directly for 64-bit addressed memory regions.Upcall exceptions
Upcall exceptions
Native code cannot propagate Java exceptions. By default, LWJGL wraps every upcall in a try-catch handler that prints uncaught exceptions. This behavior is controlled by two options:
Struct-by-value return in upcalls
Struct-by-value return in upcalls
Upcalls that return a struct by value cannot be called concurrently from multiple threads. As of LWJGL 3.4.0, this affects only
YGMeasureFunc in the Yoga module.Upcall arena management
Upcall arena management
Upcalls must be allocated in an FFM
Arena. By default, LWJGL uses a GC-managed auto arena. You can change this globally or use the ffmScoped* methods to supply your own arena per upcall:Bindings generator preview status
Bindings generator preview status
The
org.lwjgl.system.ffm API is in preview and subject to change between releases. The range of supported QoL transformations (auto-sizing array parameters, return value rewriting, etc.) is currently narrower than the offline Kotlin generator.Example: custom binding end-to-end
The following shows a complete, minimal custom binding for a hypothetical C librarygreeter that exports a single function greet(const char* name) -> int:
modules/samples/src/test/java25/org/lwjgl/demo package in the LWJGL repository, and the struct/union unit tests in modules/lwjgl/core25/src/test/java/org/lwjgl/system/ffm/StructTest.java.