$ 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.
| jolta | mise | SDKMAN | jenv | asdf | |
|---|---|---|---|---|---|
.java-version honored by default |
✓ | opt-in | ✗ | ✓ | opt-in |
.sdkmanrc honored |
✓ | opt-in | opt-in | ✗ | ✗ |
JAVA_HOME set automatically |
✓ | opt-in | ✓ | plugin | plugin |
| 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) |
✓ | diagnose | ✗ | diagnose | ✗ |
| Native Windows | ✓ | ✓ | WSL | ✗ | ✗ |
| Corporate mirror support | ✓ | partial | ✗ | ✗ | partial |
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 | 2026 | 2023 | 2012 | 2011 | 2014 |
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?
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?
.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.