The jolta manual
Commands at a glance
Every command, one line each — deep dives follow, and
jolta help <command> shows the same right in your terminal.
| Command | What it does |
|---|---|
jolta setup | Install shims + shell profile setup |
jolta pin <spec> [--resolved] | Write .java-version here (--resolved pins the exact version) |
jolta default <spec> | Set the global fallback version |
jolta install <spec> | Download a JDK (21, corretto@21, lts, latest) |
jolta catalog [x] | The JDK catalog: latest per distro/major, @v prefix filters (alias search) |
jolta update | Check jolta-managed JDKs for newer point releases |
jolta upgrade [spec] | Upgrade jolta-managed JDKs, pruning old builds |
jolta uninstall <name> | Remove a jolta-managed JDK |
jolta prune [spec] | Remove superseded builds and stale non-LTS majors — anything a project pin references is kept |
jolta vendor [name] | Preferred distro for vendorless specs (beats even higher builds of other distros) |
jolta list [--json] | List visible JDKs with distro, star the active one |
jolta jdks | Machine-readable list: major / version / distro / home |
jolta current [--json] | Show the version resolved here, and why |
jolta which [tool] | Full path the shim would exec |
jolta exec <cmd> | Run any command with JAVA_HOME/PATH set |
jolta env | Print export lines for eval |
jolta home | Print the resolved JAVA_HOME |
jolta hook [shell] | Print the shell hook (zsh, bash, fish, powershell) |
jolta completions [shell] | Print tab-completions (zsh, bash, fish) |
jolta toolchains [--write] | Maven toolchains.xml (+ Gradle hint) from installed JDKs |
jolta mirror sync|verify | Build or re-hash an offline JDK mirror (JOLTA_DOWNLOAD_BASE) |
jolta reshim | Regenerate shims after installing JDKs elsewhere |
jolta doctor [--fix] | Diagnose PATH / shim / JAVA_HOME problems; --fix repairs the safe parts |
jolta implode | Uninstall jolta completely |
Everyday commands
jolta pin <spec> [--resolved]
Pin a Java version for this project.
Writes the spec to ./.java-version (a single line — commit it). If no
installed JDK satisfies the spec, jolta downloads one on the spot, exactly as a shim
would have on first use; with JOLTA_NO_AUTO_INSTALL=1 it warns and writes
the pin anyway. Pinning bumps jolta's state stamp, so shells running the
hook refresh JAVA_HOME at their next prompt —
no cd needed.
jolta pin 21 # any distro, newest 21.x wins
jolta pin corretto@21 # this project wants Corretto specifically
jolta pin 21.0.4 # exact: this build or nothing
jolta pin lts # keyword: writes the current LTS major
jolta pin 21 --resolved # writes the exact version 21 lands on (21.0.4)
The keywords lts and latest expand to a concrete major at
pin time, so the pin never floats. --resolved is the opposite dial:
byte-identical toolchains for CI and teammates, at the cost of editing the pin for
point-release bumps (jolta upgrade keeps satisfying it either way).
Resolutions remember which file pinned them, which is how
prune knows a build is still referenced.
jolta current
What resolves here, and why.
Prints the resolved full version, its distro, the source that selected it — the
path of the winning .java-version/.sdkmanrc, the global
default, or the system JDK — and the home path. When the answer surprises you, the source line is the
explanation.
$ jolta current
21.0.4 (temurin) (from /work/api/.java-version)
/Users/you/.jolta/jdks/temurin-21.0.4
--json emits the same as one object
(version, vendor, home, source)
for editors and scripts.
jolta list · jolta jdks
Human and machine views of every visible JDK.
list (alias ls) shows jolta-managed installs and
discovered system JDKs with their distros, starring the one
active in the current directory and naming the pin that selected it.
jdks prints the same data as tab-separated
major · version · distro · home rows for
scripts — stdout is clean, warnings go to stderr. list --json is the
structured view: the active pin plus one object per JDK
(version, major, vendor, home,
managed, active).
jolta which [tool] · jolta home
The exact paths a shim would use.
which prints the full path of the tool binary the shim would exec
(default java) — it runs the same resolution as the shim, so the two can
never disagree. home prints the resolved JAVA_HOME, nothing
else, making it safe for command substitution:
export JAVA_HOME="$(jolta home)"
jolta which javac # .../jdks/temurin-21.0.4/bin/javac
Both fail with a non-zero exit and an actionable message when the pin can't be satisfied — never a fabricated path.
jolta exec <cmd> [args…] · jolta env
Run anything under the project's JDK; export it in scripts.
exec resolves the pin, sets JAVA_HOME, prepends the JDK's
bin to PATH, and execs your command — arguments pass through
byte-for-byte, the child's exit code is yours, and a missing JDK auto-installs first.
It's the right way to run launcher scripts that consult JAVA_HOME
(mvnw, gradlew) from CI or Makefiles. env prints
shell-quoted export lines for the same environment:
jolta exec ./mvnw verify
eval "$(jolta env)" # JAVA_HOME + PATH for the rest of the script
jolta default <spec>
The global fallback version.
Used whenever no .java-version/.sdkmanrc claims the
directory. Stored as a one-line file in $JOLTA_HOME/default; warns (but
still writes) when nothing installed matches yet. The lts /
latest keywords work here too. On a fresh machine with no default
and no pin, the first java invocation installs the latest LTS and sets it
as the default automatically.
Installing & updating
jolta install <spec>
Download a JDK from its vendor.
Accepts every spec form: 21 (latest 21.x of the
preferred vendor, default Temurin), corretto@21,
21.0.4 (that exact release), zulu@21.0.4, and the
lts / latest keywords for the current LTS / feature major.
Installs land in
$JOLTA_HOME/jdks/<distro>-<version> and are re-shimmed
immediately.
On macOS an installed JDK is also registered with
/usr/libexec/java_home, so java_home -V and anything
that asks it list the JDK like any other on the machine — a JDK you installed should
be discoverable as one, not only through jolta. (Homebrew has the same gap for its
formula JDKs and asks you to symlink them by hand; jolta does it for you.)
Registration is a symlink into ~/Library/Java/JavaVirtualMachines, the
per-user registry — no sudo, and the system-wide
/Library/Java/JavaVirtualMachines is never touched. Uninstall,
upgrade and prune remove the entry again, so the registry doesn't rot. It is
best-effort throughout: an unwritable or unreachable registry is skipped silently
rather than failing the install.
Installs are defensive: concurrent installs of the same version serialize on a
lock (parallel builds can fire many shims at once); a published
.sha256 sidecar is verified when present; the extracted JVM must
actually execute before it is promoted — a wrong-architecture or wrong-libc download
fails at install time with a clear message instead of a cryptic loader error at first
use. On Apple Silicon, majors that never shipped arm64 builds (8, 11) automatically
retry as x64 and run under Rosetta. Alpine/musl systems get musl builds from the
distros that publish them (temurin, zulu, liberica).
jolta catalog [x]
What's published, before you install it. Aliases: search, available, ls-remote.
jolta catalog # latest per distro, plus the Java release universe
jolta catalog 21 # each distro's latest 21.x
jolta catalog temurin # Temurin's latest per major (LTS + current)
jolta catalog temurin@21.0 # @v is a prefix filter: every published 21.0.x
Installed versions are marked ✓. Results cache for 24 hours
(JOLTA_CACHE_TTL_HOURS tunes it); --fresh refetches. Under a
mirror, the catalog reads the mirror's own metadata.
jolta update · jolta upgrade [spec]
Point-release maintenance for jolta-managed JDKs. Alias: outdated.
update reports newer point releases without downloading anything — the
latest version is learned from vendor metadata (or the redirect chain's versioned
filename, or mirror metadata). upgrade fetches the newer build, switches
resolution to it, and removes the superseded install — unless a project pin still
references the old build, in which case it is kept and reported
(kept temurin-21.0.1 (pinned by /work/api/.java-version)). Majors are
independent: jolta upgrade 17 never touches 21. System JDKs are left to
their own package managers; because resolution picks the highest build of a major, a
brew upgraded JDK takes effect automatically.
jolta prune [spec] [--dry-run]
Reclaim disk without breaking anything.
Two tiers. Within each distro+major, the newest build is kept and older builds are
removed. Then entire majors go when they are non-LTS, superseded by a higher installed
major, and not the current feature release — 22 after 24
arrived, say. Both tiers are pin-aware: jolta remembers every project
pin it has ever resolved, re-reads those files at prune time (the files are the truth;
the memory is just an index), and keeps anything still referenced, telling you which
file protected it. A spec scopes everything: jolta prune 17,
jolta prune temurin@17. -n/--dry-run previews.
jolta uninstall <spec>
Remove one jolta-managed JDK.
Takes the same spec forms as install (25, temurin@25, or a
full name like temurin-25.0.3); an ambiguous spec lists the minimal
commands that would disambiguate. Removes only from $JOLTA_HOME/jdks —
never a system JDK — then clears caches and re-shims so tools provided only by the
removed JDK disappear from the shim set.
jolta vendor [name | --unset]
Prefer a distro without editing every repo.
Sets the preferred vendor for vendorless specs (also settable per-shell via
JOLTA_VENDOR). Resolution then prefers that vendor's builds — even over a
higher build of another distro: with vendor corretto, a pin of
11 picks Corretto 11.0.31 over Temurin 11.0.32. Vendorless installs and
auto-installs fetch the preference too. Explicit specs
(temurin@21, .sdkmanrc suffixes) always win, and one
precedence ladder is shared by every command, so there is nowhere the preference
applies inconsistently.
Mirrors
jolta mirror sync · jolta mirror verify
Build and maintain an offline JDK mirror in one command.
jolta mirror sync /srv/jdks --vendors temurin,corretto --majors 8,11,17,21,25
jolta mirror verify /srv/jdks # re-hash everything (cron this)
jolta mirror sync /srv/jdks --from file:///staging/jdks # promote a mirror
sync downloads every platform's asset (macOS arm64/x64, Linux
x64/arm64, Windows x64), writes .sha256 sidecars — which installs verify —
and emits the metadata files below, derived from the archives themselves.
verify re-hashes every asset and exits non-zero on corruption.
--from reads another mirror instead of the vendors, for staging→production
promotion. Serve the directory with any static file server, S3 bucket, or Artifactory
repo, and point clients at it with JOLTA_DOWNLOAD_BASE.
Mirror layout & metadata
{base}/{vendor}/{version}/{os}-{arch}.{ext} # assets: temurin/21/linux-x64.tar.gz
{base}/{vendor}/{version}/{os}-{arch}.{ext}.sha256
{base}/{vendor}/{major}/latest # metadata: contains e.g. 21.0.4
{base}/{vendor}/index.txt # one version per line
{base}/lts # contains e.g. 21
version in an asset path is whatever spec clients request — a major
like 21 or an exact 21.0.4. The metadata is optional but
powerful: with it, update/upgrade know what's newest,
catalog works, and a fresh machine's first java bootstraps
the mirror's own LTS — all fully air-gapped. Without it, installs still work; jolta
just can't tell you what's newest. doctor reports whether your mirror has
metadata.
Integration
jolta setup
One-time install: shims, profile, hook.
Installs the binary into $JOLTA_HOME/bin, generates shims, and adds
two marked blocks to your shell profile: the PATH prepend and the
JAVA_HOME hook line. A Homebrew-installed jolta is linked
through brew's stable opt path, so brew upgrade jolta
updates the shims and everything else with no further action; any other binary
(curl installer, source build) is copied, so the downloaded file or
checkout can be deleted afterwards. Idempotent — re-running from a newer binary
upgrades the install and never duplicates profile lines. On Windows, setup edits the
user PATH in the registry itself (REG_EXPAND_SZ and
%VAR% entries preserved, the change broadcast so new terminals see it)
and adds the hook to every PowerShell profile — with the manual steps printed only
as a fallback if editing fails. implode undoes both.
jolta hook [zsh|bash|fish|powershell]
Keeps JAVA_HOME in sync; everything else works without it.
Prints shell code (installed by setup, or
eval "$(jolta hook zsh)" yourself) that re-exports JAVA_HOME
when you change directory and whenever jolta's state changes — every mutating
command bumps a stamp file the hook checks before each prompt, so
jolta pin 17 takes effect at the very next prompt with no
cd, and the shell's command cache is rehashed after installs. If a pin
can't be satisfied the hook unsets JAVA_HOME so builds fail
loudly rather than on the wrong JDK. The hook prints nothing to stdout, survives
set -u/set -e, and works on macOS's ancient
/bin/bash 3.2. The fish variant reacts to PWD changes and
the fish_prompt event; setup writes fish-syntax blocks to
~/.config/fish/config.fish when fish is your shell.
jolta completions [zsh|bash|fish]
Tab-completion that can't drift from the binary.
Prints a completion script for your shell (default: the one you're running).
Commands and their descriptions come from the same tables that power
jolta help, distros from the vendor registry, and tools for
which from the shim set — regenerated on every print, so the script
always matches the binary that printed it. uninstall,
upgrade, and prune complete against what is
actually installed (live via jolta jdks), and spec positions
offer majors, lts/latest, and
distro@major combos.
eval "$(jolta completions zsh)" # ~/.zshrc, after compinit (or save as _jolta on your fpath)
eval "$(jolta completions bash)" # ~/.bashrc
jolta completions fish > ~/.config/fish/completions/jolta.fish
jolta toolchains [--write]
Hand Maven and Gradle the jolta-managed JDK set.
Maven Toolchains and Gradle's jvmToolchain(...) resolve JDKs on their
own — Gradle will even download one behind your back — silently bypassing
jolta. This command generates a Maven toolchains.xml from your installed
JDKs instead: one <toolchain> per distro+major (newest build wins),
with version and vendor in <provides>.
jolta toolchains # print the XML, e.g. to merge by hand
jolta toolchains --write # manage ~/.m2/toolchains.xml directly
--write is marker-guarded: it refuses to overwrite a
toolchains.xml it didn't generate, so a hand-written file is never lost.
Re-run it after installing or removing JDKs. For Gradle, the matching
org.gradle.java.installations.paths line for
~/.gradle/gradle.properties is printed alongside (on stderr, so
redirecting the XML stays clean), together with
org.gradle.java.installations.auto-download=false — otherwise a
project using a toolchain resolver (the common foojay convention plugin)
downloads its own JDK whenever nothing matches, and jolta stops being the
source of truth.
jolta reshim
Regenerate the shim set.
Rebuilds $JOLTA_HOME/shims: the full baseline JDK toolset (shimmed even
before any JDK is installed, so the very first java can bootstrap), plus
every extra executable your installed JDKs ship — GraalVM's gu and
native-image, for instance. Bundled language runtimes
(node, python, ruby…) are deliberately
not shimmed so jolta never hijacks your other version managers. Runs
automatically after installs and uninstalls; run it manually after installing a JDK
outside jolta.
jolta doctor [--fix]
Diagnose the whole chain; exit 0 means healthy.
Checks that shims exist and win on PATH (naming any binary shadowing
them), that JAVA_HOME matches what the current directory should resolve
(catching stale exports that would mislead Maven/Gradle), that a Homebrew-linked
install still points at a live binary (a brew uninstall without
jolta implode leaves it dangling), that the resolved
java matches your CPU architecture, that no ~/.mavenrc sets
JAVA_HOME behind jolta's back, mirror reachability and metadata when
JOLTA_DOWNLOAD_BASE is set, and prints the pin and preferred vendor in
effect. CI-friendly: the exit code is the verdict.
--fix then repairs what is mechanically safe — rebuilds broken or
missing shims, re-adds missing jolta profile blocks — and reports what it did.
Anything requiring judgment (a stale JAVA_HOME export you wrote
yourself, a ~/.mavenrc override, a dangling Homebrew link) stays
diagnose-only: jolta never edits profile lines it didn't write.
jolta implode
Complete, surgical uninstall.
Removes $JOLTA_HOME and the marked jolta blocks from your shell
profile — after a confirmation prompt (--yes skips) that lists exactly
which jolta-installed JDKs will be deleted. System JDKs and your projects'
.java-version files are never touched. On macOS the
/usr/libexec/java_home entries jolta registered are removed too, so
nothing of jolta's is left behind — entries you created yourself stay, since only
symlinks named jolta-*.jdk that resolve inside
$JOLTA_HOME/jdks are ever removed.
Version specs
A spec is a version with an optional distro, joined by @ or
- (case-insensitive): 21, 21.0.4,
corretto@21, graalvm-25, temurin@8.
- Majors (
21) match the highest installed build of that major, subject to the precedence ladder. - Exact versions (
21.0.4) mean exact — never satisfied by some other 21.x. Comparison is numeric, so21.0.4+9matches an installed21.0.4and vice versa; build metadata after+is insignificant. - Legacy notation works:
1.8is major 8, and1.8.0_392-style update numbers order correctly. - Early access: a spec ending
-eamatches only EA builds; GA specs prefer GA and use an EA build only when nothing else provides the major. - Keywords:
ltsandlatest(alsocorretto@lts) expand to the current LTS / feature major — resolved against the live release index atpin/default/installtime, then written concrete, so nothing floats afterwards.
Resolution precedence
Two ladders, evaluated in order. First, which spec applies here:
- the nearest
.java-versionor.sdkmanrc, walking up from the current directory (.java-versionwins inside one directory) - the global
jolta default - the system default JDK
The project pin is authoritative — there is deliberately no environment-variable override: an export lingering in some forgotten profile would silently beat every pin on the machine.
Then, which installed JDK satisfies the spec: an explicit distro in the spec filters hard; otherwise preferred-vendor GA > any GA > preferred-vendor EA > any EA, highest build within the winning tier — the preferred vendor beats even a higher build of another distro. Version ties prefer jolta-managed JDKs over system ones, then break alphabetically, so the answer is always deterministic.
Pinning
The pin is a plain .java-version file (compatible with jenv and asdf) —
commit it to your repo:
jolta pin 21 # this project uses Java 21
jolta default 21 # global fallback when a project has no pin
Resolution order: nearest .java-version walking up →
jolta default → system default JDK.
On a machine with no JDK at all, the first java run installs the latest
LTS (Temurin) and sets it as your default.
A major pin (21) matches the highest installed build of that major.
An exact pin (21.0.2) means exact — never silently substituted, and
auto-installed precisely. .sdkmanrc files (java=21.0.2-tem)
are honored with vendor mapping when no .java-version claims the
directory, and resolve to SDKMAN's own install when it has one — including
identifiers that don't spell the version the JDK reports, like Corretto's
21.0.5.11.1-amzn (release says 21.0.5) or a
JDK 8 8.0.432-tem (1.8.0_432). Neither tool ends up
with a duplicate copy. jolta catalog (alias search) shows what every distro
publishes — latest per distro, per-major comparisons, and @v prefix
filters like temurin@21.0 — with installed versions marked.
Distros
A spec can name a JDK distribution as distro@version or
distro-version:
jolta pin corretto@21 # this project wants Amazon Corretto 21
jolta install graalvm@25 # explicitly fetch a GraalVM JDK
- Downloadable:
temurin(default),corretto,graalvm,oracle,zulu,liberica,sapmachine,graalce(GraalVM Community). - Recognized when matching: those eight plus
openjdk(Homebrew),semeru(IBM OpenJ9),microsoft, anddragonwell. - A distro-qualified pin is strict —
corretto-21picks an installed Corretto 21 over a Homebrew OpenJDK 21, and auto-installs Corretto if missing. A bare21matches any distro.
Point releases: jolta update checks jolta-managed JDKs against the
vendors (via redirect filenames — no downloads) and jolta upgrade
fetches newer builds and prunes superseded ones. Homebrew/system JDKs stay with
their own package managers; resolution picks the highest build of a major, so
their upgrades apply automatically.
JAVA_HOME, Maven & Gradle
Two mechanisms keep JAVA_HOME correct:
- Shims export it for everything they exec.
- The shell hook (installed by setup; zsh, bash, fish, and
PowerShell) re-resolves it whenever the pin in effect changes — covering tools
that read
JAVA_HOMEdirectly, like Maven, Gradle, and IDEs launched from a terminal. That is more thancd: agit checkoutthat swaps.java-versionunder a shell that never moved applies at the next prompt too, as do installs and defaults from another shell. The per-prompt check is shell builtins only —joltaruns only on a real change. If a pin can't be satisfied the hook unsetsJAVA_HOME, so builds fail loudly rather than silently using the wrong JDK.
Don't export JAVA_HOME manually in your profile — the hook owns it. In
scripts and CI, use jolta exec mvn ... or
eval "$(jolta env)". jolta doctor flags stale values.
Multi-JDK builds (Maven Toolchains, Gradle jvmToolchain) get the
jolta-managed set via jolta toolchains
instead of downloading their own JDKs behind your back.
Where JDKs are found
Every invocation sees the union of: jolta's own installs
($JOLTA_HOME/jdks); macOS's /usr/libexec/java_home -V
(which covers /Library/Java/JavaVirtualMachines, Homebrew casks, and
IntelliJ-downloaded JDKs); Homebrew formula kegs
(openjdk, openjdk@21, …) read straight from
$HOMEBREW_PREFIX/opt, since brew never registers those with
java_home — it only prints a caveat suggesting you symlink them into
/Library/Java yourself, so they would otherwise be invisible;
/usr/lib/jvm; SDKMAN's
~/.sdkman/candidates/java — or wherever $SDKMAN_DIR /
$SDKMAN_CANDIDATES_DIR point (vendor read from the directory suffix,
Contents/Home bundles handled, JDK 8-era installs without a
release file identified by directory name); Windows vendor directories
under Program Files; and JAVA_HOME_<major>_<arch>
environment variables (as set by GitHub Actions runners). No registration step —
if it's on the machine, it resolves.
The reverse direction holds too, on macOS: jolta's own installs are registered
with /usr/libexec/java_home (see
install), so a JDK jolta downloaded shows up
for tools that never heard of jolta. Note that registry is global and has no notion
of a project pin — java_home answers the same in every directory,
whatever a .java-version says. It tells you what is on the
machine; jolta current tells you what is
active here.
Environment variables
| Variable | Effect |
|---|---|
JOLTA_HOME | State root (default ~/.jolta); a leading ~ is expanded |
JOLTA_VENDOR | Preferred distro for vendorless specs (same as jolta vendor) |
JOLTA_NO_AUTO_INSTALL | Set to disable all on-demand downloads; missing JDKs become clean errors |
JOLTA_DOWNLOAD_BASE | Mirror base URL (https:// or file://) replacing every vendor endpoint |
JOLTA_FRESH / --fresh | Drop the remote-metadata cache for this invocation, then re-cache |
JOLTA_CACHE_TTL_HOURS | Remote-metadata cache lifetime (default 24) |
JAVA_HOME_<major>_* | Registers an extra JDK for discovery (CI images set these) |
HOMEBREW_PREFIX | Where Homebrew kegs are scanned for JDKs; authoritative when set, else /opt/homebrew, /usr/local, Linuxbrew |
NO_COLOR, TERM=dumb | Plain output; piped output is always uncolored |