一文搞定 Mac 上的 Java 版本管理:SDKMAN 安装、多版本切换与项目实战
装过几次 Java 的人,大概率都被 JDK 版本折腾过:这个项目要 JDK 8,那个要 21,新拉的代码又点名要 25;JAVA_HOME 手动改来改去,终端一关就忘。说白了,JDK 不是装一个就完事,而是要能在多个版本之间随手切。
本文用一套从安装到项目实战的完整流程,讲清楚怎么用 SDKMAN 把 Java(以及 Maven、Gradle)的版本彻底管起来。覆盖:为什么选 SDKMAN、安装、多 JDK 并存、日常命令速查、拿到项目后的推荐用法,以及常见坑。
一、先厘清:为什么需要一个 Java 版本管理工具
直接从官网下载安装包、或者用 Homebrew 装一个 JDK,单看都能用。问题出在"多个版本"和"切换"上:
- 系统里装了多个 JDK,
java命令到底用哪个,取决于PATH里谁排在前面,靠肉眼很难理清; JAVA_HOME是构建工具(Maven、Gradle)和 IDE 认的关键变量,手动export容易漏、容易忘;- 不同项目要求不同版本,你不可能每切一个项目就重装一遍 JDK。
版本管理工具就是来解决"装在哪、用哪个、怎么切"这件事的。JVM 生态里常见的有三个,定位并不一样:
| 工具 | 定位 | 下载安装 JDK | 管理 Maven / Gradle | JAVA_HOME |
|---|---|---|---|---|
| Homebrew 直装 | 通用包管理器 | 能,但多版本切换麻烦 | 能装,但各管各的 | 需手动配置 |
| jenv | 纯版本切换器 | 不能,JDK 要另装 | 不能 | 需额外开 export 插件 |
| SDKMAN | JVM 生态一站式管理器 | 能,多家发行版可选 | 能,统一安装切换 | 自动设置 |
一句话区分:jenv 只管"切换",SDKMAN 管"下载 + 安装 + 切换 + 构建工具"。
如果你只是偶尔切版本、并且习惯用 Homebrew 管理一切,jenv 够用;但只要你认真做 Java 开发------要装多个 JDK、用 Maven 或 Gradle、还想让 JAVA_HOME 自动跟着走------SDKMAN 明显更省心,也是 Java 开发者的主流选择。本文就以 SDKMAN 为准。
二、环境说明与前置条件
本文环境如下,命令在 macOS(Apple Silicon)上实测通过:
| 组件 | 版本 / 说明 |
|---|---|
| 操作系统 | macOS(Apple Silicon,M 系列,aarch64) |
| Shell | zsh(SDKMAN 同时支持 bash、zsh) |
| SDKMAN | core 5.24.0 / native 0.8.0 |
| 已装 JDK | Temurin 21.0.13、Temurin 25.0.5 |
| 系统依赖 | curl、zip、unzip(macOS 自带) |
SDKMAN 安装前会用到 curl、zip、unzip,Mac 上默认都有,不用额外装。不确定的话可以先确认:
bash
for c in curl zip unzip; do which "$c"; done
三、安装 SDKMAN
3.1 一条命令安装
打开终端,执行官方安装脚本:
bash
curl -s "https://get.sdkman.io" | bash
脚本会把 SDKMAN 装到 ~/.sdkman 目录,并自动在你的 ~/.zshrc(或 ~/.bash_profile)末尾追加初始化代码。
3.2 让当前终端立即生效
安装脚本只会影响"之后新开的"终端。要让当前这个窗口立刻生效,执行:
bash
source ~/.sdkman/bin/sdkman-init.sh
或者直接关掉终端、重新开一个,效果一样。
3.3 验证安装
bash
sdk version
正常会输出 core 和 native 两个版本号,类似:
SDKMAN!
core: 5.24.0
native: 0.8.0 (macos aarch64)
✅ 看到版本号,说明 SDKMAN 装好了。
3.4 初始化代码长什么样
安装后,~/.zshrc 末尾会多出这么几行,作用是每次开终端时自动加载 SDKMAN:
bash
#THIS MUST BE AT THE END OF THE FILE FOR SDKMAN TO WORK!!!
export SDKMAN_DIR="$HOME/.sdkman"
[[ -s "$HOME/.sdkman/bin/sdkman-init.sh" ]] && source "$HOME/.sdkman/bin/sdkman-init.sh"
⚠️ 注意注释里的提示:这段初始化建议放在 .zshrc 的靠后位置。如果你 .zshrc 里还有别的工具(比如 Starship 提示符),不用慌,SDKMAN 靠 source 加载,和这些工具通常不冲突。
四、安装与管理多个 JDK
4.1 先看有哪些 JDK 可装
SDKMAN 本身不发行 JDK,它聚合了多家厂商的构建版本。先列出所有可装的 Java:
bash
sdk list java
列表会给出每个版本的标识符(Identifier),长这样:
21.0.13.0+9-tem
25.0.5.0+8-tem
25.0.5.0+8-zulu
25.0.5.0+9-amzn
标识符的规律是 版本号+构建号-发行版后缀。常见后缀:
| 后缀 | 发行版 | 说明 |
|---|---|---|
-tem |
Eclipse Temurin(Adoptium) | 免费、社区主流,本文推荐 |
-zulu |
Azul Zulu | 老牌、覆盖面广 |
-amzn |
Amazon Corretto | AWS 出品 |
-ms |
Microsoft Build | 微软出品 |
-oracle |
Oracle JDK | 注意商用授权条款 |
-graalce |
GraalVM CE | 适合 AOT 编译、Native Image 场景 |
为什么推荐 Temurin:它是 Eclipse 基金会 Adoptium 工作组维护的 OpenJDK 构建,免费开源、更新及时、被广泛使用,作为日常开发和学习的默认版本最稳妥。
Apple Silicon(M 系列)用户不用手动挑架构,SDKMAN 会自动选择 aarch64 版本。
4.2 安装指定版本的 JDK
复制 sdk list java 里的准确标识符来安装。以 Temurin 21 为例:
bash
sdk install java 21.0.13.0+9-tem
安装结尾会问你要不要把它设为默认:
Do you want java 21.0.13.0+9-tem to be set as default? (Y/n):
回车(或输入 Y)会把它设为所有新终端的默认版本;输入 n 则只安装、不改变当前默认。
4.3 再装一个 JDK,多版本并存
SDKMAN 的价值就在这------多个版本可以同时装、互不影响。再装一个 JDK 25:
bash
sdk install java 25.0.5.0+8-tem
🔴 关于版本选择:JDK 21(2023 年 9 月发布)和 JDK 25(2025 年 9 月发布)都是 LTS(长期支持)版本,Oracle 对 LTS 提供多年支持。学习和新项目可以用 25,企业存量项目大量还在 21,更老的项目可能要 17 甚至 8。装哪些、默认用哪个,取决于你手头的项目。
4.4 JDK 装在哪、怎么切换的
所有通过 SDKMAN 安装的 JDK 都在 ~/.sdkman/candidates/java/ 下,每个版本一个独立目录,另有一个 current 软链指向当前默认版本:
~/.sdkman/candidates/java/
├── 21.0.13.0+9-tem/
├── 25.0.5.0+8-tem/
└── current -> 21.0.13.0+9-tem
JAVA_HOME 会自动指向这个 current,这正是 SDKMAN 省心的地方------不用你手动 export。
五、日常操作命令速查
SDKMAN 的命令都以 sdk 开头,子命令不多。先给一张集中的全表,后面再分场景逐条说明。
5.0 SDK 常用命令全表(速查)
基础查询
| 命令 | 作用 |
|---|---|
sdk version |
查看 SDKMAN 版本 |
sdk help [命令] |
查看帮助 |
sdk config |
打开配置文件 ~/.sdkman/etc/config |
sdk list |
列出所有可管理的 SDK |
sdk list <sdk> |
列出某 SDK 的全部版本 |
sdk current |
查看所有 SDK 当前版本 |
sdk current <sdk> |
查看某 SDK 当前版本 |
sdk home <sdk> <version> |
查看某版本的安装路径 |
安装与卸载
| 命令 | 作用 |
|---|---|
sdk install <sdk> |
安装最新稳定版 |
sdk install <sdk> <version> |
安装指定版本 |
sdk install <sdk> <name> <path> |
注册已有的本地版本 |
sdk uninstall <sdk> <version> |
卸载某版本 |
版本切换
| 命令 | 作用范围 |
|---|---|
sdk default <sdk> <version> |
全局默认,所有新终端生效 |
sdk use <sdk> <version> |
仅当前终端临时切换 |
项目级环境(.sdkmanrc)
| 命令 | 作用 |
|---|---|
sdk env init |
在项目根目录生成 .sdkmanrc |
sdk env |
应用当前目录的 .sdkmanrc |
sdk env clear |
退出项目,恢复默认版本 |
sdk env install |
安装 .sdkmanrc 里缺失的 SDK |
升级与维护
| 命令 | 作用 |
|---|---|
sdk upgrade |
查看所有 SDK 可升级项 |
sdk upgrade <sdk> |
升级某 SDK |
sdk selfupdate |
升级 SDKMAN 本身 |
sdk update |
刷新候选版本缓存 |
sdk flush |
清理临时文件和缓存 |
5.1 版本切换
| 操作 | 命令 | 作用范围 |
|---|---|---|
| 设全局默认 | sdk default java 21.0.13.0+9-tem |
所有之后新开的终端 |
| 当前终端临时切换 | sdk use java 25.0.5.0+8-tem |
仅当前窗口,关掉即恢复 |
| 查看当前版本 | sdk current java |
单个 SDK |
| 查看全部 SDK 版本 | sdk current |
所有已安装的 SDK |
5.2 安装、卸载、升级
bash
# 查看可装版本
sdk list java
# 安装最新稳定版(不指定版本时)
sdk install java
# 安装指定版本
sdk install java 25.0.5.0+8-tem
# 卸载某个版本
sdk uninstall java 25.0.5.0+8-tem
# 查看哪些 SDK 有新版本可升级
sdk upgrade
# 只升级某一类
sdk upgrade java
# 升级 SDKMAN 自身
sdk selfupdate # 有新版本才更新
sdk selfupdate force # 强制重装
# 刷新候选版本缓存(当提示 SDKMAN 过期时)
sdk update
5.3 清理缓存
bash
sdk flush
SDKMAN 安装过程会留下临时文件和缓存,sdk flush 用来清理它们。
⚠️ 千万别手动删 ~/.sdkman/tmp 这类目录 ,官方明确警告手动删除会破坏 SDKMAN。要清理,一律用 sdk flush。
5.4 顺带管理 Maven、Gradle
SDKMAN 能管的不只是 Java,整个 JVM 生态的工具都能装,版本同样可以多版本切换:
bash
sdk list maven
sdk install maven
sdk list gradle
sdk install gradle
这样 Maven、Gradle 也统一由 SDKMAN 管理,升级切换都是同一套命令。
5.5 注册一个已有的本地 JDK
如果你已经有一个手动安装的 JDK(比如某些厂商的安装包),也可以把它注册进 SDKMAN,而不重新下载:
bash
sdk install java my-21 /Library/Java/JavaVirtualMachines/some-jdk.jdk/Contents/Home
my-21 是你给它起的唯一名字,不能和已有标识符重名。注意:卸载这种本地版本时,SDKMAN 只会移除注册,不会删你原来的目录。
六、拿到一个项目后怎么用(推荐方法,重点)
这一节是日常最高频的场景。先说一个最容易混淆的点:项目里其实存在两套独立的版本控制,分别由 SDKMAN 和 Maven 各管一件事。
6.1 两套版本控制,别搞混
| 控制对象 | 配置文件 | 决定什么 |
|---|---|---|
| 工具链 | 项目根目录 .sdkmanrc |
用哪个 JDK 运行 Maven(即 JAVA_HOME)、用哪个 Maven 版本 |
| 编译目标 | 项目的 pom.xml |
编译出的字节码是哪个 Java 版本 |
说白了:
.sdkmanrc决定"用什么跑";pom.xml里的release决定"编译成什么版本"。
这两件事是分开的,理解了这一点,后面的配置就不会乱。
6.2 推荐流程(一个项目从头到尾)
第 1 步:安装 Maven(只需一次)
bash
sdk install maven
第 2 步:进入项目,生成 .sdkmanrc
bash
cd my-project
sdk env init
这会在项目根目录生成一个 .sdkmanrc,并预填当前使用的版本:
properties
# Enable auto-env through the sdkman_auto_env config
# Add key=value pairs of SDKs to use below
java=21.0.13.0+9-tem
第 3 步:编辑 .sdkmanrc,锁定项目需要的版本
需要几个 SDK 就写几行,Java 和 Maven 都能锁:
properties
java=25.0.5.0+8-tem
maven=3.9.11
Maven 具体版本号以
sdk list maven为准;只想用最新版、不锁 Maven,也可以不写 maven 这行。
第 4 步:应用配置
bash
sdk env
成功会提示:
Using java version 25.0.5.0+8-tem in this shell.
想离开项目、恢复默认版本,执行:
bash
sdk env clear
第 5 步:开启进目录自动切换(强烈推荐)
每次手动 sdk env 还是有点烦。在 SDKMAN 配置文件 ~/.sdkman/etc/config 里设置:
properties
sdkman_auto_env=true
之后你 cd 进含 .sdkmanrc 的项目目录,会自动切到项目指定的版本;离开目录,自动恢复默认。这一步配置一次,永久省心。
第 6 步:在 pom.xml 里指定编译目标版本
用 maven-compiler-plugin 的 release 选项(JDK 9+ 推荐,等价于同时设定 source、target 和引导类路径):
xml
<properties>
<maven.compiler.release>25</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
或者显式配置插件:
xml
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<release>25</release>
</configuration>
</plugin>
</plugins>
</build>
第 7 步:验证
bash
mvn -version
这是确认环境最关键的一条命令,它会同时显示 Maven 版本、运行 Maven 的 Java 版本 和 JAVA_HOME。三个信息都对得上,环境就没问题。
6.3 一条铁律:运行 Maven 的 JDK 必须 ≥ 编译目标
release 设定的编译目标,不能高于运行 Maven 的那个 JDK 版本,因为低版本 JDK 不认识高版本的 API:
| 运行 Maven 的 JDK | 编译目标 release | 结果 |
|---|---|---|
| JDK 25 | 25 | ✅ 可以,能用 25 新特性 |
| JDK 25 | 21 | ✅ 可以,高版本 JDK 编译低目标(常见、推荐) |
| JDK 21 | 25 | ❌ 不行,JDK 21 无法编译成 25 |
所以:
- 项目要用 Java 25 的特性 →
.sdkmanrc里java用 25,pom.xml里release设 25; - 项目只需要 Java 21 →
.sdkmanrc用 21(或 25),release设 21 即可。
6.4 克隆新项目,缺版本一键补齐
从 Git 拉下来一个带 .sdkmanrc 的项目,如果你本机还没装它指定的版本,不用一个个手动装,直接:
bash
sdk env install
SDKMAN 会读取 .sdkmanrc,把里面缺的 SDK 全部下载安装好。这在换电脑、拉新项目时特别省事。
6.5 旧项目里的 .java-version 怎么处理
如果项目里有 .java-version 文件(那是 jenv 的项目级配置,内容通常是一个版本号),SDKMAN 不识别它。处理方式:
bash
# 进入项目,改用 SDKMAN 的配置
sdk env init
# 确认 .sdkmanrc 内容无误后,删掉 jenv 旧文件
rm .java-version
七、常见问题排查
7.1 非交互环境报 "Unable to locate a Java Runtime"
在脚本、CI 或某些非交互方式下调用 java,可能报:
The operation couldn't be completed. Unable to locate a Java Runtime.
原因:SDKMAN 是在交互式 shell 启动时通过 sdkman-init.sh 接管命令的,非交互环境不会自动加载。
解决:在脚本开头先加载初始化,或直接用绝对路径、显式设置 JAVA_HOME:
bash
source "$HOME/.sdkman/bin/sdkman-init.sh"
# 或
export JAVA_HOME="$HOME/.sdkman/candidates/java/current"
7.2 JAVA_HOME 为空
先确认 ~/.zshrc 里有 SDKMAN 的初始化片段,并且当前终端已经 source 过或重新打开。正常情况下 SDKMAN 会自动设置 JAVA_HOME,不需要手动 export。
7.3 下载慢、启动卡顿
SDKMAN 启动时会做一次网络健康检查(healthcheck),网络不稳时可能拖慢启动。可以在 ~/.sdkman/etc/config 里关掉:
properties
sdkman_healthcheck_enable=false
关掉后启动不再等检查,真没网时命令依然会正常报错。如果是下载 JDK 本身慢,可考虑配置代理,或用 5.5 节的本地安装方式,先手动下载再注册。
7.4 想知道某个 SDK 具体装在哪
写脚本时需要某个 SDK 的绝对路径,用 sdk home(类似 macOS 的 java_home):
bash
sdk home java 21.0.13.0+9-tem
# /Users/you/.sdkman/candidates/java/21.0.13.0+9-tem
八、总结:真正需要记住的几件事
- SDKMAN 管"装和切" :JDK、Maven、Gradle 等整个 JVM 工具链都能统一管理,
JAVA_HOME自动设置。 - 三级切换 :
sdk default(全局)、sdk use(当前终端)、.sdkmanrc(项目级,推荐)。 - 项目里两套版本控制 :
.sdkmanrc定"用什么跑",pom.xml的release定"编译成什么版本"。 - 铁律:运行 Maven 的 JDK 版本 ≥ 编译目标版本。
- 清理用
sdk flush,别手动删~/.sdkman下的目录 ;升级用sdk upgrade/sdk selfupdate。 - 开启
sdkman_auto_env=true,进项目自动切版本,最省心。 - 克隆带
.sdkmanrc的项目 ,用sdk env install一键补齐缺失版本。
验证清单
-
sdk version能正常输出版本号 -
sdk list java中已安装版本带有*标记 - 新开终端执行
java -version,与预期默认版本一致 -
echo $JAVA_HOME指向~/.sdkman/candidates/java/current - 项目根目录有
.sdkmanrc,mvn -version显示的 Java 版本正确 -
sdkman_auto_env=true,进入 / 离开项目目录时版本自动切换 - 旧的
.java-version文件已替换为.sdkmanrc
参考资源
- SDKMAN 官网:https://sdkman.io
- SDKMAN 安装指南:https://sdkman.io/install
- SDKMAN 使用手册(命令与配置):https://sdkman.io/usage
- Eclipse Temurin(Adoptium):https://adoptium.net
- Oracle Java SE 支持路线图:https://www.oracle.com/java/technologies/java-se-support-roadmap.html
- OpenJDK 25 项目:https://openjdk.org/projects/jdk/25