
1. 为什么JDK21配IDEA 2023.2值得单独写一篇JDK21是Oracle在2023年9月发布的LTS版本这个版本在Java社区里的讨论热度一直没降下来。虚拟线程、分代ZGC、记录模式、switch模式匹配正式转正这几个特性随便拎一个出来都够写一篇长文。但实际动手的时候很多人卡在第一步——IDEA里怎么把JDK21配好怎么让项目真正跑起来怎么用上新语法还不报错。我自己从JDK17迁移到JDK21的过程中踩了不少坑。IDEA 2023.2这个版本对JDK21的支持是有的但默认配置下会有一些“看起来配好了实际没生效”的情况。比如SDK列表里加了21但模块语言级别还是17比如虚拟线程的代码写出来编译过了运行时报UnsupportedClassVersionError再比如用了记录模式但IDEA一直标红说语法错误。这篇内容就是把这些实际操作中会遇到的问题拆开讲清楚。从JDK21的下载安装、IDEA里的SDK配置、项目结构设置、编译器参数调整到虚拟线程和记录模式的实战代码再到常见报错排查整个流程走一遍。适合正在用IDEA做Java开发、想升级到JDK21但还没动手或者动了一半卡住的人。不管你是刚接触Java不久还是已经写了几年只要跟着步骤走配好之后直接就能用新特性写代码。2. JDK21的获取与安装别在第一步就埋雷2.1 选哪个发行版Oracle JDK还是OpenJDKJDK21的发行版有好几个来源Oracle JDK、Eclipse Temurin、Amazon Corretto、Microsoft Build of OpenJDK等等。从功能角度来说它们都实现了JDK21规范虚拟线程、记录模式这些特性都有。区别主要在许可证、更新频率和附加工具上。Oracle JDK在JDK17之后改成了NFTC许可证个人开发和测试免费生产环境有条件限制。Eclipse Temurin是社区维护的采用GPLv2Classpath Exception用起来限制少。Amazon Corretto和Microsoft的版本也类似都是免费且长期维护的。我个人的选择是Eclipse Temurin原因是更新及时、没有许可证方面的顾虑、和IDEA的兼容性经过大量验证。如果你所在团队有统一要求跟着团队走就行。下载地址直接搜“Eclipse Temurin 21”就能找到选对应操作系统的安装包。注意不要从各种第三方下载站获取JDK安装包尽量走官方渠道。第三方包有被篡改的风险而且版本号可能对不上。2.2 安装路径的选择与验证Windows下安装Temurin的时候默认路径是C:\Program Files\Eclipse Adoptium\jdk-21.x.x.x-hotspot\。这个路径里有空格某些老旧的构建工具处理带空格的路径会出问题。我的习惯是装到一个没有空格的路径下比如C:\dev\jdk21\。安装的时候手动改一下路径就行。macOS下如果用Homebrewbrew install temurin21装完之后路径在/Library/Java/JavaVirtualMachines/temurin-21.jdk/Contents/Home。Linux下解压tar.gz到/opt/jdk21或者用户目录下都可以。装完之后验证一下。打开终端或命令行执行java -version正常输出应该类似openjdk version 21.0.1 2023-10-17 OpenJDK Runtime Environment Temurin-21.0.112 (build 21.0.112) OpenJDK 64-Bit Server VM Temurin-21.0.112 (build 21.0.112, mixed mode, sharing)如果系统里有多个JDK版本java -version显示的可能是旧的。这时候需要检查JAVA_HOME环境变量指向哪里。Windows下在系统环境变量里把JAVA_HOME设成C:\dev\jdk21然后把%JAVA_HOME%\bin加到Path最前面。macOS和Linux下在.zshrc或.bashrc里加export JAVA_HOME/opt/jdk21 export PATH$JAVA_HOME/bin:$PATH改完之后重新打开终端再执行java -version确认输出是21。提示IDEA有自己的JDK管理机制不完全依赖系统的JAVA_HOME。但系统层面的JAVA_HOME设对了命令行工具Maven、Gradle才能用对版本。两边都配好最省心。3. IDEA 2023.2里的SDK配置从全局到项目3.1 全局SDK列表的添加打开IDEA 2023.2进入File Project Structure快捷键CtrlAltShiftS。左侧选Platform Settings SDKs点上面的号选Add JDK。在弹出的文件选择框里定位到JDK21的安装目录比如C:\dev\jdk21选中之后点OK。IDEA会自动识别版本号在SDK列表里显示为21。这时候注意看一下Name字段默认可能是21或者temurin-21改成你容易识别的名字比如JDK21。改名字不影响功能但后面在模块设置里选SDK的时候会清晰很多。添加完成之后在SDK列表里选中JDK21右侧会显示这个SDK包含的类路径和源码路径。源码路径一般指向lib/src.zip如果显示为空或者路径不对手动点Sourcepath标签页把src.zip的路径加上。这样后面看JDK源码的时候能直接跳转到定义。3.2 项目SDK与语言级别的对应设置全局SDK加好了接下来要把它应用到具体项目上。还是在Project Structure里左侧选Project。这里有两个关键设置Project SDK和Project language level。Project SDK下拉框里选刚才加的JDK21。Project language level选21 - Record patterns, pattern matching for switch, virtual threads。这两个要匹配如果SDK选了21但语言级别还是17新语法就用不了。语言级别这个设置控制的是编译器接受哪个版本的语法。IDEA的编译器可以独立于JDK版本设置语言级别比如用JDK21的编译器但只接受Java 17的语法。这种灵活性在某些场景下有用但大多数时候我们就是想让语言级别和SDK版本一致。设置完之后点Apply再点OK。这时候项目层面的配置就生效了。但如果你是多模块项目还需要检查每个模块的设置。3.3 模块级别的SDK与语言级别检查在Project Structure里选左侧的Modules逐个选中模块看右侧的Dependencies标签页。每个模块有自己的Module SDK设置可以选Project SDK跟随项目设置或者指定某个SDK。如果这里显示的是旧版本JDK改成Project SDK或者直接选JDK21。然后看Sources标签页里面有Language level设置。同样选21或者Project default。多模块项目里经常出现的情况是项目级别设了21但某个子模块还是17导致这个模块里的代码用不了新语法。逐个模块检查一遍比较稳妥。实操心得IDEA 2023.2有一个小问题有时候在Project Structure里改完设置编辑器里还是标红。这时候执行File Invalidate Caches Invalidate and Restart重启之后一般就好了。缓存不一致是IDEA的老毛病遇到“明明配了但就是不生效”的情况先清缓存。4. 编译器与构建工具的配套调整4.1 IDEA编译器参数的确认IDEA默认使用自己的编译器不是javac在Settings Build, Execution, Deployment Compiler Java Compiler里可以看到。Project bytecode version应该自动跟随项目SDK如果显示的是旧版本手动改成21。Additional command line parameters里可以加一些编译参数。JDK21支持--enable-preview来启用预览特性但虚拟线程和记录模式在21里已经是正式特性不需要加这个参数。如果你要用预览API比如某些还在预览阶段的特性才需要加。Target bytecode version也要确认是21。这个设置影响生成的class文件版本号。如果target设成了17即使SDK是21生成的class文件也是17版本的运行时如果用21的JVM加载没问题但用17的JVM加载就会报UnsupportedClassVersionError。4.2 Maven项目的pom.xml配置Maven项目需要在pom.xml里显式指定编译器的source和target版本。JDK21对应的配置properties maven.compiler.source21/maven.compiler.source maven.compiler.target21/maven.compiler.target maven.compiler.release21/maven.compiler.release /propertiesmaven.compiler.release这个参数比source/target更推荐它会让编译器同时检查API的可用性防止你用了JDK21的语法但调用了JDK22才有的API。如果用的是maven-compiler-plugin配置类似plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-compiler-plugin/artifactId version3.11.0/version configuration release21/release /configuration /pluginMaven本身也需要用JDK21来运行。在Settings Build, Execution, Deployment Build Tools Maven Runner里JRE选项选Use Project JDK或者直接指定JDK21。如果Maven用的是旧JDK运行编译时可能会报奇怪的错误。4.3 Gradle项目的build.gradle配置Gradle项目在build.gradle里设置java { sourceCompatibility JavaVersion.VERSION_21 targetCompatibility JavaVersion.VERSION_21 }或者用toolchain的方式java { toolchain { languageVersion JavaLanguageVersion.of(21) } }toolchain的好处是Gradle会自动寻找匹配的JDK找不到会报错而不是静默用旧版本。Gradle本身运行用的JDK在Settings Build, Execution, Deployment Build Tools Gradle里设置Gradle JVM选JDK21。注意Gradle版本太旧可能不支持JDK21。Gradle 8.4及以上版本对JDK21的支持比较完善。如果用的是Gradle 7.x建议先升级Gradle Wrapper。在gradle-wrapper.properties里把distributionUrl改成8.4或更高版本。5. JDK21新特性在IDEA里的实战5.1 虚拟线程的代码编写与运行虚拟线程是JDK21里最受关注的特性的。用Thread.ofVirtual()创建虚拟线程代码写起来很简洁public class VirtualThreadDemo { public static void main(String[] args) throws InterruptedException { Thread vThread Thread.ofVirtual().name(my-vthread).start(() - { System.out.println(Running in: Thread.currentThread()); try { Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } System.out.println(Done); }); vThread.join(); } }在IDEA里写这段代码如果SDK和语言级别都设对了不会有任何标红。运行的时候注意看控制台输出虚拟线程的toString()会显示VirtualThread[#21]/runnableForkJoinPool-1-worker-1这样的格式和平台线程的Thread[#21,main,5,main]明显不同。如果要批量创建虚拟线程用Executors.newVirtualThreadPerTaskExecutor()try (var executor Executors.newVirtualThreadPerTaskExecutor()) { IntStream.range(0, 10_000).forEach(i - { executor.submit(() - { Thread.sleep(Duration.ofSeconds(1)); return i; }); }); }这段代码创建一万个虚拟线程每个睡一秒。用平台线程的话需要一万个操作系统线程内存直接爆掉。虚拟线程因为由JVM调度一万个也就占几MB内存。在IDEA里跑这段代码观察任务管理器的内存变化能直观感受到差异。实操心得IDEA 2023.2的调试器对虚拟线程的支持还在完善中。在虚拟线程里打断点调试器的线程列表里会单独分组显示虚拟线程。如果断点没停住检查一下是不是在ForkJoinPool的worker线程里执行的有时候需要调整断点的挂起策略为All而不是Thread。5.2 记录模式与switch模式匹配记录模式Record Patterns允许在instanceof和switch里直接解构记录类的组件。先定义一个记录类record Point(int x, int y) {} record Line(Point start, Point end) {}然后用记录模式做类型判断和解构static void printLineInfo(Object obj) { if (obj instanceof Line(Point(var x1, var y1), Point(var x2, var y2))) { System.out.println(Line from ( x1 , y1 ) to ( x2 , y2 )); } }switch的模式匹配在JDK21里也正式转正了static String describe(Object obj) { return switch (obj) { case Integer i - Integer: i; case String s - String: s; case Point(int x, int y) - Point: x , y; case null - null; default - Unknown; }; }注意case null这个分支JDK21之前switch遇到null会直接抛NPE现在可以显式处理null了。这个特性在实际业务代码里很有用省掉了一层null检查。在IDEA里写这些代码如果语言级别没设到21case Point(int x, int y)这行会标红提示“Pattern matching for switch is not supported at language level 17”。这时候回到Project Structure确认语言级别。5.3 分代ZGC的启用与观察分代ZGC在JDK21里是正式特性启动参数是-XX:UseZGC -XX:ZGenerational。在IDEA里配置运行参数Run Edit Configurations选中你的运行配置在VM options里填入-XX:UseZGC -XX:ZGenerational -Xmx4g运行之后在控制台或者用jcmd工具可以看到GC日志。分代ZGC把堆分成年轻代和老年代年轻代回收频率高但速度快老年代回收频率低。相比非分代ZGC分代版本在大多数场景下停顿时间更短。观察GC行为可以用jcmd pid GC.heap_info输出里会显示ZGC的堆使用情况。如果看到ZGC Cycle相关的日志说明分代ZGC在工作。提示分代ZGC目前不支持-XX:MaxTenuringThreshold这类传统的分代参数调优方式和G1不同。官方建议先跑起来看日志根据实际停顿情况再调整。不要一上来就抄G1的调优参数。6. 常见报错与排查速查6.1 编译期报错无效的目标发行版报错信息java: invalid target release: 21这个报错说明编译器不认识21这个目标版本。原因通常是IDEA用的编译器版本太旧或者Maven/Gradle配置的编译器插件版本太旧。排查步骤先确认Project Structure Project里的SDK和语言级别都是21。然后检查Settings Build, Execution, Deployment Compiler Java Compiler里的Target bytecode version是不是21。如果是Maven项目检查maven-compiler-plugin的版本3.11.0及以上才支持JDK21。Gradle项目检查Gradle版本8.4及以上。6.2 运行期报错UnsupportedClassVersionError报错信息java.lang.UnsupportedClassVersionError: com/example/Main has been compiled by a more recent version of the Java Runtime (class file version 65.0), this version of the Java Runtime only recognizes class file versions up to 61.0class file version 65对应JDK2161对应JDK17。这个报错说明编译用的是JDK21但运行用的是JDK17。检查运行配置里的JRE设置Run Edit Configurations选中配置看JRE下拉框是不是选的JDK21。如果是Default检查系统JAVA_HOME是不是指向了JDK17。6.3 IDEA标红但命令行编译通过这种情况通常是IDEA的缓存或者索引出了问题。先执行File Invalidate Caches Invalidate and Restart。如果重启后还标红检查Project Structure Modules里对应模块的语言级别。有时候项目级别设了21但模块级别还是17IDEA的编辑器会以模块级别为准。还有一种可能是IDEA的Java语言注入设置有问题。在Settings Editor File Types里找到Java确认没有奇怪的关联。不过这种情况比较少见大多数时候清缓存就能解决。6.4 虚拟线程调试时断点不生效在虚拟线程里打断点如果断点类型是Thread可能停不住。因为虚拟线程由ForkJoinPool的载体线程执行断点挂起的是载体线程而不是虚拟线程。把断点的挂起策略改成All右键断点选Suspend: All。这样所有线程都会挂起虚拟线程里的断点就能停住了。另外IDEA 2023.2的调试器在Frames面板里会把虚拟线程的栈帧单独显示。如果看不到虚拟线程的栈检查调试器设置里的Show virtual threads选项有没有勾上。6.5 Maven编译时提示不支持release 21报错信息[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile ... invalid target release: 21maven-compiler-plugin 3.8.1不支持JDK21。升级到3.11.0plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-compiler-plugin/artifactId version3.11.0/version /plugin同时确认Maven本身运行在JDK21上。在Settings Build Tools Maven Runner里把JRE设成JDK21。7. 多版本JDK共存时的管理策略7.1 系统层面的版本切换开发机上同时装JDK17和JDK21是很常见的情况。Windows下可以用环境变量切换但每次改环境变量太麻烦。我的做法是装一个版本管理工具比如SDKMANmacOS/Linux或者手动维护几个批处理脚本。macOS/Linux下用SDKMANsdk install java 21.0.1-tem sdk install java 17.0.9-tem sdk use java 21.0.1-temsdk use只在当前终端会话生效不影响其他终端。这样可以在不同终端里用不同JDK版本。Windows下可以写两个批处理文件jdk17.bat和jdk21.bat内容分别是设置JAVA_HOME和Path。需要切换的时候运行对应的批处理。7.2 IDEA里的多SDK管理IDEA的SDK列表里可以同时存在多个版本的JDK。在Project Structure SDKs里把JDK17和JDK21都加上分别命名清楚。然后每个项目根据自己的需要选对应的SDK。多模块项目里不同模块可以用不同的SDK。比如主模块用JDK21某个依赖旧库的模块用JDK17。在Modules Dependencies Module SDK里单独设置。这种灵活性是IDEA的强项但也要注意模块之间的依赖关系JDK17编译的模块被JDK21的模块依赖时class文件版本是兼容的21可以加载17的class反过来不行。实操心得在SDK列表里给每个JDK加上清晰的命名比如JDK17-temurin、JDK21-temurin。不要用默认的版本号命名因为多个发行版的版本号可能一样时间长了分不清哪个是哪个。8. 从JDK17迁移到JDK21的注意事项8.1 废弃API的清理JDK21里有一些API被标记为废弃或者移除了。比如Thread.stop()、Thread.suspend()这些方法在JDK21里调用会抛UnsupportedOperationException。迁移之前先用jdeps工具分析一下项目依赖jdeps --jdk-internals -recursive -cp libs/* myapp.jar这个命令会列出所有使用了JDK内部API的地方。根据输出逐个替换成标准API。8.2 第三方库的兼容性检查项目里用的第三方库需要确认支持JDK21。Spring Boot 3.2及以上版本对JDK21有官方支持Spring Boot 3.0/3.1在JDK21上跑可能会有问题。Netty 4.1.100及以上版本支持虚拟线程。数据库驱动方面MySQL Connector/J 8.2.0及以上、PostgreSQL JDBC 42.6.0及以上都支持JDK21。检查方法在Maven里执行mvn dependency:tree把依赖树导出来逐个确认关键库的版本。或者直接跑单元测试看有没有NoSuchMethodError、ClassNotFoundException这类兼容性报错。8.3 构建脚本的版本升级Maven Wrapper的版本、Gradle Wrapper的版本、各种插件的版本都需要检查。Maven Wrapper 3.9.5及以上、Gradle Wrapper 8.4及以上对JDK21的支持比较好。maven-compiler-plugin升到3.11.0maven-surefire-plugin升到3.1.2及以上。升级构建脚本的时候建议先在独立分支上操作跑通全部测试再合并。构建工具的版本升级有时候会引入行为变化比如Gradle 8.x对依赖解析的默认行为有调整可能需要额外配置。9. 我在实际操作中总结的几个关键点JDK21的配置本身不复杂但细节比较多。SDK、语言级别、编译器目标版本、构建工具配置这几个地方任何一个没对上就会出现“看起来配好了但实际用不了”的情况。我的习惯是配完之后写一个最简单的测试类用上虚拟线程和记录模式跑一遍确认没问题再开始迁移正式代码。IDEA 2023.2对JDK21的支持整体是稳定的但缓存问题偶尔会出现。遇到标红或者提示不一致的时候先清缓存重启能省很多排查时间。另外虚拟线程的调试和平台线程有些差异断点挂起策略需要调整这个在官方文档里提得不多但实际调试时一定会遇到。最后分享一个小技巧在Settings Editor Inspections里把Java的语言级别检查打开IDEA会在你用了当前语言级别不支持的语法时给出更明确的提示。这个检查默认是开的但有时候会被误关。确认它是开启状态能帮你更早发现配置问题。