Turn any executable JAR or WAR into a standalone native binary — no Java required at runtime.
jar2native packages a Java JAR/WAR into a self-contained executable with an embedded JRE. The output is a single binary file that runs anywhere the target platform supports, with zero external dependencies.
- Distribute Java apps as native binaries — Ship a single executable instead of requiring users to install Java.
- Simplify deployment — One file to copy and run. No
java -jar, no CLASSPATH, no JRE setup. - Cross-platform builds — Package for Linux, macOS, or Windows from a single command.
- Embed in containers — Smaller footprint than a full JDK image; just copy the binary in.
- Inspect — Parse
MANIFEST.MF, detect Spring Boot, validateMain-Class. - Resolve JDK — From
--jdk,JAVA_HOME, or auto-search standard platform locations. - Build runtime — Full JRE via
jlink(JDK 9+) or copy legacy JRE (Java 8). Use-analyzeto runjdepsfor module trimming. - Assemble payload — Deterministic
payload.zip(app + JRE +manifest.json) with fixed timestamps and content hashing. - Stamp runner — A precompiled, platform-specific runner reads its configuration from the binary tail; jar2native appends the payload and JSON without compiling Go.
- No runtime toolchain — Supported targets use only the embedded precompiled runner; packaging never invokes
go build.
Download the executable for your platform from the Releases page, then run it directly:
# Linux/macOS
chmod +x jar2native
# Package a JAR into ./myapp
./jar2native -jar app.jar -o myapp
# Package a WAR into ./myapp
./jar2native -jar app.war -o myapp
# Run the generated self-contained application
./myappOn Windows, use PowerShell. Windows executables are generated with the .exe suffix:
# Package a JAR into .\myapp.exe
.\jar2native.exe -jar app.jar -o myapp
# Run the generated self-contained application
.\myapp.exeThe input JAR/WAR must be executable and contain a Main-Class. The packaging machine needs a compatible target-platform JDK. Release builds include the generic runners, so packaging does not need Go; the generated application needs neither Go, Java, nor a JRE at runtime.
# Build the tool and its embedded platform runners (requires Go 1.21+)
make build
# Package a JAR — produces ./myapp
./jar2native -jar app.jar -o myapp
# Package a WAR (must be executable — have Main-Class)
./jar2native -jar app.war -o myapp
# With JVM arguments
./jar2native -jar app.jar -o myapp --jvm-args "-Xmx2g -Dfile.encoding=UTF-8"
# Run the result
./myappOn Windows, build and run with:
make build
.\jar2native.exe -jar app.jar -o myapp
.\myapp.exe--platform selects the embedded runner and JRE target platform. The embedded JRE is built by jlink from the JDK selected by --jdk or JAVA_HOME, so it must be a JDK for the target platform. The current implementation does not download or switch JDKs automatically. A binary built for one OS cannot run on another OS; build the matching target package instead.
- Build jar2native: Go 1.21+ (runs
make build, which creates embedded runners) - Package an application: JDK 9+ (for
jlink) or JDK 8 (legacy JRE copy); no Go - Run the generated application: No dependencies. The output binary is fully self-contained — no Go, Java, JRE, or external runtime.
| Platform | JDK Source | Runtime Image | Status |
|---|---|---|---|
| macOS (amd64/arm64) | Homebrew, .jdks, /Library/Java/... |
jlink or legacy copy |
✅ |
| Linux (amd64/arm64) | /usr/lib/jvm, /usr/java, .jdks |
jlink or legacy copy |
✅ |
| Windows (amd64) | Program Files\Java, .jdks |
jlink or legacy copy |
✅ |
amd64 means x86-64 and works on both Intel 64-bit and AMD 64-bit CPUs. The Windows target currently documented and tested is windows/amd64; 32-bit x86 (windows/386) is not currently supported because it also requires a compatible 32-bit JDK/JRE.
The tool auto-detects the JDK from standard locations per OS. Override with --jdk or JAVA_HOME.
-jar Path to JAR or WAR (required)
-o, --output Output binary name (default: same as input without extension)
--jdk JDK home path (default: JAVA_HOME or auto-detect)
--platform Target os/arch (default: host, e.g. linux/amd64)
--jre-mode JRE build mode: auto, jlink, copy (default: auto)
-analyze Run jdeps to trim unused modules (default: full JRE)
--modules Extra JDK modules, comma-separated (use with -analyze)
--jvm-args JVM args passed to runner (e.g. "-Xmx2g -Dfile.encoding=UTF-8")
--verbose Verbose output
- Full JRE by default —
jdepsoften fails on real-world WARs (obfuscated classes, new bytecode tags). Default to full JRE; jdeps is opt-in via-analyze. - Deterministic builds — Fixed timestamp (1980-01-01), normalized permissions (0755/0644), deflate compression. Same input always produces byte-identical output.
- PID-based cache lock — Stale locks from killed processes are automatically reclaimed. Atomic extract to temp dir + rename.
- Single source of truth —
runner/shared.gohandles zip-slip protection, cache management, and manifest verification. It's compiled normally by the tool AND written verbatim into each generated runner, eliminating build-time/runtime drift.
jar2native/
├── main.go # CLI entry, config, platform, logging, orchestration
├── go.mod
├── Makefile
├── jdk/jdk.go # JDK discovery, version detection, validation, jlink compress
├── runtime/builder.go # jlink full build + module trim + legacy JRE copy
├── payload/payload.go # Artifact inspection, manifest, deterministic payload.zip, zip-slip extraction
├── analyzer/analyzer.go # jdeps module dependency analysis (opt-in)
├── runner/runner.go # Runner stamping and legacy template helpers
├── runner/stamp.go # Deterministic payload/config trailer format
├── runner/generic/main.go # Runtime-configured precompiled runner
├── runner/bin/ # make runners output, embedded into jar2native
├── runner/shared.go # Shared zip-slip, cache, and manifest logic
└── tests/e2e/run.sh # End-to-end test (3-line shell script)
# Build the tool first
make build
# Run e2e — downloads Jenkins WAR, packages it, runs the binary
bash tests/e2e/run.shMIT