最近连着维护好几个项目,Java 版本换来换去。一个老系统还在用 Spring Boot 2.x,只能跑 JDK 8;一个内部工具链锁在 JDK 11;新项目想直接用 JDK 21 的虚拟线程。之前我的“办法”是装个新 JDK,然后把项目编译 target 调到 8,结果 javac 版本检查、Lombok 兼容性、Gradle 插件报错一个接一个。后来干脆同时装了几套 JDK,靠手动改 JAVA_HOME 和 PATH 切换,一个上午被“明明改了却没生效”折腾到怀疑人生。
最后我决定写一套 PowerShell 脚本,把 Windows 下的 JDK 多版本管理做成命令行操作:jdk list 看列表,jdk install 17 一键下载安装,jdk use 8 秒切版本。不需要管理员权限,不污染系统 PATH,当前终端立即生效,新开终端也会持久生效。这篇文章把完整脚本和里面的设计思路都摊开来讲,适合所有在 Windows 上做 Java 开发、经常被多个版本绑架的人。
1. 痛点复盘:多版本 JDK 为什么总是相互打架
1.1 我遇到的实际场景
先说你最可能遇到的现场。假设电脑目前装了 JDK 17,日常开发没问题。但是突然要排查一个老项目,这个项目用了 Elasticsearch 7.x 的客户端和旧版 Lombok,必须在 JDK 8 下编译,否则一堆反射警告甚至直接报 UnsupportedClassVersionError。
你下载 JDK 8,装到一个目录,打开系统属性,把 JAVA_HOME 指向 JDK 8,把 PATH 里的 java.exe 也改成 JDK 8 的目录。点确定后,打开新的 cmd 执行 java -version,结果可能还是 17,或者显示 8 了,但 Maven 打包时又告诉你用的是 17。等你不厌其烦地切回 17,另一个在跑的 Elasticsearch 单机节点又起不来了,因为它启动时读的还是老的环境变量。
如果你还要同时维护 Java 8 和 Java 17 的工具链,那就更痛苦:javac、jar、jcmd、jstack 全部由同一个 PATH 决定,换一个等于把所有命令行工具全换一遍。
| 场景 | 需要的 JDK | 不切版本的后果 |
|---|---|---|
| 老系统维护 | 8 | 编译报错,运行期 agent 不兼容 |
| 框架升级到 Spring Boot 3 | 17+ | 启动失败,CGLIB/ASM 版本冲突 |
| 学习 JDK 21 新特性 | 21 | 无法编译 preview 特性 |
| 重放生产 dump 文件 | 和线上版本一致 | jstack/jmap 版本不匹配,分析结果可疑 |
这不是“装一个新版就完事”的问题。JDK 每个大版本都有编译器行为和运行时行为差异,很多时候代码本身没有故意用新特性,但构建工具、字节码库、测试框架会被版本带偏。
1.2 手动配置环境变量的三个致命伤
手动改环境变量的方式,问题不只在于慢,而在于它有几个结构性的坑。
第一个坑是环境变量刷新机制。Windows 的环境变量是从父进程继承的,不是你改了注册表,所有终端就会自动拿到新值。即使你打开了新的 PowerShell 窗口,也有可能会继承一个没有刷新的 Explorer 进程环境。所以你经常会碰到“明明改好了,新窗口还显示旧版本”的情况。
第二个坑是机器级 PATH 和用户级 PATH 的优先级问题。很多安装包会往系统环境变量里塞 JDK 目录,比如 Oracle 安装器会加一个 C:\Program Files\Common Files\Oracle\Java\javapath,而且这个目录在 PATH 中排在很靠前的位置。你就算把用户级的 JAVA_HOME 改对了,执行 java 时系统还是先找到那个 javapath 里的老 Java,导致你以为切换失败。
第三个坑是手工删除某个版本的 bin 目录时很容易误删非 Java 路径。比如用户 PATH 里可能同时有 Maven、Node、Python 的目录,你为了给新 JDK 腾位置,复制粘贴时少了一个分号,整个 PATH 就废了,接下来所有命令都告诉你“无法将 mvn 项识别为 cmdlet”。
1.3 我给“正常的多版本管理”定义的标准
经过这些折腾后,我认为一套合手的 Windows JDK 版本管理工具至少要满足五个条件:
- 能列出当前装了哪些 JDK,并一眼看清当前用的是哪个。
- 能一键安装指定大版本,不需要打开浏览器去官网点选。
- 切换版本后当前 PowerShell 立即生效,不用重启终端。
- 切换结果在后续新开的终端里也生效。
- 不需要管理员权限,也不依赖 IDE 的 GUI 设置项。
这套标准不追求全自动,它只解决本机 Java 开发环境的问题。系统服务、CI 服务器那种场景,应该交给更隔离的方案,我在后面会专门说边界。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 方案取舍:为什么选择 PowerShell 函数而不是 Scoop 等
2.1 现成工具也能做,但总会隔一层
如果你只是偶尔换一两次版本,用 Scoop、Chocolatey 这类包管理器其实也能装多个 JDK。比如 scoop install temurin17 、choco install temurin8,装完后各自管理各自的路径。问题是这些包管理器更擅长“安装软件”,而“切换当前环境”并不是它们的核心能力。
SDKMAN 是 Java 社区里非常好用的版本管理工具,但它的原生平台是 Unix/Linux,在 Windows 上要么用 WSL,要么用第三方改版,体验和宿主 Windows 环境之间始终隔着一层。你要在 Windows 的 PowerShell 里直接用 sdk use java 17.0.11,原生是做不到的。
我曾经
