<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: captivator</title>
    <description>The latest articles on DEV Community by captivator (@captivator).</description>
    <link>https://dev.to/captivator</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4229182%2Fd4721506-9efe-4de0-bc90-48d0367fc973.png</url>
      <title>DEV Community: captivator</title>
      <link>https://dev.to/captivator</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/captivator"/>
    <language>en</language>
    <item>
      <title>How to switch Java versions on Windows with one command (no more editing JAVA_HOME)</title>
      <dc:creator>captivator</dc:creator>
      <pubDate>Thu, 05 Nov 2026 19:49:43 +0000</pubDate>
      <link>https://dev.to/captivator/how-to-switch-java-versions-on-windows-with-one-command-no-more-editing-javahome-5d07</link>
      <guid>https://dev.to/captivator/how-to-switch-java-versions-on-windows-with-one-command-no-more-editing-javahome-5d07</guid>
      <description>&lt;p&gt;I kept juggling JDK 8, 21 and 25 on Windows. Every time a project needed a different one, I was back in the environment variables dialog, editing &lt;code&gt;JAVA_HOME&lt;/code&gt; and then checking &lt;code&gt;PATH&lt;/code&gt; to make sure the right &lt;code&gt;java.exe&lt;/code&gt; won.&lt;/p&gt;

&lt;p&gt;So I made &lt;strong&gt;jfs&lt;/strong&gt; (see what it stands for at the end), a Windows Java version manager for Java fans.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/qaptivator" rel="noopener noreferrer"&gt;
        qaptivator
      &lt;/a&gt; / &lt;a href="https://github.com/qaptivator/jfs" rel="noopener noreferrer"&gt;
        jfs
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Windows Java version manager for Java fans. Switch JDKs with one command. No shims, no shell hooks, no background service.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;&lt;a href="https://jfs.acdev.app/" rel="nofollow noopener noreferrer"&gt;jfs&lt;/a&gt;&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;
  &lt;a rel="noopener noreferrer" href="https://github.com/qaptivator/jfs/web/assets/logo.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fqaptivator%2Fjfs%2FHEAD%2Fweb%2Fassets%2Flogo.png" alt="jfs logo, a blue coffee cup with orange steam" width="160"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stop editing &lt;code&gt;JAVA_HOME&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;jfs (Java Fucking Sucks) is a Windows Java version manager. Point it at JDKs you already have, then switch with one command. Your actual &lt;code&gt;JAVA_HOME&lt;/code&gt; environment variable stays unchanged. Behind the scenes, a directory junction moves. No shims, no shell hooks, no background service.&lt;/p&gt;

&lt;div class="highlight highlight-source-powershell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;irm https:&lt;span class="pl-k"&gt;//&lt;/span&gt;jfs.acdev.app&lt;span class="pl-k"&gt;/&lt;/span&gt;install.ps1 &lt;span class="pl-k"&gt;|&lt;/span&gt; iex&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Git Bash:&lt;/p&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;curl -fsSL https://jfs.acdev.app/install.ps1 &lt;span class="pl-k"&gt;|&lt;/span&gt; powershell.exe -NoProfile -ExecutionPolicy Bypass -Command -&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;cmd:&lt;/p&gt;

&lt;div class="highlight highlight-source-batchfile notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;powershell&lt;/span&gt; -NoProfile -ExecutionPolicy Bypass -Command &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;irm https://jfs.acdev.app/install.ps1 | iex&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Or run &lt;a href="https://jfs.acdev.app/install.bat" rel="nofollow noopener noreferrer"&gt;install.bat&lt;/a&gt;.&lt;/p&gt;

&lt;div class="highlight highlight-source-powershell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;PS C:\Users\you&lt;span class="pl-k"&gt;&amp;gt;&lt;/span&gt; java &lt;span class="pl-k"&gt;-&lt;/span&gt;version
openjdk version &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;25.0.4.1&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt; &lt;span class="pl-c1"&gt;2026&lt;/span&gt;&lt;span class="pl-k"&gt;-&lt;/span&gt;&lt;span class="pl-c1"&gt;08&lt;/span&gt;&lt;span class="pl-k"&gt;-&lt;/span&gt;&lt;span class="pl-c1"&gt;18&lt;/span&gt; LTS
OpenJDK Runtime Environment Temurin&lt;span class="pl-k"&gt;-&lt;/span&gt;&lt;span class="pl-c1"&gt;25.0&lt;/span&gt;.&lt;span class="pl-c1"&gt;4.1&lt;/span&gt;&lt;span class="pl-k"&gt;+&lt;/span&gt;&lt;span class="pl-c1"&gt;1&lt;/span&gt; (build &lt;span class="pl-c1"&gt;25.0&lt;/span&gt;.&lt;span class="pl-c1"&gt;4.1&lt;/span&gt;&lt;span class="pl-k"&gt;+&lt;/span&gt;&lt;span class="pl-c1"&gt;1&lt;/span&gt;&lt;span class="pl-k"&gt;-&lt;/span&gt;LTS)
OpenJDK &lt;span class="pl-c1"&gt;64&lt;/span&gt;&lt;span class="pl-k"&gt;-&lt;/span&gt;Bit Server VM Temurin&lt;span class="pl-k"&gt;-&lt;/span&gt;&lt;span class="pl-c1"&gt;25.0&lt;/span&gt;.&lt;span class="pl-c1"&gt;4.1&lt;/span&gt;&lt;span class="pl-k"&gt;+&lt;/span&gt;&lt;span class="pl-c1"&gt;1&lt;/span&gt; (build &lt;span class="pl-c1"&gt;25.0&lt;/span&gt;.&lt;span class="pl-c1"&gt;4.1&lt;/span&gt;&lt;span class="pl-k"&gt;+&lt;/span&gt;&lt;span class="pl-c1"&gt;1&lt;/span&gt;&lt;span class="pl-k"&gt;-&lt;/span&gt;LTS&lt;span class="pl-k"&gt;,&lt;/span&gt; mixed mode&lt;span class="pl-k"&gt;,&lt;/span&gt; sharing)
PS C:\Users\you&lt;span class="pl-k"&gt;&amp;gt;&lt;/span&gt; jfs&lt;/pre&gt;…
&lt;/div&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/qaptivator/jfs" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;h2&gt;
  
  
  The trick
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;JAVA_HOME&lt;/code&gt; is set &lt;strong&gt;once&lt;/strong&gt;, to &lt;code&gt;%LOCALAPPDATA%\jfs\current&lt;/code&gt;. That path is an NTFS directory junction. When you run &lt;code&gt;jfs use 21&lt;/code&gt;, jfs retargets the junction to the JDK you picked. &lt;code&gt;JAVA_HOME&lt;/code&gt; itself never changes.&lt;/p&gt;

&lt;p&gt;Because of that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;every new &lt;code&gt;java&lt;/code&gt; process follows the JDK you picked&lt;/li&gt;
&lt;li&gt;already-open terminals don't need a restart (a &lt;code&gt;java&lt;/code&gt; process that is already running keeps its old JDK, of course)&lt;/li&gt;
&lt;li&gt;there are no shims, no shell hooks, and no background service&lt;/li&gt;
&lt;li&gt;it doesn't need admin rights or Developer Mode, because junctions (&lt;code&gt;mklink /J&lt;/code&gt;) don't, unlike symlinks&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;irm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://jfs.acdev.app/install.ps1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;iex&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;jfs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;21&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C:\path\to\jdk-21"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;jfs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C:\path\to\jdk8"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;jfs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;jfs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;21&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-version&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;jfs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;jfs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;8&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;jfs add&lt;/code&gt; checks that &lt;code&gt;bin\java.exe&lt;/code&gt; exists in the directory and records the path in a small &lt;code&gt;config.json&lt;/code&gt;. The first JDK you add becomes active. &lt;code&gt;jfs rm&lt;/code&gt; only forgets a version and never touches files on disk.&lt;/p&gt;

&lt;h2&gt;
  
  
  It does not download JDKs
&lt;/h2&gt;

&lt;p&gt;This is on purpose, at least for now. Many of us already have several JDKs lying around: Gradle's cached toolchains, the ones bundled with IDEs, Adoptium installs. jfs lets you reuse those instead of downloading yet another copy. I was inspired by nvm, but jfs only switches, it doesn't install.&lt;/p&gt;

&lt;h2&gt;
  
  
  Things worth knowing
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Windows only.&lt;/strong&gt; The switching mechanism is an NTFS junction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local NTFS paths only.&lt;/strong&gt; A junction can't target a network share.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;System PATH beats user PATH.&lt;/strong&gt; Windows searches the system &lt;code&gt;PATH&lt;/code&gt; first, so if a system directory contains its own &lt;code&gt;java.exe&lt;/code&gt;, that one wins. The installer prints those directories when it finds them. An entry that is literally &lt;code&gt;%JAVA_HOME%\bin&lt;/code&gt; is fine, since it follows jfs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Uninstall carefully.&lt;/strong&gt; &lt;code&gt;Remove-Item -Recurse&lt;/code&gt; follows the &lt;code&gt;current&lt;/code&gt; junction and can delete the JDK it points at. Remove the junction first with &lt;code&gt;cmd /c rmdir "%LOCALAPPDATA%\jfs\current"&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Under the hood
&lt;/h2&gt;

&lt;p&gt;It's written in Rust and MIT licensed. All state is one &lt;code&gt;config.json&lt;/code&gt; plus the junction, living in &lt;code&gt;%LOCALAPPDATA%\jfs&lt;/code&gt; (or wherever &lt;code&gt;JFS_DIR&lt;/code&gt; points).&lt;/p&gt;

&lt;h2&gt;
  
  
  Feedback
&lt;/h2&gt;

&lt;p&gt;I'd especially like to hear about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PATH edge cases I haven't hit&lt;/li&gt;
&lt;li&gt;how IDEs and Gradle daemons behave after a switch, since some tools cache &lt;code&gt;JAVA_HOME&lt;/code&gt; at startup&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Source, releases and issues: &lt;a href="https://github.com/qaptivator/jfs" rel="noopener noreferrer"&gt;github.com/qaptivator/jfs&lt;/a&gt;&lt;br&gt;
Landing page: &lt;a href="https://jfs.acdev.app" rel="noopener noreferrer"&gt;jfs.acdev.app&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(and, yes, it stands for "Java Fucking Sucks")&lt;/p&gt;

</description>
      <category>java</category>
      <category>windows</category>
      <category>rust</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
