Skip to main content

How‑to Guide

Common tasks and recipes with javm.

Switch versions

  • Use a specific version or range:
    • javm use temurin@21
    • javm use zulu@~1.8.144 (equivalent to >=1.8.144 <1.9.0)
  • Without arguments, javm reads .java-version in the current directory:
    • javm use
  • Check the current version:
    • javm current
  • Deactivate the current shell session:
    • javm deactivate
    • This removes javm-managed entries from PATH and restores the previous JAVA_HOME when available. It does not remove a JDK.

Use javm uninstall to remove a managed JDK, or javm unlink to remove a link to an external JDK.

Example:

javm use 21
java --version
javm deactivate

Choose how to select a JDK

I want to...Use
Switch the JDK in the current sessionjavm use <selector>
Run one process with a selected JDKjavm exec [--jdk <selector>] <command> [args...]
Choose a JDK for a projectAdd .java-version, then run javm use in that project directory
Set the JDK used in new shellsjavm default <selector>
Create a short name for a selectorjavm alias <name> <selector>

An explicit selector passed to javm use takes precedence over .java-version. Without an argument, javm use reads .java-version from the current directory; it does not automatically switch versions when you enter a directory and it does not fall back to javm default. The default is applied by the shell integration when a new shell starts, and an alias is resolved when its name is used as a selector.

Run tools without shell integration

Use javm exec when an IDE, CI job, launcher, or non-interactive script needs a predictable JDK but cannot or should not modify the parent shell environment:

javm exec ./gradlew build
javm exec mvn test
javm exec --jdk temurin@21 ./gradlew build
javm exec --jdk 21 mvn test

The process receives the selected JDK as JAVA_HOME and with its bin directory first in PATH. The current shell is unchanged, javm init is not required, and the command plus all following arguments are passed directly without a shell parser for native executables. Use --jdk <selector> for an explicit JDK; without it, the selector comes from .java-version in the current directory. On Windows, .cmd and .bat wrappers use COMSPEC because they are command-interpreter scripts.

Set default version for new shells

  • Set the default:
    • javm default temurin@21
  • Effect:
    • Creates default-version in javm config dir.
    • The shell integration applies the saved selector when a new shell starts.

Discover local JDKs

  • Refresh discovery cache:
    • javm discover refresh (forces refresh)
  • List discovered JDKs:
    • javm ls
  • Show detailed information (source, name, vendor, architecture, path):
    • javm ls --details or javm ls -d

Managed vs linked JDKs

A managed JDK is downloaded by javm install and, without --output, stored in javm's managed JDK directory. It can be removed with javm uninstall.

A linked/system JDK already exists outside javm. javm link system@21.0.1 /path/to/jdk checks the expected Java executable and registers a reference to it; it does not copy or install the JDK. javm unlink system@21.0.1 removes only that reference and leaves the external installation in place.

The link is registered in javm's local JDK set, so javm ls --details lists it and javm use or javm which can resolve its system@... name. The JDK itself remains external to javm.

The link name uses an exact version, while later selectors can use ranges:

  • Link an existing (unmanaged) JDK:
    • javm link system@21.0.1 /path/to/jdk
    • The name must begin with system@ and use an exact semantic version.
  • Show a link target:
    • javm link system@21.0.1
  • Remove the link:
    • javm unlink system@21.0.1
  • Use the registered link name with javm use or javm which:
    • javm use system@21
    • javm which system@~21

The path passed to link must contain the platform's Java executable: <path>/bin/java on Linux, <path>/Contents/Home/bin/java on macOS, and <path>/bin/java.exe on Windows. If link reports that Java was not found, check the path and pass the JDK root; on macOS, pass the bundle root rather than its nested Contents/Home.

Aliases

  • Create or update an alias:
    • javm alias project temurin@21
  • Resolve an alias value:
    • javm alias project
  • Delete an alias:
    • javm unalias project
  • Notes:
    • Aliases are stored as *.alias files in the javm config dir.
    • Alias updates automatically relink the jdk\alias symlink to the best match.

List versions (local vs remote)

  • Local/discovered:
    • javm ls
    • Filter by range: javm ls "~21"
  • Show detailed information: javm ls --details
  • Remote (available to install):
    • javm ls-remote (defaults: --os current, --arch current, the effective java.default_distribution, initially temurin, and --latest=major)
    • All distributions: javm ls-remote --distribution=all
    • Filter by range: javm ls-remote zulu@">=1.8 <1.9"
    • Latest per major (default): javm ls-remote --latest=major
    • Latest per minor: javm ls-remote --latest=minor
    • Latest per patch: javm ls-remote --latest=patch

Configure the default distribution

The public configuration key java.default_distribution controls the distribution used by remote operations when no distribution is specified explicitly. Its initial value is temurin.

  • Inspect the effective value:
    • javm config list
    • javm config get java.default_distribution
  • Choose another distribution:
    • javm config set java.default_distribution zulu
  • Restore the initial default:
    • javm config unset java.default_distribution

After setting zulu, for example, javm install 21 and javm ls-remote 21 use Zulu. An explicit selector such as zulu@21, or an explicit --distribution value for ls-remote, overrides the setting. This does not change local discovery or make javm use 21 prefer a particular distribution among discovered JDKs.

Which JDK path

  • Resolve path to a discovered JDK matching a selector:
    • javm which temurin@21
  • Use .java-version if no argument is provided:
    • javm which
  • macOS: use --home to append /Contents/Home so the result is suitable for JAVA_HOME:
    • javm which temurin@21 --home

Coding agents

javm provides an agent skill for coding agents that need to run Java tools with a predictable JDK.

Install it with:

npx skills add felipebz/javm --skill javm -g

The skill guides agents to use project selectors such as .java-version and prefer javm exec <command> [args...] for builds, tests and other non-interactive Java processes.

Troubleshooting

javm: command not found after Unix installation

Symptom: the installer finishes, but the current shell cannot run javm.

Cause and check: install.sh puts the executable in $HOME/.local/bin without editing profiles.

command -v javm
ls -l "$HOME/.local/bin/javm"

Fix: run export PATH="$HOME/.local/bin:$PATH" for the current shell, then add the directory to the appropriate profile if it should persist. See Verify the executable.

javm use does not change the effective Java

Symptom: javm use <selector> succeeds, but java or JAVA_HOME still points to another JDK.

Cause and check: the shell integration was not loaded, the shell was not restarted after setup, or the selected JDK is not the one being resolved.

javm which <selector>
javm current
command -v java

In PowerShell, use Get-Command java instead of command -v java. Load the integration from Shell setup, restart the shell, and run javm use again.

shell integration is not active

Symptom: use or deactivate reports that shell integration is not active.

Cause and check: javm was invoked as a normal child process instead of through the wrapper generated by javm init <shell>.

javm init <shell>

Apply the generated integration using the instructions for your shell in Shell setup.

Invalid or unmatched .java-version

Symptom: javm use without an argument fails, or the project version cannot be selected.

Cause and check: .java-version contains an invalid selector, is empty, or names a version that is not discovered.

cat .java-version
javm ls --details
javm use

In PowerShell, use Get-Content .java-version. Replace the file contents with a valid selector, then install or link a matching JDK. See Selectors and ranges.

Selector has no result

Symptom: javm reports that a selector is not installed.

Cause and check: no discovered JDK matches the requested version, distribution, or range.

javm ls --details
javm ls-remote "temurin@21"

Install a matching JDK, link an existing one, or make the selector more specific, for example temurin@21 instead of 21.

An unexpected distribution is selected

Symptom: an unqualified command such as javm install 21 or javm ls-remote 21 uses a different distribution than expected.

Cause and check: unqualified selectors use the effective java.default_distribution, which is temurin unless changed.

javm config get java.default_distribution
javm ls-remote 21 --distribution all

Use an explicit selector such as temurin@21, or change the setting with javm config set java.default_distribution <distribution>. See Configuration.

A local JDK does not appear in javm ls

Symptom: a JDK installed outside javm is not listed or cannot be selected.

Cause and check: the installation is outside the configured discovery locations, or discovery has not been run again after the installation changed.

javm ls --details
javm discover refresh

For an external JDK at a known path, register it with javm link system@<exact-version> <path>. See link for the expected platform layout.

Symptom: link reports that the Java executable was not found or the path could not be inspected.

Cause and check: the argument is not the JDK root for the current operating system, or its expected Java executable is missing.

ls -l /path/to/jdk/bin/java
# macOS: ls -l /path/to/jdk/Contents/Home/bin/java
# PowerShell: Test-Path 'C:\path\to\jdk\bin\java.exe'

Pass the JDK root to javm link system@<exact-version> <path>. Check the Linux, macOS, or Windows layout in the link reference; on macOS, pass the bundle root rather than Contents/Home.

Symptom: javm uninstall system@... is rejected, or a link is removed when you intended to remove a JDK.

Cause and check: uninstall removes a managed JDK; unlink removes only the reference to an external JDK.

javm ls --details
javm link system@21.0.1

Use javm uninstall <selector> for a managed JDK and javm unlink system@<version> for a linked JDK. See Managed vs linked JDKs.

The default is not applied in a new shell

Symptom: javm default <selector> succeeds, but a new shell does not activate that JDK.

Cause and check: the shell profile is not loading javm init, a static integration was generated before the default was configured, or the default selector has no matching JDK.

javm ls --details
javm init <shell>

Ensure the profile loads the integration, regenerate a static integration if you use one, and start a new shell. See Set default version for new shells and Shell setup.

See all commands with javm --help; use javm --debug for additional diagnostic logs.