Jolta
Manual

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.

CommandWhat it does
jolta setupInstall 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 updateCheck 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 jdksMachine-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 envPrint export lines for eval
jolta homePrint 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|verifyBuild or re-hash an offline JDK mirror (JOLTA_DOWNLOAD_BASE)
jolta reshimRegenerate shims after installing JDKs elsewhere
jolta doctor [--fix]Diagnose PATH / shim / JAVA_HOME problems; --fix repairs the safe parts
jolta implodeUninstall 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.

Resolution precedence

Two ladders, evaluated in order. First, which spec applies here:

  1. the nearest .java-version or .sdkmanrc, walking up from the current directory (.java-version wins inside one directory)
  2. the global jolta default
  3. 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

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:

  1. Shims export it for everything they exec.
  2. 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_HOME directly, like Maven, Gradle, and IDEs launched from a terminal. That is more than cd: a git checkout that swaps .java-version under 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 — jolta runs only on a real change. If a pin can't be satisfied the hook unsets JAVA_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

VariableEffect
JOLTA_HOMEState root (default ~/.jolta); a leading ~ is expanded
JOLTA_VENDORPreferred distro for vendorless specs (same as jolta vendor)
JOLTA_NO_AUTO_INSTALLSet to disable all on-demand downloads; missing JDKs become clean errors
JOLTA_DOWNLOAD_BASEMirror base URL (https:// or file://) replacing every vendor endpoint
JOLTA_FRESH / --freshDrop the remote-metadata cache for this invocation, then re-cache
JOLTA_CACHE_TTL_HOURSRemote-metadata cache lifetime (default 24)
JAVA_HOME_<major>_*Registers an extra JDK for discovery (CI images set these)
HOMEBREW_PREFIXWhere Homebrew kegs are scanned for JDKs; authoritative when set, else /opt/homebrew, /usr/local, Linuxbrew
NO_COLOR, TERM=dumbPlain output; piped output is always uncolored