Jolta — best in class Java version manager

Jolta — best in class Java version manager

Like Volta, but for Java.
Pin a JDK per project and never switch manually again.

macOSLinuxWindows zero dependenciesMIT

GitHub · Releases

$ cd my-service
$ jolta pin 21          # writes .java-version
$ java -version         # → OpenJDK 21, automatically

$ cd ../legacy-app      # has .java-version with "8"
$ java -version         # → 1.8, without running anything first

Install

Prebuilt binaries for every platform. There's no Rust toolchain to install and nothing to compile.

macOS & Linux Apple Silicon & Intel · any Linux distro (static musl)

Homebrew:

brew install OneAppPlatform/tap/jolta
jolta setup

Or the one-line installer:

curl -fsSL https://raw.githubusercontent.com/OneAppPlatform/jolta/main/install.sh | sh

Windows x86_64

Download jolta-x86_64-pc-windows-msvc.zip from the releases page, unzip, then:

.\jolta.exe setup

Setup adds the user PATH entries and the PowerShell JAVA_HOME hook automatically. Open a new terminal and run jolta doctor.

Sorry about the manual step. macOS and Linux get a one-line installer and Windows doesn't yet. That's a gap in how we ship it, not in jolta, which is fully supported on Windows once installed. A winget package is submitted and awaiting review; when it merges this becomes winget install OneAppPlatform.Jolta.

Verify with a new shell:

jolta doctor

How it works

Jolta puts lightweight shims on your PATH (java, javac, jar, jshell, …). Each one is a link to the single jolta binary. Every invocation walks up from the current directory to the nearest .java-version, resolves an installed JDK for it, sets JAVA_HOME, and execs the real tool. Resolution takes about 2 ms, which you won't notice next to JVM startup.

If a project pins a version you don't have, the first invocation downloads it on demand from the distro's official endpoint. Changing directory never triggers a download, only actually running something does. It's safe under parallel builds, and JOLTA_NO_AUTO_INSTALL=1 turns it off.

Restricted networks can point JOLTA_DOWNLOAD_BASE at an internal mirror ({base}/{vendor}/{version}/{os}-{arch}.{ext}) instead of the vendor endpoints. jolta mirror sync /srv/jdks builds that mirror: every platform, sha256 sidecars that are verified on each install, and enough metadata to keep update, upgrade, catalog and the first-run LTS bootstrap working air-gapped. jolta mirror verify re-hashes it on a schedule.

Jolta also finds the JDKs you already have: Homebrew, /Library/Java/JavaVirtualMachines, SDKMAN, /usr/lib/jvm, Windows Program Files, and JAVA_HOME_<major>_* environment variables.

How jolta compares

Current as of July 2026. Corrections welcome.

joltamiseSDKMANjenvasdf
.java-version honored by default opt-inopt-in
.sdkmanrc honored opt-inopt-in
JAVA_HOME set automatically opt-inpluginplugin
Downloads JDKs
Auto-install on first java run partial
Exact point-release pinning partial
GraalVM manual
Finds JDKs you already have (brew, system) manual
Point-release upgrades with cleanup manual
Remote catalog browsing
Generates Maven toolchains.xml from installed JDKs
Environment doctor with auto-repair (doctor --fix) diagnosediagnose
Native Windows WSL
Corporate mirror support partialpartial
Builds & verifies the mirror for you (mirror sync)
Air-gapped updates & catalog (mirror metadata)
Pruning that never breaks a pinned project
Also manages Maven/Gradle/other tools
No project config beyond .java-version
Battle-tested since 20262023201220112014

If Java is one of five languages you juggle, mise is excellent. If Java is the job, jolta does all of it by default. There's nothing to enable, and a .java-version is all your repo needs.

Why shims work better for Java than they did for Node

Volta proved this architecture, but Node made it costly in two ways. node starts in about 30 ms, so shim overhead was borderline noticeable. Node tooling also finds everything through PATH alone, which forced Volta to intercept npm's global installs as well, which is where most of its open bugs live.

Java inverts both. JVM startup is 40–100 ms or more, so a 2 ms resolution disappears below measurement noise. The long-running tools that matter (Maven, the Gradle daemon, IDEs) read JAVA_HOME rather than probing PATH, so jolta keeps one environment variable in sync and the hot path never crosses a shim at all.

mise makes the opposite bet: a shell hook rewrites your PATH before every prompt to point at the resolved tools directly. That's zero-overhead at invocation time, but the rewritten environment only exists where the hook runs, meaning an interactive shell, and only as of the last prompt. Scripts, Makefiles, CI steps, cron jobs, and IDE-spawned builds see a stale snapshot or nothing, and keeping that rewritten PATH correct across version switches and other tools' own edits to it is its own class of bugs. Jolta's shims re-resolve from the working directory on every invocation, so the same project gets the same JDK everywhere: terminal, CI, cron, IDE. No shell integration required. The shell hook only keeps JAVA_HOME exported for tools that read it; everything else works without it.

Common questions

How do I switch Java versions per project?

Run jolta pin 21 in the project. That writes a .java-version file, and from then on cd-ing into that directory selects Java 21 automatically. There's no command to re-run. Pin a different version in another project and moving between them switches for you.

Does it set JAVA_HOME, or just PATH?

Both. This matters because Maven and Gradle read JAVA_HOME: a version manager that only changes PATH leaves you with a java on the command line that disagrees with your build. jolta installs a shell hook that keeps JAVA_HOME pointed at the version in effect for the directory you're in.

Does it install JDKs, or only switch between ones I already have?

It installs them. jolta install 21 fetches a JDK, and pinning a version you don't have downloads it on first use, so entering a directory never blocks on the network. It also finds JDKs you installed elsewhere (Homebrew, /Library/Java, SDKMAN, Program Files) and can use those instead of downloading a second copy.

Can different modules in one repository use different JDKs?

Yes. The nearest pin wins: .java-version is resolved by walking up from the current directory, so a repository can pin 21 at the root and 17 in one module, and each directory gets its own JDK.

I already have .sdkmanrc files. Do I have to convert them?

No. jolta reads .sdkmanrc as well as .java-version, so existing repositories keep working and you don't need the whole team to switch first. Where both exist in the same directory, .java-version wins.

Does it work with IntelliJ IDEA?

Yes. There's a plugin that reads the same .java-version and sets the Project SDK, the Gradle JVM and the Maven importer JDK from it, with nothing to configure. It follows along live, so pinning a new version in a terminal switches the IDE within seconds without a restart or re-import.

Does it work on Windows?

Yes. Setup adds the shims to your user PATH in the registry and installs the JAVA_HOME hook into your PowerShell profiles. Installation is currently a zip download and .\jolta.exe setup; a winget package is submitted and awaiting review.

How do I remove it?

jolta implode removes ~/.jolta and the jolta lines from your shell profile, after a confirmation that lists exactly which JDKs will be deleted. JDKs you installed outside jolta are never touched.

Everything else

Commands, pinning semantics, distros, mirrors, and the JAVA_HOME story live in the full manual.

Uninstall

jolta implode

Removes ~/.jolta and the jolta lines from your shell profile, after a confirmation prompt that lists exactly which jolta-installed JDKs will be deleted. JDKs installed outside jolta and your .java-version files are never touched. jolta only ever deletes what it downloaded itself.