Open-source project
ystyle/jvms avatar
ystyle/jvms

jvms switches Windows JDK versions through one symlink

JDK Version Manager (JVMS) for Windows

1,035 stars109 forksGoMIT

At a glance

What is it?
jvms is a Go based JDK version manager for Windows that installs, lists and switches between JDK distributions through a single symlink placed in the system PATH, so a version change reaches every open console at once. It suits Windows developers juggling several JDKs, and it assumes you are willing to initialise a machine-level symlink as administrator.
Who is it for?
jvms fits a Windows developer or build agent that needs several JDK versions installed side by side and switched without editing PATH by hand, and who can run one initialisation as administrator. It does not fit a machine where creating a machine-wide symlink is not allowed, and it is not a per-project toolchain pinner.
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 last received commits 9 days ago.
What is it written in?
Mainly Go, 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

One symlink in the system PATH instead of a shim

The design choice that makes jvms different is stated in the readme as a contrast between two ways of supporting several JDK installations on one machine.

The first approach is to rewrite the system PATH every time the version changes, or to bypass it with a .bat file that mimics the java executable and redirects accordingly. The author's view is that this is hackish and carries quirks as a result.

The second approach is a symlink, with the symlink itself living in the system PATH and its target pointing at the JDK installation you want. The readme notes that this is the approach people usually recommend, until they remember how painful symlinks are on Windows: creating or modifying one requires administrator rights and getting past the UAC prompt, which is the reason such a tool did not exist before.

jvms resolves that by keeping exactly one symlink. The init command puts it in the system PATH, and switching versions means changing the target of that symlink rather than creating a new one. Two consequences follow, and they are the reason to use this tool at all. First, you do not have to run a switch command in every new console window, because the PATH entry is permanent. Second, when you do run a switch, the active JDK is updated across all open console windows, and the choice persists across system reboots. The readme puts it plainly: you only need to use jvms when you want to make a change.

The implementation is Go, which the readme presents as a more structured approach than hacking around a limited batch file, and it has no dependency on other libraries, does not rely on an existing JDK installation, and the module targets go 1.24.2.

Installing it on Windows in five steps

Installation is a manual, administrator-level operation rather than a package manager affair. You download the release archive, decompress it and copy jvms.exe to a path of your choosing. Then you open a command prompt or PowerShell session as administrator, change to the folder where jvms.exe sits, and run the init command, which initializes the configuration file and creates the symlink in the system PATH.

The init command is the step with consequences, and the readme is explicit about why: creating or modifying a symlink on Windows needs administrator rights, so that is where the symlink in the system PATH comes from. Once init has run, the rest of the tool is an ordinary command line utility that does not need elevation for every operation.

The repository layout is small and consistent with a single binary. The root holds main.go, go.mod and go.sum, an internal directory with the implementation, the jdkdlindex.json data file that holds the default version list, and images for the readme. The dependency list shows where the features come from: the charmbracelet bubbletea, bubbles and lipgloss packages provide the interactive terminal interface, codegangsta/cli provides the command structure, the adoptium_jdk_go package covers the Adoptium API, tucnak/store handles local storage, and the pb.v1 package draws the download progress bar.

The project is MIT licensed, the last push was on 2026-10-14, and the release list shows v2.1.20 on 2026-10-09, v2.1.19 on 2026-07-23 and v2.1.18 on 2026-07-05. The default branch is named new, which is worth knowing if you clone it, and the help text printed in the readme still shows version 2.1.17, so the documented output lags the current release.

The command set: rls, install, switch, use

The command surface is small and every command is a verb you would expect. The help output in the readme lists them: tui for the interactive interface, init to initialize the config file, list with the alias ls, install with the alias i, switch with the alias s, use with the alias u, remove with the alias rm, rls to show the versions available for download, config to show or update the configuration, proxy to set a proxy for downloads, and help.

The readme also spells out a short recipe for the common path, and the ordering matters because each command assumes the previous one has run:

bash
jvms rls
jvms install 1.8.0_31
jvms ls
jvms switch 1.8.0_31
jvms use 21.0.4
jvms tui

The distinction between install, switch and use is the useful part. rls lists what can be downloaded, install fetches one, ls lists what is already on the machine, switch activates an installed version, and use does both in one step by installing the version if it is missing and then switching to it. The readme notes that the switch and use commands are run as administrator, and it also recommends running the commands as administrator generally, which follows from the symlink target being a machine-level resource.

The TUI has three forms. jvms tui opens the interactive picker, jvms tui -i opens the install picker, and jvms tui -s opens the switch picker. On a machine with a dozen JDKs installed, the pickers are the argument for the terminal interface existing at all.

One more command exists for networks that need it. The proxy command sets a proxy to use for downloads, which matters on corporate networks where the Adoptium and Azul endpoints are otherwise unreachable.

Four distributions and an index that only lists LTS

The version catalogue is the part of the design with a policy attached. jvms supports four distributions: Oracle JDK, Amazon Corretto, Adoptium which is Eclipse Temurin, and Azul Zulu. The first two are included in the default index, and only their LTS versions. The latter two are reached through an API and fetched dynamically, which is why they can offer more than the default list.

The default index file, jdkdlindex.json in the repository root, contains LTS releases only, and the readme names them: Java 8, 11, 17, 21 and 25. Non-LTS versions, which the readme enumerates from Java 9 through 24, are deliberately excluded, and the reason given is their six month end-of-life support window. The policy is a sensible default for a machine that keeps several JDKs around, since an LTS installation is the one you will still want a year later.

The policy is not a wall, though. The readme lists the ways around it. rls shows every available version including the non-LTS builds from Adoptium and Azul, you can add local JDK installations yourself, and you can point the tool at a custom download server that carries the versions you want. The rls command also refreshes the provider's catalog before it lists anything, so a fresh provider index is fetched rather than a stale local copy being shown.

The author also maintains a simple data feed of JDK versions and says it is free for anyone to use, which is the piece that makes the custom index practical: the same file can be hosted internally, which is also how the documented route for a private download server works.

Local JDKs, index selection and quoting on Windows

Not every JDK you want came from a download index, and jvms handles that case. To add a local installation you copy the JDK home folder into the jvms/store directory, rename the folder to the version string such as 17.0.1, run the list command to confirm it is there, switch to it, and check with java -version.

The naming rules are the fiddly part on Windows. A bare value is always read as a version identifier, never as a row number, and the hash prefix is how you refer to a position in a list. jvms install with a hash and a number selects row 17 of the output of jvms rls, and jvms use does the same for the combined install and switch behaviour, while the switch command works from the installed list, so a hash and a number there selects the first row of the installed output. The readme is specific that explicit indexes must be quoted so that PowerShell passes them through as arguments rather than treating them as something else.

Folder names with spaces work as well, and here quoting is about the shell rather than the syntax. A directory called jdk 17 can be selected with jvms switch "jdk 17" and removed with jvms rm "jdk 17", and the same installed index approach works for it.

There is one more switch form worth knowing. jvms switch with the as_path option activates a JDK by directory rather than by version, which is the way to use a JDK that was never installed through jvms at all.

The configuration file and a setting that was retired

Configuration lives in a file that the init command creates, and the same flag names are used by both init and config, which is a small convenience when you are rewriting the settings:

bash
jvms config --java_home="C:/Program Files/jdk"
jvms config --originalpath=https://example.com/index.json
jvms config --cache_toggle=true --cache_ttl=24h
jvms switch --as_path "C:/Java/jdk-21"

The flags cover the four things you are likely to change. java_home sets the directory the tool works in, originalpath points at a custom index file, which is how a private download server is plugged in, and cache_toggle with cache_ttl control whether the version catalogue is cached and for how long.

Running the config command with no flags prints the current settings, and rls refreshes the provider catalog before listing, so the two commands cover the read side of the configuration.

One entry is a compatibility trap in the other direction. Configuration files that still contain the retired resolution_priority setting continue to load, the setting is ignored, and it is omitted when the configuration is saved again. That is a deliberate migration path, and it also means a stale key sitting in your file is not doing anything, which is worth knowing if you expected it to change how a version is resolved.

For a private catalogue, the readme describes the format as a JSON array of objects with a version and a url, hosted on a static file server such as nginx or apache, and then pointed at with the original path option.

Where a per-project toolchain manager fits better

The alternative approach is the one most Java projects now use, which is a per-project toolchain declaration that the build reads, so each repository states the Java version it expects and the manager installs it if it is missing. The difference in approach is scope. jvms is a machine-level switch: one symlink, one active JDK for every console on the box, changed when you decide to change it. A per-project toolchain is a repository-level declaration: several versions installed at once, and the right one selected by the build depending on where you are.

The jvms approach wins when you want a single interactive shell to move between versions, when you are testing a project against the latest release without uninstalling the stable one, and when the machine is yours rather than shared. The per-project approach wins when a repository has to build the same way on every developer laptop and on CI, where an environment-dependent active version is a liability rather than a feature.

The gap is the one the readme is most candid about, and it is not a gap in the symlink mechanism. The symlink is the part that works, and it works well enough that the author frames the earlier alternative as hackish. What jvms does not do is give you a declarative per-project pin, and it has no equivalent of a version file committed next to the code. It is also Windows only, which is the point of the project rather than a gap in it.

On the practical side, the LTS-only default index and the need for administrator rights are the two constraints to plan around. The first is a policy you can override with a custom index. The second is not, and a machine policy that forbids machine-wide symlinks rules this tool out.

Editorial conclusion

jvms fits a Windows developer or build agent that needs several JDK versions installed side by side and switched without editing PATH by hand, and who can run one initialisation as administrator. It does not fit a machine where creating a machine-wide symlink is not allowed, and it is not a per-project toolchain pinner. Verify first which distributions your index can reach, since the default index is limited to LTS releases from Oracle and Corretto, and that the symlink target is the one java on your PATH actually resolves to after jvms init.

Frequently asked questions

How do I install jvms on Windows?

Download the release archive, decompress it and copy jvms.exe to a path you choose. Then open a command prompt or PowerShell as administrator, change to that folder and run jvms.exe init, which creates the config file and puts the symlink in the system PATH.

Which command do I use to install and switch a JDK with jvms?

jvms rls lists the versions available for download, jvms install fetches one, jvms ls lists what is installed, jvms switch activates an installed version, and jvms use installs the version if it is missing and then switches to it. The TUI offers jvms tui, jvms tui -i and jvms tui -s for picking.

Does jvms support non-LTS Java versions?

The default jdkdlindex.json lists only LTS versions, meaning Java 8, 11, 17, 21 and 25, because non-LTS releases reach end of life within six months. Non-LTS builds from Adoptium and Azul still show up in jvms rls, and you can also add local installations or point the tool at a custom download server.

How do I add a JDK that is already on my machine to jvms?

Copy the JDK home folder into the jvms/store directory and rename the folder to its version string, for example 17.0.1. Then jvms list shows it and jvms switch 17.0.1 activates it, which you can confirm with java -version. Folder names with spaces need quoting, such as jvms switch "jdk 17".

What do the jvms configuration flags do?

The config command shows or updates the settings, and the same flags work with init. They cover java_home for the working directory, originalpath for a custom index file, cache_toggle with cache_ttl for the catalogue cache, and the retired resolution_priority key, which still loads but is ignored and dropped when the file is saved again.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. Releases
  5. ystyle/jvms on GitHub
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.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/ystyle-jvms.svg)](https://hysenlabs.com/projects/ystyle-jvms)

Was this useful?