
写这篇东西的起因是我自己的开发机差点被搞崩。上个月接了个老项目的维护任务项目用的是 JDK 8而我自己手头新写的服务已经跑在 JDK 17 上机器里还留着之前做实验装的 JDK 21。三个版本挤在一块儿一开始我还挺自信觉得改一下JAVA_HOME就行。结果不到半天就乱了——这边mvn clean package刚过那边 IDE 里跑测试就报UnsupportedClassVersionError甚至有一次我忘了切环境变量直接把 JDK 17 编译的 class 文件提交进了基于 8 的分支CI 直接红了大半天。后来我花了一晚上把机器上的 JDK 环境彻底理清用 jenv 做统一管理顺手把 Homebrew、Maven、IDE 全部串起来。今天这篇就把这套完整的方案写出来包括 jenv 的安装、配置、日常切换、和 Maven/IDE 的配合以及我踩过的全部坑。这套方案不需要你懂太深的内核原理照着做就能让你的 mac 干净地同时容纳多个 JDK 版本随时一键切换。适合目前在用 mac 做 Java 开发、被多版本 JDK 搞到头大的朋友参考。1. 为什么 mac 上需要 jenv 这种工具1.1 多版本 JDK 的真实痛点很多人刚接触多版本 JDK 时第一反应是我手动改.zshrc里的 export 不就行了。比如安装两个 JDK一个放/Library/Java/JavaVirtualMachines/jdk-8.jdk一个放/Library/Java/JavaVirtualMachines/jdk-17.jdk然后用export JAVA_HOME$(/usr/libexec/java_home -v 17)或者直接写死路径。这种方式对于“一周切一次”的场景勉强够用。但真实开发里的痛点在于切换频率远比你想的高你同时维护多个项目项目 A 必须用 8项目 B 要求 17项目 C 实验性用 21。你每天在 IDE 里打开多个窗口每个窗口可能是不同项目的不同 JDK 版本要求。Maven、Gradle 有自己的JAVA_HOME发现逻辑IDEA、Eclipse 也有自己缓存的 SDK 设置。命令行里跑java -version、mvn test、gradle build每一步都可能用到不同的 JDK。如果你只靠手改环境变量就必然出现一个场景在终端里切到了 JDK 17但 IDE 里还跑着 8或者反过来。每次排查环境问题都要花半小时非常消耗注意力。更麻烦的是~/.zshrc里如果写死了某个 JDK 路径那你每次系统升级、Homebrew 更新、JDK 小版本升级后路径都可能会变。写死的路径一旦失效所有依赖JAVA_HOME的工具全部罢工。1.2 jenv 的定位和核心价值jenv 本质上是管理JAVA_HOME环境变量的工具。它做的事其实很朴素把各个 JDK 版本通过 symlink 登记到自己的目录下然后用一个配置文件记录“当前目录用什么版本、全局用什么版本”你执行jenv local 17这样一条命令切换后它会自动帮你重定向JAVA_HOME和PATH。它的价值不在于“它是一个高深的工具”而在于把切换环境这个高频动作收敛成了“一条命令 一个配置文件”。这个配置还可以直接放进项目的.java-version文件里提交到 Git团队其他人 clone 项目后执行jenv local就能自动对齐环境这是手改环境变量完全做不到的。和同类工具相比jenv 最大的优势是“克制”。它不负责安装 JDKJDK 由 Homebrew 或手动安装包解决不绑定特定的包管理器也不侵入你的构建流程。它只做环境切换这一件事。另一个常用工具 sdkman 能力更强还负责安装各种 JDK 发行版但如果你希望 JDK 安装回归 Homebrew 生态统一管理jenv 是更合适的选择。1.3 一个合理的 JDK 安装布局说到 mac 上的 JDK 安装我踩过的第一个坑就是 JDK 安装来源混乱。我见过有人从 Oracle 官网下载、有人用 Homebrew 装、有人直接解压 tar.gz 丢进/Library/Java/JavaVirtualMachines还有人往用户目录里塞。为了后续 jenv 管理方便我强烈建议统一走 Homebrew。用brew install --cask temurin8或brew install --cask temurin17这种方式安装系统会自动把 JDK 放进/Library/Java/JavaVirtualMachines/路径规则统一权限问题少升级也方便。jenv 只是“发现”这些已经装好的 JDK 并把它们登记起来。2. 安装 jenv 并完成基础配置2.1 用 Homebrew 安装 jenv如果你是 mac 用户且还没装 Homebrew那已经不是有没有的问题了这一节默认你机器上已经有 Homebrew。可以在终端里跑brew install jenv安装完成后先别急着用还需要把 jenv 的初始化脚本加进 shell 配置。mac 现在主流是 zsh所以修改~/.zshrcecho export PATH$HOME/.jenv/bin:$PATH ~/.zshrc echo eval $(jenv init -) ~/.zshrc source ~/.zshrc如果你用的是 bash把上面两行加进~/.bash_profile即可。需要提醒的是jenv init -这一步尤其重要很多情况下你装了 jenv 却发现java -version没有任何变化就是因为初始化脚本没加载。执行完后可以用jenv doctor确认一下jenv doctor如果输出类似下面这样说明基础环境没问题[OK] No JAVA_HOME set [OK] Java binaries in path are accessible [OK] Jenv is correctly loaded这里的[OK] No JAVA_HOME set不是报错它是在告诉你当前没有手动设置过JAVA_HOME完全交给 jenv 接管。如果之前配置过JAVA_HOME必须先清掉否则 jenv 和你的手动配置会打架。2.2 使用 Homebrew 安装多个 JDK 版本配合 jenv 使用我推荐统一用 Homebrew 的 cask 安装 OpenJDK 发行版。目前常用的 TemurinEclipse Adoptium发行版在 mac 上兼容性和性能都表现良好安装命令如下# 安装 JDK 8 brew install --cask temurin8 # 安装 JDK 11 brew install --cask temurin11 # 安装 JDK 17 brew install --cask temurin17 # 安装 JDK 21 brew install --cask temurin21如果你是 Apple Silicon 芯片Homebrew 会自动安装 arm64 版本性能和原生支持都没问题。Intel 芯片则安装 x86_64 版本这样无需担心架构不匹配导致的编译或运行问题。还有一个很重要的点Homebrew cask 安装的 JDK实际路径一般在/Library/Java/JavaVirtualMachines/temurin-17.jdk/Contents/Homejenv 会自动识别这个目录。但如果你某些 JDK 是手动下载安装的路径不标准就需要手动添加这一步我会在下一节详细说。2.3 配置 shell 的 JAVA_HOME 导出插件仅仅装好 jenv 还不够因为 jenv 切换 JDK 后PATH确实变了但JAVA_HOME这个环境变量并不会自动更新。很多工具比如 Maven、Gradle、IDEA 的命令行工具依赖的是JAVA_HOME所以必须启用 jenv 的export插件。jenv enable-plugin export执行完这条命令后jenv 会在每次切换版本时自动把JAVA_HOME设置为当前版本的家目录。可用jenv plugins查看所有插件jenv plugins重点关注的还有maven插件它会帮你处理 Maven 相关的环境配置。启用方式jenv enable-plugin maven有些发行版的 jenv 可能还需要写exec插件来保证在子 shell 中动态切换生效不过默认安装通常已经包含执行jenv enable-plugin exec也不会报错建议一并启用。做完这些步骤你在任何目录里跑jenv versions应该能看到已经登记的 JDK 版本。如果还没有下一步就是把 JDK 交给 jenv。3. 实操让 jenv 识别并管理 JDK 版本3.1 添加已安装的 JDK用过其他语言版本管理工具的人可能习惯输入install命令直接下载 SDK。但 jenv 的哲学不一样它默认不下载 JDK只做“登记”。所以添加 JDK 之前你必须已经通过 Homebrew 或官方网站把 JDK 本体安装好了。如果 Homebrew 装的路径标准jenv 有自动发现的机制。但为了稳妥我建议手动添加一遍这样输出信息更明确# 添加 JDK 8 jenv add /Library/Java/JavaVirtualMachines/temurin-8.jdk/Contents/Home # 添加 JDK 17 jenv add /Library/Java/JavaVirtualMachines/temurin-17.jdk/Contents/Home添加成功后会回显类似于temurin64-8.0.392 added temurin64-17.0.9 added如果你下载的是非标准版本的 JDK比如解压在/Users/你的名字/jdk-21同样可以直接把该目录加进去jenv add /Users/你的名字/jdk-21jenv 会自动根据目录内容识别版本号。这一点对“手动解压版”特别友好不用你手动维护任何版本号列表。可以用下面的命令确认jenv versions输出会列出所有登记的版本其中带*的是当前 shell 正在使用的版本。例如* system (set by /Users/你的名字/.jenv/version) 1.8 1.8.0.392 17 17.0.9 21 21.0.2system指你没有用 jenv 管理时的默认 JDK一般指向/usr/bin/java。3.2 全局、目录级和 Shell 级切换jenv 支持三个层级的版本设置理解清楚这三者的优先级是日常使用不迷路的关键。全局版本global是兜底配置代表你机器上默认的 JDK 版本jenv global 17目录级别local是最常用的模式。进入项目目录后执行一次jenv 会在当前目录生成.java-version文件记录该目录应该使用哪个 JDK。以后每次进入这个目录jenv 自动读取该文件并切换cd /path/to/your/project jenv local 8执行完后可以查看一下当前项目的.java-version文件内容就只有8一个数字或者1.8。这个文件建议提交进 Git这样团队协作时所有人不用互相问“你用的什么 JDK”clone 下来执行jenv local就能自动对齐。shell 级别shell是最临时的切换方式只对当前终端窗口生效关闭后失效。适合那种只在当前窗口测试某个 JDK 版本的场景jenv shell 17优先级从高到低为shell local global。也就是说如果当前目录有.java-version文件但你在同一个 shell 里手动执行了jenv shell 17那么生效的是 shell 级的 17而不是 local 级的 8。这个规则和git config的优先级很像理解起来不难。3.3 验证切换是否真正生效很多人在执行切换后发现java -version还是老版本第一反应是 jenv 有问题。其实不是大部分情况是 shell 的缓存或者JAVA_HOME还指向旧路径。切换完成后建议执行以下几条命令逐一验证java -version javac -version echo $JAVA_HOME which java如果你希望立刻看到效果且怀疑有缓存可以执行hash -r这个命令会清除当前 shell 里命令路径的缓存强制重新查找java的位置。另一个常见坑是JAVA_HOME已经手动写死在~/.zshrc里了这样无论 jenv 怎么切换JAVA_HOME都不会变。解决办法是去.zshrc里删除export JAVA_HOME...以及手动把 JDK 加进 PATH 的那些行然后把管理权完全交给 jenv。我在自己的机器上曾经遇到过一个特别隐蔽的问题IDEA 里改了 Project SDK但终端里 Maven 用的还是旧的两边的JAVA_HOME不一致。后面细查才发现是.mavenrc文件里写死了JAVA_HOME。排查时一定要记得检查~/.mavenrc和项目级的.mvn/jvm.config这类隐藏配置文件。3.4 删除或移除不再需要的 JDK 版本如果你不再需要某个 JDK建议先把 jenv 里的登记移除再卸载 JDK 本体jenv remove 8这里remove用的是版本别名也就是jenv versions里看到的名字。移除登记后你可以用 Homebrew 卸载实际安装的 caskbrew uninstall --cask temurin8如果你之前是手动解压的直接删除对应目录即可。需要注意jenv remove只是让 jenv 不再管理这个版本并不会自动卸载 JDK 本体理解这个区别能避免后续误操作。如果你家目录下有多个.java-version文件都指向不存在的版本jenv 在进入这些目录时会报告类似jenv: version8is not installed的提示。此时应执行jenv versions确认可用的版本再更新各个目录下的.java-version文件。4. 和 Maven、Gradle、IDE 的协同工作4.1 让 Maven 正确读取 jenv 的 JAVA_HOMEMaven 本身不定义自己的 JDK它靠的是JAVA_HOME环境变量来定位编译器。如果你已经启用了 jenv 的export插件那么 Maven 会自动使用当前 jenv 选中的 JDK 版本不需要额外的配置。最稳妥的验证方式是在某个项目目录下先执行jenv local 8再执行mvn -v看输出里的 Java 版本是不是 8。如果mvn -v显示的 Java 版本始终是系统默认的说明JAVA_HOME没有被正确导出。这时我建议执行jenv enable-plugin export后重新打开一个终端再试。如果依然不行极大概率是~/.mavenrc里存在JAVA_HOME...的写死配置。这个文件很小容易被忽略但它的优先级比JAVA_HOME环境变量更高一不留神就会让你排查很久。另外Maven 编译器插件也有自己的release参数比如-Dmaven.compiler.release17如果你项目里显式指定了目标版本即使命令行里切换了 JDK编译产物的字节码版本也不会变。这是很多人踩过的坑命令行 JDK 明明已经切到 17 了但编译出来的 class 还是 1.8。所以需要区分“运行 Maven 的 JDK”和“Maven 编译产物的 target 版本”二者是独立控制的。4.2 Gradle 和 jenv 的配合Gradle 工具链的设计比 Maven 灵活一些它支持在build.gradle里显式指定 toolchainjava { toolchain { languageVersion JavaLanguageVersion.of(17) } }如果项目配置了 toolchainGradle 会自动去当前系统里找 JDK 17找不到会提示你安装或配置。此时 jenv 的价值在于你把多个 JDK 都交给 jenv 管理后Gradle 在查找 toolchain 时就有多个候选版本可以匹配不用你手动指定org.gradle.java.installations.paths。不过需要提醒的是Gradle 默认的 daemon 进程会复用有时候你切换了 JDK但 Gradle daemon 还停留在旧的 JVM 上。执行./gradlew --stop停掉所有 daemon 后再构建才能保证环境干净。这个坑我遇到不下三次每次排查到最后都是 daemon 在捣鬼。4.3 IntelliJ IDEA 等 IDE 的 SDK 设置IDEA 有一套自己的 SDK 管理它不会完全跟随终端的 jenv 设置。所以你在终端里切换了 JDK不代表 IDEA 里的项目就自动切换了。正确做法是在 IDEA 的Project Structure快捷键Command ;里找到Project SDK点击Add SDK JDK然后选择/Library/Java/JavaVirtualMachines/temurin-17.jdk/Contents/Home之类的真实路径。IDEA 会记住这个选择和终端里 jenv 的设置互不干扰。如果你希望 IDEA 项目与 jenv 的.java-version文件自动同步可以安装社区插件jenv相关的版本管理插件或者干脆手动在团队规范里约定所有成员统一用 jenv 的 local 配置管理项目版本IDEA 里的 SDK 选择也手动对齐到同一个版本的路径。在 IDEA 的 Terminal 面板里执行命令时默认会加载你的 shell 配置所以 jenv 的效果在 IDEA 内置终端里是生效的。但前提是你安装 jenv 时修改的是~/.zshrc或~/.zprofile且 IDEA 的 Terminal 选择了使用系统 shell。这一点不少人忽略了导致“明明设置了 jenvIDEA 终端里却全部失效”。4.4 多项目并行开发时的设置模板我自己目前维护三个不同 JDK 版本的项目为了尽量减少环境切换带来的心智负担我总结了一套项目管理模板每个项目初始化时先做三件事第一确认项目根目录的.java-version文件存在且内容正确例如要 JDK 8 就写8要 JDK 17 就写17。第二检查pom.xml或build.gradle里的 Java 版本配置与.java-version一致避免 jenv 切到 17 但 Maven 编译目标却是 8 这种“表面一致、产物不一致”的尴尬。第三在 README 里写入以下内容方便新同事快速对齐# 环境要求 执行 jenv local 后使用项目配置的 JDK 版本这三件事做完基本不会出现跨项目切换时的环境混乱问题。5. 从零到一完整配置一份 macOS 的 JDK 多版本环境5.1 一次性全流程步骤速查如果你是想从头整理一台新的 mac 开发机下面的步骤是我验证过的最快路径整体大约 10 到 15 分钟第一步安装 Homebrew如果还没有/bin/bash -c $(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)第二步安装 jenvbrew install jenv第三步配置 shellecho export PATH$HOME/.jenv/bin:$PATH ~/.zshrc echo eval $(jenv init -) ~/.zshrc source ~/.zshrc第四步启用必要的插件jenv enable-plugin export jenv enable-plugin maven第五步安装你需要的 JDK 版本brew install --cask temurin8 brew install --cask temurin11 brew install --cask temurin17 brew install --cask temurin21第六步把 JDK 添加进 jenvfor d in /Library/Java/JavaVirtualMachines/temurin-*.jdk/Contents/Home; do jenv add $d; done这一步用了一个简单的 for 循环把/Library/Java/JavaVirtualMachines/下所有temurin-*.jdk目录一次性添加进去。如果有的目录不是temurin开头自行替换前缀即可。第七步设置全局默认版本并验证jenv global 17 java -version mvn -v到这里你的 mac 已经可以随时通过cd进入不同项目目录自动切换 JDK 版本了。5.2 验证命令的完整清单配置完成后我建议把下面这些命令打印出来贴到工位旁边每次环境有问题时按顺序排查# 查看所有已登记的版本 jenv versions # 查看当前版本 jenv version # 查看 JAVA_HOME echo $JAVA_HOME # 查看 java 实际路径 which java # 查看 java 版本 java -version # 查看 javac 版本 javac -version # 查看 Maven 使用的 JDK mvn -v这些命令能快速定位 80% 以上的环境问题。如果你的which java指向的不是~/.jenv/shims/java那说明你的 PATH 里 jenv 的 shims 不在最前面或者eval $(jenv init -)没被正常执行。5.3 和容器化开发环境的配合现在很多人还会用 Docker 来做一致的开发环境那 jenv 还有必要吗我的看法是有而且和 Docker 不冲突。Docker 容器里的 JDK 版本由镜像决定那是运行环境的规范。但你本机的 jenv 解决的是你“构建产物”和“本地调试”的问题。比如你在容器里跑的是 JDK 17但你本机命令行、Maven、IDE 如果还用 8就会出现本地编译通过、容器运行失败的反差。jenv 让你本机的工具链与容器目标版本保持一致这是它不可替代的价值。如果你频繁切换 Docker 和本机开发我的建议是进入项目目录后先看一眼.java-version再确认容器内的版本两边保持一致再动手写代码能省掉大量低效排查。6. 常见问题与排查技巧实录6.1 jenv 切换后 java -version 没变这是最经典的问题处理优先级如下先检查 shell 是否加载了 jenvcommand -v jenv如果没有任何输出说明初始化脚本没生效。再检查~/.zshrc是否包含eval $(jenv init -)如果没有加上后重新加载 shell。然后检查 PATH 顺序echo $PATH正常情况下~/.jenv/shims应该在PATH的最前面。如果它在后面手动把 export PATH 放在eval $(jenv init -)之前并确认没有其他地方覆盖了 PATH。最后检查是否有.mavenrc、JAVA_HOME等其他配置干扰。执行env | grep JAVA如果输出里有JAVA_HOME且不是指向 jenv 的 shims 目录多半就是被别处写死了。6.2 jenv versions 里看不到刚安装的 JDK刚用 Homebrew 装完 JDK 后jenv 并不会自动感知。需要手动jenv add一次。如果你已经 add 了还是看不到先确认 JDK 的真实路径是否存在ls /Library/Java/JavaVirtualMachines/如果目录存在但 jenv 不识别通常是路径多了一层或者少了一层比如某些版本在temurin-17.jdk/Contents/Home而有些是openjdk-17.jdk/Contents/Home以实际目录名为准。还有一个小技巧执行jenv add时不要手动指定版本号jenv 会从 release 文件里自动解析。你可以用jenv add /Library/Java/JavaVirtualMachines/temurin-17.jdk/Contents/Home它会自动解析出版本号比手动指定准确得多。6.3 JVM 在编译时报找不到 tools.jarJDK 8 时代tools.jar位于$JAVA_HOME/lib/tools.jar这是 JVM 里很常见的一个 jar。但 JDK 9 之后模块化系统改变了结构tools.jar被拆进了jdk.compiler等模块不再以独立 jar 形式存在。如果你用 JDK 17 调试旧项目时遇到tools.jar不存在那不是 jenv 的锅而是你的项目依赖了 Java 8 的内部实现。建议方案有三条一是尽量升级项目依赖到支持新 JDK 的版本二是如果实在没法升用 jenv 切回 JDK 8 来构建三是把--add-modules jdk.compiler加进启动参数以适配模块化结构。具体方案取决于项目实际使用的框架和工具链。6.4 maven 编译产物 Java 版本和切换的不一致Maven 编译产物的字节码版本由maven.compiler.source和maven.compiler.target决定或者由release标签决定。这两个参数可以设置为任意 Java 版本和你本机实际运行的 JDK 版本无关。所以如果你切到 JDK 17但pom.xml里maven.compiler.target是1.8Maven 会生成 Java 8 的字节码。这是合理的因为很多库要保证兼容性。但如果你在项目里同时设置了maven.compiler.release17又用 JDK 8 的 jenv 环境去构建就会报错“release version not supported”。这种情况先检查 pom 配置再反过来调整 jenv 环境。6.5 Homebrew 升级导致的 jenv 失效Homebrew 会自动更新一些软件包如果你执行过brew upgrade某些 JDK 的小版本号变了比如temurin-17.0.9.jdk升级成temurin-17.0.10.jdk那 jenv 里登记的旧版本路径就失效了。解决方案无比简单重新执行一次jenv add /Library/Java/JavaVirtualMachines/temurin-17.jdk/Contents/Home让 jenv 重新解析目录。如果旧条目还占着位数用jenv remove 17.0.9清理后再 add 新版本。这里也有一个我自己的习惯不要使用带小版本号的版本名做 local 设置比如jenv local 17.0.9看似精确实则升级后很容易失效。建议统一用大版本号比如jenv local 17这样 Homebrew 升级同系列小版本时jenv 能继续正确映射到同一个大版本目录。6.6 多用户或 CI 机器上使用 jenvjenv 默认配置是写在用户目录下的如果多用户共用一台 mac每个用户需要独立安装并配置 jenv。如果你在 CI 机器上跑构建建议不要依赖 jenv而是直接显式指定 JDK 路径export JAVA_HOME/Library/Java/JavaVirtualMachines/temurin-17.jdk/Contents/HomeCI 环境保持“简单直白”更不容易出问题。jenv 更适合交互式开发不适合无人值守的自动化流程。7. 我个人的工作流和一些小建议到最后再分享一点我用 jenv 快两年的个人心得。依赖 jenv 的切换不是万能的它解决的是环境切换效率问题而不解决项目本身的构建配置问题。也就是说你不能指望 jenv 帮你解决所有 Java 版本不兼容的异常。它的定位就是把每个项目的 JDK 版本约束在统一配置里让你少花时间在和 JDK 版本搏斗上。我的日常工作流大致是这样每天早上打开终端进入项目目录先习惯性看一眼.java-version文件然后jenv versions确认当前环境。打开 IDEA 前我会先确认 IDEA 里Project SDK选中的版本和项目根目录下.java-version文件一致。虽然多了一步检查但省下的是后面不可预测的构建失败排查时间。另外建议大家给终端设置一个显示当前 JDK 版本的提示符。zsh 里可以这样设置function jenv_prompt_info() { local version$(jenv version-name 2/dev/null) if [[ -n $version ]]; then echo %F{green}JDK:$version%f fi } PROMPT%n%m %~ $(jenv_prompt_info) $ 这样你站在任何目录里都能一眼看到当前项目用的是哪个 JDK不会因为 “我以为我切了” 而踩坑。这个提示符虽然只节省了几秒钟但在高强度的版本切换场景里收益非常显著。最后再补充一个小技巧如果你刚重构了一批环境配置无论如何都无法恢复正常先冷静下来把所有与 Java 相关的环境变量全部打印出来看一眼env | grep -i -E java|jdk|jenv你不一定需要立刻找到问题但在看输出的时候往往就能发现某些老配置在默默捣乱。比如我之前就发现.mavenrc里居然残留着一条指向旧 JDK 的JAVA_HOME记录终端的 export 显示一切正常但 Maven 就是固执地用旧版本直到我打开.mavenrc才恍然大悟。多版本 JDK 管理这块工具只是辅助真正重要的是建立一个让团队和项目都能遵守的约定。jenv 只是把这些约定固化成配置文件的方式之一。希望这篇内容能帮你把 mac 上的 Java 开发环境收拾妥当别再为切 JDK 浪费时间。