asdf-java: managing JDK versions across Bash, zsh, fish, nushell and xonsh
A Java plugin for asdf-vm.
At a glance
- What is it?
- asdf-java is the Java plugin for the asdf version manager, and its job is turning a stream of JDK releases from several vendors into something asdf can install and switch between. It also does the part plugins often skip, wiring JAVA_HOME into your shell, and it handles the awkward cases, early access builds, Apple Silicon versus Rosetta, and the macOS java_home integration.
- Who is it for?
- Adopt asdf-java if you already use asdf and need to move between JDK builds from several vendors on a per-project basis, since it handles the version listing, the install and the shell integration in one place and covers the awkward cases of early access builds and Apple Silicon. Do not adopt it if you manage Java with another tool or need a vendor that does not appear in asdf list all java, since the candidate set is what the plugin knows about.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly Shell, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What the plugin is and what it needs
asdf-java is a Java plugin for asdf, the version manager, and it is a small thing by design. The whole job is to answer three questions: which JDK builds exist, how do I install one, and how do I make a particular one the active one.
The requirements list is where the shape of the project shows. You need bash v5.0, curl, sha256sum but only on Linux, unzip, and jq but only for updating the release data. Two of those five are conditional, and the conditions are the interesting part. sha256sum is a GNU coreutils tool and is not present by default on macOS, where the equivalent is shasum, so the plugin avoids the Linux-only path when it detects it is not on Linux. jq is needed only when you refresh the release data, which is a maintenance operation rather than a daily one, so an ordinary user never needs it.
The plugin is written in shell. The primary language listed for the project is Shell, and the top-level listing shows it: a bin/ directory, an update_data.bash script, and shell files for setting the Java home.
What the plugin does not do is manage the build or the project. The README points at asdf itself for instructions on how to install and manage versions of Java, which is the division of labour asdf plugins follow: the plugin knows how to fetch and unpack a runtime, and asdf knows how to record which runtime a project wants.
The primary language is Shell, the licence is MIT, the default branch is master, and the last push was on 2026-10-22 with the project not archived. The repository publishes no GitHub releases, so the plugin is consumed as a git clone of the master branch, which is how asdf plugins normally work.
Installing the plugin and the first commands
Adding the plugin is one command:
asdf plugin add java https://github.com/halcyon/asdf-java.gitThat is the whole installation. asdf clones the repository into its own plugins directory and the plugin is available immediately, with no separate build step, which is a direct consequence of it being a shell script rather than a compiled tool.
From there the first command is to see what is on offer:
asdf list all javaThe list of candidates is not limited to one vendor, which matters more than it sounds. A developer moving between projects may need an old JDK from one distribution for a legacy service and a current one from another, and a plugin that only tracked a single vendor's download site would force a manual install for half of those cases.
The naming convention carries the vendor and the version together, so a candidate is identified by a string like adoptopenjdk-11.0.16+8. The version part includes the build number, and the plus sign is a real character that has to be quoted in a shell, which is why every example in the README quotes the candidate.
Installing is then the obvious next step:
asdf install java adoptopenjdk-11.0.16+8And selecting an installed candidate for use is a set command. There are two forms, and the distinction between them is the one thing a new user should get right. The global form, with the u flag, sets the version for the user:
asdf set -u java adoptopenjdk-11.0.16+8And the local form sets it for the current directory only:
asdf set java adoptopenjdk-11.0.16+8So the global and local settings are separate versions, and the local one wins inside a directory that has it. This is how a repository can pin its own JDK while your shell defaults to something else, and it is the same model as any other asdf language plugin, so the knowledge transfers.
The latest tag, and the .tool-versions file
The plugin supports a shorthand for the common case where you want the newest release of a major version and do not care about the patch number:
asdf install java latest:adoptopenjdk-11The tag has the form latest: followed by the vendor and major version, and the README notes that the latest tag will resolve to the most recent version. It works for both installing and setting, whether global or local.
The interesting part is what happens when you set it locally. The README says running asdf local java latest:adoptopenjdk-11 will result in the following, and shows the .tool-versions file:
$ cat .tool-versions
java adoptopenjdk-11.0.16+8So the tag does not persist as a tag. The resolution happens once, and the .tool-versions file records the concrete version, java adoptopenjdk-11.0.16+8. That is the right behaviour, and it is worth understanding why. A file that records latest would mean every new developer, and every CI run, would pick up a different JDK, and the whole point of pinning a version in a project is that it does not move under you.
The .tool-versions file is asdf's own mechanism, shared across all its language plugins, and it is what a colleague or a CI job reads to know which JDK the project expects. The cost is that the tag is a convenience for the moment you set the version, and after that you are on an exact pin.
That is also why the plugin caches the available versions. Listing candidates hits the network, and doing that on every command would make asdf slow, so the data is cached and refreshed only when needed.
Early access builds and the cache
The Early Access section covers builds that let you prepare for the next version of the JDK but are not stable releases, and the mechanism is a setting in the .asdfrc file called java_release_type with three supported values.
The ga value means you can only install GA, General Availability or stable, releases, and it is the default. The ea value means you can only install EA, Early Access or unstable, builds. And all means you can install both GA and EA builds.
That default of ga is worth noting, because it means an early access build never appears unless you ask for it. A developer experimenting with a new JDK has to opt in, and the opt-in is a file in your home directory rather than a flag on each command, so it applies to every project.
The catch is that changing the setting does not change what you can see immediately, and the README explains why with a cache step. After setting the value, for example to all, you need to prune the cache of asdf-java so that asdf will refresh the available versions to install:
export ASDF_JAVA_CACHE_DIR="${TMPDIR:-/tmp}/asdf-java.cache/"
ls -al "$ASDF_JAVA_CACHE_DIR"
rm -rf "$ASDF_JAVA_CACHE_DIR"Three commands: point ASDF_JAVA_CACHE_DIR at the cache location, which defaults to a directory under the temporary directory, list it to see what is there, and remove it. After that asdf list-all java should show the type of builds you set and you can install them.
This is the sort of detail that documentation usually omits and that costs an hour when you hit it. A cached candidate list is a reasonable design, since listing JDK versions means fetching and parsing several vendor download pages, and a cache makes asdf instant. The cost is that a change to the configuration is invisible to the cache, and nothing reminds you to clear it. The ASDF_JAVA_CACHE_DIR variable is also useful on its own, because it lets you point the cache somewhere that survives a reboot or a temporary directory cleanup, which is the case on systems where /tmp is cleared regularly.
JAVA_HOME, and five shells
Setting JAVA_HOME is the part plugins often leave to the user, and asdf-java ships a script for each supported shell.
To set JAVA_HOME in your shell's initialisation, add the following for bash:
`. ~/.asdf/plugins/java/set-java-home.bash`For zsh, use the zsh variant instead:
`. ~/.asdf/plugins/java/set-java-home.zsh`For fish, the fish variant:
`. ~/.asdf/plugins/java/set-java-home.fish`For nushell, the nu variant:
`source ~/.asdf/plugins/java/set-java-home.nu`And for xonsh, the xsh variant:
`source ~/.asdf/plugins/java/set-java-home.xsh`Five shells, five files, and the repository listing confirms it: set-java-home.bash, set-java-home.fish, set-java-home.nu, set-java-home.xsh and set-java-home.zsh all sit at the top level of the repository.
The distinction between dot-sourcing and sourcing is not a typo in the README. The bash, zsh and fish versions are dot-sourced, with a leading dot, because those shells need the variables set in the current shell context rather than in a subshell. The nushell and xonsh versions use the source keyword, because those shells do not have the dot form.
The point of the script is that it reads the current.tool-versions file, works out which JDK is active, and exports JAVA_HOME accordingly. Without it, a build tool that shells out to javac will not find the JDK that asdf has selected, and the failure is confusing: the version manager says one thing and the build says another.
This is the piece of the plugin that makes it usable rather than merely correct; one file per shell covers the shells people actually run.
macOS specifics, Apple Silicon and the java_home integration
The macOS section covers two separate problems, and both are real.
The first is the java_home integration. Some applications in macOS use /usr/libexec/java_home to set the Java home, and setting java_macos_integration_enable to yes on the .asdfrc file enables this integration:
java_macos_integration_enable=yesThe note attached is an important caveat. Not all distributions of Java JDK packages offer this integration, with liberica named as an example, and the option only works for packages that do offer it. So this is a per-distribution capability, not a universal one, and the setting is a no-op on a JDK that does not implement it.
The second is Apple Silicon, and the design here is worth reading closely. On an Apple Silicon Mac you can choose to run either an arm64 JVM natively or an x86_64 JVM under Rosetta translation, and the arch command prints arm64 when you are running natively or x86_64 when you are under Rosetta.
The plugin's behaviour follows from that: when you run asdf list all java, it lists only the VMs which are available for the architecture you are currently running under. So under a native arm64 shell you see the arm64 builds, and under Rosetta you see the x86_64 ones.
That is a sensible default, because a build that lists a candidate you cannot run is a build that fails later. But it also means the candidate list changes with your shell, which can look like the plugin losing versions. The switch is explicit: to change your terminal from native ARM to Rosetta, use arch -x86_64 /bin/zsh.
A JVM that runs under Rosetta rather than natively is also a real performance difference, which makes the choice of shell architecture a decision with a measurable cost rather than a curiosity.
The macOS handling is the part of the plugin that goes beyond a plain downloader, and it is why the README gives it a section of its own rather than folding it into a list of platform notes.
How to evaluate it, and what to check first
There are four things to decide, and the first one decides whether you need this plugin at all.
Do you already use asdf? A plugin only makes sense inside asdf, and if you manage Java with something else, this is a tool for a different workflow. The README's own pointer is to the asdf documentation for how to install and manage versions, which is a reminder that the plugin is one part of a larger system.
Second, which JDK vendors you need. The candidate names carry the vendor, so the list is wider than a single distribution, and you should check that the vendor and major version you need appear in asdf list all java. If they do not, the plugin is not for you, and no amount of configuration will add them.
Third, how you pin versions. The global and local split, with the local one written into.tool-versions, is the model to use: set a sensible global default and pin the exact version per project. Remember that the latest tag resolves once, at the moment you set it, and the file that gets written contains the concrete version, so a project that needs a specific patch level should record it rather than the tag.
Fourth, the shell integration. Add the set-java-home script for your shell to your initialisation file, using the dot form for bash, zsh and fish and source for nushell and xonsh. Skipping this is the most common way to end up with a working asdf and a broken build, and the symptom is a build tool that picks up a different JDK from the one asdf reports.
Two smaller things. If you want early access builds, set java_release_type in.asdfrc and then clear the cache directory, or you will see the old candidate list and wonder whether the setting took effect. And on Apple Silicon, be aware that the candidate list follows the architecture of the shell you are in, so the same project can show different candidates depending on whether you are running natively or under Rosetta.
The maintenance picture is straightforward. The repository publishes no GitHub releases, so the plugin tracks the master branch, and the last push was on 2026-10-22. The CI workflows named in the badges are an update-data workflow that refreshes the release data and a tests workflow, so both the candidate data and the plugin itself are checked automatically, which is the arrangement you want from a tool whose failure mode is a stale download URL.
Editorial conclusion
Adopt asdf-java if you already use asdf and need to move between JDK builds from several vendors on a per-project basis, since it handles the version listing, the install and the shell integration in one place and covers the awkward cases of early access builds and Apple Silicon. Do not adopt it if you manage Java with another tool or need a vendor that does not appear in asdf list all java, since the candidate set is what the plugin knows about. Before you commit, add the set-java-home script for your shell to your initialisation file, because without it your build tool and asdf can disagree about which JDK is active, remember that the latest tag resolves to a concrete version when you set it and the .tool-versions file records that exact version rather than the tag, and clear the cache directory if you change java_release_type to see early access builds.
Frequently asked questions
What is asdf-java?
It is the Java plugin for the asdf version manager, written in shell and MIT licensed. It lets you list candidate JDKs, install them and select a version globally or per directory, using the .tool-versions file that asdf shares across its language plugins.
How do I install the asdf-java plugin?
Run asdf plugin add java https://github.com/halcyon/asdf-java.git. That clones the plugin into asdf's plugin directory and it is available immediately, with no build step, because the plugin is a shell script.
What requirements does asdf-java have?
bash v5.0, curl, sha256sum but only on Linux, unzip, and jq but only for updating the release data. The two conditional requirements are the GNU coreutils checksum tool, which macOS does not provide by default, and jq, which is only needed when refreshing the release data rather than for everyday use.
How do I install early access JDK builds with asdf-java?
Set java_release_type in your .asdfrc file to ga for stable releases only, which is the default, to ea for early access only, or to all for both. After changing it you must prune the cache by pointing ASDF_JAVA_CACHE_DIR at the cache directory and removing it, so that asdf refreshes the available versions.
How do I set JAVA_HOME when using asdf-java?
Add the set-java-home script for your shell to your shell initialisation file: the .bash version for bash, the .zsh version for zsh, the .fish version for fish, the .nu version for nushell and the .xsh version for xonsh. The bash, zsh and fish scripts are dot-sourced, and the nushell and xonsh ones use the source keyword.
How does asdf-java handle Apple Silicon and Rosetta?
When you run asdf list all java it lists only the VMs available for the architecture you are currently running under, so a native arm64 shell shows the arm64 builds and a shell under Rosetta shows the x86_64 ones. You can switch your terminal to Rosetta with arch -x86_64 /bin/zsh. Separately, java_macos_integration_enable set to yes on .asdfrc enables the /usr/libexec/java_home integration, but only for JDK distributions that offer it.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/halcyon-asdf-java)Was this useful?