A Java code-generation library that bridges C/C++ native code to JVM platforms -- desktop, mobile, and web.
- Overview
- How It Works
- Supported Targets
- TeaVM C Native Linkage
- Code Block Convention
- WebIDL Bindings
- IDLBase API
- Requirements
- Documentation
- Getting Started
- Libraries Using jParser
- License
Inspired by gdx-jnigen, jParser lets you embed native C/C++ code directly inside Java source files using annotated comment blocks. Each block is translated into target-specific Java source code, enabling a single base module to produce a bridge-agnostic core API plus platform bridge outputs for JNI (desktop/mobile), FFM (desktop, Java 25+), TeaVM web (JS/WASM), and TeaVM C (native applications).
For web targets, jParser uses Emscripten to compile C/C++ into JS/WASM and TeaVM to generate the corresponding Java-to-JavaScript bridge via @JSBody annotations.
jParser consists of two main stages:
Reads the hand-written Java source in the base module, which contains embedded native code blocks, and generates platform-specific Java source for each target:
| Output Module | Target | Description |
|---|---|---|
core |
Core API | Generated bridge-agnostic API classes |
shared/<Lib>-jni |
JNI | Generated JNI Java shared by desktop and Android |
shared/<Lib>-c |
TeaVM C | Generated TeaVM C Java shared by desktop and Android |
desktop/<Lib>-desktop-ffm |
FFM | Generated FFM Java for desktop (Java 25+) |
web/<Lib>-web |
TeaVM web | Generated @JSBody-annotated Java for web |
android/<Lib>-android |
JNI (Android) | Android JNI packaging |
android/<Lib>-android-c |
TeaVM C (Android) | Android TeaVM C packaging |
Compiles the C/C++ source into platform-specific native libraries:
| Platform | Toolchain |
|---|---|
| Windows | MSVC |
| Linux | GCC / G++ |
| macOS | Xcode CLI tools |
| Android | Android NDK |
| Web | Emscripten SDK |
| Target | Bridge | Platforms | Java Version |
|---|---|---|---|
| JNI | Java Native Interface | Windows, Linux, macOS, Android | 8+ |
| FFM | Foreign Function & Memory API | Windows, Linux, macOS | 22+ |
| TeaVM | JavaScript / WASM | Web browsers | Java 17+ for TeaVM 0.15 web modules/tooling |
| TeaVM C | TeaVM native C imports | Windows, Linux, macOS, Android | Java 17+ for TeaVM tooling |
TeaVM C generation supports three TeaVMCLinkage modes:
| Mode | Native resolution | Deployment |
|---|---|---|
STATIC |
Links the packaged archive into the final native target. This remains the default. | Application executable, subject to unrelated shared dependencies |
SHARED_LINKED |
Links against a DLL import library or a shared object/dylib; the operating-system loader resolves it when the process starts. | Application plus the matching shared native library |
RUNTIME_LOADED |
loader-c opens the shared library on demand, validates its generated versioned API table, and binds the generated dispatch shim. |
Application plus one runtime-selectable shared native library per logical binding |
The Gradle plugin exposes the typed mode directly:
import com.github.xpenatan.jParser.builder.tool.TeaVMCLinkage
jParser {
teaVMCLinkage.set(TeaVMCLinkage.RUNTIME_LOADED)
}Manual builders set the same value before constructing BuildToolOptions:
import com.github.xpenatan.jParser.builder.tool.BuildToolOptions;
import com.github.xpenatan.jParser.builder.tool.TeaVMCLinkage;
BuildToolOptions.BuildToolParams params = new BuildToolOptions.BuildToolParams();
params.teaVMCLinkage = TeaVMCLinkage.SHARED_LINKED;
BuildToolOptions options = new BuildToolOptions(params, args);loader-c is the TeaVM C substitution for the normal JParserLibraryLoader API. On Windows it uses LoadLibraryExW/GetProcAddress; Linux, macOS, and Android use dlopen/dlsym. A logical library may be bound only once, and jParser intentionally keeps a successfully loaded library open for the process lifetime. Future iOS C support must use native code bundled with and signed as part of the application; it will not support downloading and executing arbitrary libraries.
RUNTIME_LOADED derives the normal platform filename from the logical library name by default. To choose a specific backend or physical payload, set JParserLibraryLoaderOptions.fileName; this exact filename bypasses automatic prefix and architecture-suffix decoration, while path remains its containing directory:
JParserLibraryLoaderOptions options = new JParserLibraryLoaderOptions();
options.path = "plugins";
options.fileName = "webgpu_dawn64.dll";
JParserLibraryLoader.load("webgpu", options, listener);Native binaries inside a dependency jar are build resources, not runtime files. A TeaVM C launcher or native-resource consumer must extract shared binaries to the filesystem and deploy them where the platform loader can open them. The generated portable CMake hook performs native target wiring and stages selected shared binaries for CMake-based consumers.
In base Java source files, native code is embedded via annotated comment blocks. jParser reads these blocks and generates the appropriate code for each target.
public class MyLib extends IDLBase {
// TeaVM replacement — generates @JSBody-annotated method for web
/*[-TEAVM;-REPLACE]
@org.teavm.jso.JSBody(params = {"this_addr"},
script = "var jsObj = [MODULE].wrapPointer(this_addr, [MODULE].MyType);"
+ "return jsObj.getValue();")
private static native int internal_native_getValue(int this_addr);
*/
// JNI native code block — compiled into C++ for desktop & mobile
/*[-JNI;-NATIVE]
MyType* obj = (MyType*)this_addr;
return obj->getValue();
*/
private static native int internal_native_getValue(long this_addr);
}| Command | Description |
|---|---|
-NATIVE |
Inline C/C++ code compiled for the target |
-ADD |
Adds code to the generated output |
-ADD_RAW |
Adds raw code without processing |
-REMOVE |
Removes code from the generated output |
-REPLACE |
Replaces the following method with the block content |
-REPLACE_BLOCK |
Replaces the following code block |
-IDL_SKIP |
Placed on a class comment to skip IDL generation for that class |
To reduce the effort of manually porting each method, jParser supports Emscripten WebIDL. Define a .idl file and jParser automatically generates binding code for all targets.
interface NormalClass {
void NormalClass();
long addIntValue(long value1, long value2);
static long subIntValue(long value1, long value2);
attribute long intValue;
attribute float floatValue;
};This generates fully working Java classes with native bindings for JNI, FFM, and TeaVM — no manual glue code required.
- IDL helper classes (
IDLInt,IDLIntArray, etc.) let you pass primitive pointers to C++. They work across Emscripten, desktop, and mobile. - C++ enums are converted into Java enums, each carrying the integer value from native code.
[Value]methods return a cached copy of the object. The cache is overwritten on each call — do not retain references.[NoDelete]classes should not havedispose()called. All other classes require explicit disposal.
Every native class extends IDLBase, which provides common memory-management functionality.
Important: jParser does not automatically dispose C++ objects. You must call
dispose()when you're done with an object to free native memory. Only objects you create or explicitly own require disposal. Creating and disposing native objects is expensive — avoid doing it every frame.
| Method | Description |
|---|---|
ClassName.native_new() |
Creates an empty instance without native data |
ClassName.NULL |
Returns a NULL instance — use instead of Java null for native parameters |
dispose() |
Deletes the native instance (only if owned) |
isDisposed() |
Checks whether the native instance has been disposed |
native_setVoid(...) |
Sets an integer or long memory address |
native_reset() |
Resets the instance to default state |
native_takeOwnership() |
Takes ownership, enabling dispose() to delete the object |
native_releaseOwnership() |
Releases ownership, preventing dispose() from deleting |
native_hasOwnership() |
Checks whether you own the native instance |
native_copy(...) |
Copies memory address and native data from another instance |
The
native_prefix is used to avoid naming conflicts with C/C++ methods.
| Requirement | Purpose |
|---|---|
| JDK 11+ | Building jParser tool modules |
| JDK 22+ (25 recommended) | FFM modules and FFM-based apps |
| Visual Studio C++ | Windows native builds |
| GCC / G++ | Linux native builds |
| Xcode CLI tools | macOS native builds |
| Emscripten SDK | Web builds (JS/WASM) |
Windows (MSVC): Windows native builds initialize the Visual Studio C++ environment with
vcvarsall.bat. The build auto-detects it fromVCVARSALL_PATH/JPARSER_VCVARSALL,PATH, Visual Studio environment variables, orvswhere.exe. To force a location, pass-Djparser.vcvarsall=C:\Program Files\Microsoft Visual Studio\[Year]\[Edition]\VC\Auxiliary\Build\vcvarsall.bat.
- Runtime publishing/classifier strategy guide:
docs/runtime-maven-artifacts.md - Architecture details:
docs/architecture.md - Workflow/checklists:
docs/workflows.md - Command matrix:
docs/commands.md
For a complete working example, refer to the examples/TestLib module.
jParser projects follow a module pattern centered on source (base), generator entry (builder), and generated/runtime-specific outputs:
| Module Suffix | Purpose |
|---|---|
base |
Hand-written Java source with embedded native code blocks |
builder |
Build entry point that configures IDL, targets, generation, and native compilation |
core |
Generated bridge-agnostic API output (do not hand-edit) |
shared/<Lib>-jni |
Generated JNI Java shared by desktop and Android JNI (do not hand-edit) |
shared/<Lib>-c |
Generated TeaVM C Java shared by desktop and Android C (do not hand-edit) |
desktop/<Lib>-desktop-jni |
Desktop JNI native payloads |
desktop/<Lib>-desktop-ffm |
Generated desktop FFM Java output + native payloads (do not hand-edit) |
desktop/<Lib>-desktop-c |
Desktop TeaVM C native payloads |
web/<Lib>-web |
Generated TeaVM WebAssembly output (do not hand-edit) |
android/<Lib>-android |
Android JNI packaging |
android/<Lib>-android-c |
Android TeaVM C packaging |
# 1. Build runtime (required once)
./gradlew :jParser:runtime:builder:runtime_helper_build_project_windows64_jni
./gradlew :jParser:runtime:builder:runtime_helper_build_project_windows64_ffm
# 2. Generate code + compile native library
./gradlew :examples:TestLib:lib:builder:TestLib_build_project_windows64_jni
./gradlew :examples:TestLib:lib:builder:TestLib_build_project_windows64_ffm
# 3. Run the desktop app
./gradlew :examples:TestLib:app:platforms:desktop-jni:TestLib_run_app_desktop_jni
./gradlew :examples:TestLib:app:platforms:desktop-ffm:TestLib_run_app_desktop_ffm
Replace
windows64withlinux64,mac64, ormacArmfor other platforms. On Windows, usegradlew.batinstead of./gradlew. Desktop FFM app tasks useLibExt.javaFFMTarget; ensure that Java toolchain is available when running..._run_app_desktop_ffmtasks.
| Library | Description | Status |
|---|---|---|
| jWebGPU | WebGPU bindings for Java | Active |
| xImGui | Dear ImGui bindings for Java | Active |
| xJolt | Jolt Physics bindings for Java | Active |
| xLua | Lua bindings for Java | Active |
| xBullet | Bullet Physics bindings for Java | Active |
| gdx-box2d | Box2D bindings for libGDX | Inactive |
| gdx-physx | PhysX bindings for libGDX | Inactive |
If you find this project valuable and want to fuel its continued growth, please consider sponsoring it. Your support keeps the momentum going!
jParser is licensed under the Apache License 2.0.