最近帮一位同事收拾旧项目报错很典型本地装的是JDK 11代码从仓库拉下来在IDE里一点编译控制台直接打出java: 错误: 不支持发行版本 6。第一反应是Maven没配置好又怀疑依赖版本冲突折腾了一圈才发现真正的根子就两处pom.xml里把编译目标写成了老掉牙的1.6而现在的JDK早就把Java 6这个发行版本移出了支持列表。这不是偶发问题。升级过JDK、接手过陈旧项目、或者从网上下过“祖传模板”工程的人基本都会撞上一次。说破天它也不是Maven本身坏了而是javac编译器对你声明的发行版本说了不。只要把背后的逻辑理顺下次遇到就能五分钟定位而不是像我一样绕远路。1. 先搞懂报错到底在说什么这是JDK在“除名”老版本1.1 报错背后Maven到底执行了什么命令Maven之所以能编译Java工程并不是它自己懂Java语法而是在背后调用javac。这个动作由maven-compiler-plugin发起真实执行的命令大致长这样javac -source 1.6 -target 1.6 -encoding UTF-8 ...Maven把pom.xml里配置的source和target翻译成javac的两个参数。这里的source表示“源码允许使用什么版本的语法”target表示“生成的class字节码按什么版本输出”。而javac报错时说的“发行版本”翻译成人话就是“你指望我编译出来的东西兼容哪个时代的Java”。从JDK 9开始javac对老版本的支持策略发生了明显变化Java 6及其更早的版本的源码不再被接受。所以你在JDK 11上执行上面那条命令编译器直接拒绝干活中文报错就是“不支持发行版本 6”。如果是在英文环境下你会看到类似“release version 6 not supported”的提示。这里有个容易混淆的地方有人会以为“不支持发行版本 6”是说当前JDK不是6得装一个Java 6才行。抛开历史版本不谈这个方向就错了。现代JDK并不是“不支持比自己高的版本”而是“对太老的目标版本做了下线处理”。JDK版本越高能编译的老目标版本就越少。1.2 三个关键“清除节点”JDK 9、JDK 12、JDK 20javac对历史版本的兼容是逐步收缩的。我整理了一张表非常值得收藏JDK版本编译器最低可用的source/target被除名的版本JDK 81.66以下JDK 9 ~ JDK 1176及更早JDK 12 ~ JDK 1987及更早JDK 20 及以上98及更早把这个表和你自己的环境对照一下基本就能解释绝大多数报错为什么同事在JDK 8上跑得好好的到你换了JDK 17的机器上就抛“不支持发行版本 6”因为JDK 17属于JDK 9~11档位吗不对JDK 17的javac最低只支持到71.6早就没了。如果把JDK 8比作一家还能接待老系统客户的公司那JDK 9、JDK 12、JDK 20就是三次“业务收缩”每收缩一次就有一批更老的发行版本被请出名单。Java 6、Java 7、Java 8依次成为被淘汰的编译目标。那“6”这个数字是怎么进到你的构建流程里的这才是解决问题的关键。它不会凭空出现一定是构建链路里某个环节写死了1.6。2. 动手前的三步定位先别急着改pom把“谁在捣乱”查清楚2.1 看底牌确认JDK和Maven的真实版本排查的第一步不是改代码是确认环境。java -version javac -version mvn -version echo $JAVA_HOME which java这几个命令输出什么就代表你当前用的编译工具是什么。我见过很多次“改了半天pom还是报错”的案例最后发现是JAVA_HOME指向了旧JDK或者命令行里的java是PATH里另一个版本的JDK。注意JAVA_HOME和PATH是两套体系某些工具看JAVA_HOME某些工具直接调PATH里的java两者不一致时你看到的版本和你实际构建用的版本经常是两码事。另一个常见的坑是IDE里的Maven运行环境。IDEA的Settings里Build Tools - Maven - Runner - JRE这里可以单独指定Maven运行时用的JRE。如果这里被设置成某个内置JDK命令行里改JAVA_HOME根本不生效。mvn -version输出里有一行Java version这一行的数字才是Maven实际运行在哪个JDK上的最终答案以它为准不要凭感觉。2.2 查pom所有可能出现“6”的角落环境确认后回到工程本身。逐个文件排查根pom.xml的properties里有没有maven.compiler.source6/maven.compiler.source或maven.compiler.target6/maven.compiler.targetbuild节点下有没有显式配置maven-compiler-plugin并且source、target写成了6有没有一个父pom父pom里定义了上述配置而当前项目继承了它项目是否继承了类似spring-boot-starter-parent这类自带编译参数约束的parent。我建议直接在项目根目录跑一条命令一步到位看最终生效的pommvn help:effective-pom -Doutputeffective-pom.txt打开生成的effective-pom.txt搜索maven-compiler-plugin和maven.compiler所有从父pom继承来的、从properties里解析出来的最终值都会现出原形。很多“明明改了子项目pom却没反应”的问题其实就是父pom里的配置一直在覆盖你。2.3 IDE也会横插一脚Language Level和Java Compiler设置还有一类误报是pom完全没问题但IDE的编译设置把它override了。IDEA里重点关注两个地方Project Structure - Project - Language LevelSettings - Build, Execution, Deployment - Compiler - Java Compiler - Target bytecode version。IDEA的Java Compiler面板允许按模块设置字节码版本如果某个模块这里写了6就算pom里写的是1.8IDE内部的构建也会按6来走然后当着你的面报“不支持发行版本 6”。Eclipse则是Window - Preferences - Java - Compiler里的Compiler compliance level同一个道理。我的排查习惯是先跑命令行mvn clean compile。命令行是最干净的环境如果命令行通过而IDE报错那问题十有八九就在IDE的编译器设置而不是项目文件上。3. 对症下药五种场景的完整修复方案3.1 快速止血把1.6改成当前JDK支持的目标版本如果只是急着让项目跑起来最简单直接的做法是把pom里的1.6改成你的JDK实际支持的版本。比如JDK 11按上一节那个对照表最低只能到7稳妥起见直接上8properties maven.compiler.source1.8/maven.compiler.source maven.compiler.target1.8/maven.compiler.target /properties注意1.8和8的写法都合法javac两种都认。但整个项目里要保持统一别一处写1.8一处写8看着乱不说后面维护容易看走眼。改完之后命令行执行mvn clean compile验证。如果之前是IDE报错记得让IDE重新导入Maven项目。IDEA里点一下Maven面板的刷新按钮或者直接按CtrlShiftO。很多人改了pom不刷新IDE还拿着旧配置自然觉得“没用”。3.2 推荐用properties统一管理编译版本光改一处不叫修好得让整个项目只有一个版本入口。我建议在根pom如果有parent就放在parent里这样写properties maven.compiler.source1.8/maven.compiler.source maven.compiler.target1.8/maven.compiler.target project.build.sourceEncodingUTF-8/project.build.sourceEncoding /propertiesmaven-compiler-plugin会自动读取maven.compiler.source和maven.compiler.target这两个属性不需要额外在build节点里写插件配置。好处很明显升级JDK时只改一个地方不需要翻遍所有子模块找写死的版本号。这里多说一句编码问题顺手把project.build.sourceEncoding也写上。老项目里经常缺这个属性导致在不同操作系统下编译出来的注释乱码、资源文件编码错乱这属于另一个高频坑跟版本问题凑一起时特别迷惑人。3.3 新项目或新JDK直接用release参数少走弯路如果你的JDK版本在9以上我更推荐用release参数而非sourcetarget的组合properties maven.compiler.release8/maven.compiler.release /properties或者显式配置插件build plugins plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-compiler-plugin/artifactId version3.13.0/version configuration release8/release /configuration /plugin /plugins /buildrelease和旧式写法的本质区别是旧式写法只告诉javac“源码级别”和“字节码级别”但编译器仍然会用当前JDK自带的类库来编译很容易编出“自称兼容Java 8但内部调用了Java 11新增API”的代码而release会同时设定源码级别、字节码级别和API签名版本从源头上杜绝这种拧巴状态。注意一个雷release不能和source、target同时使用否则编译器会报“--release与--source/--target不兼容”之类的错误。还有JDK 8本身不认识--release参数如果你机器上还是JDK 8但pom里写了maven.compiler.release反而会报“无效的标记: --release”。所以用release的前提是构建JDK在9以上。3.4 maven-compiler-plugin的版本也要跟上有些工程pom里的properties写得完全正确还是报错那就得看maven-compiler-plugin本身的版本了。老版本插件在解析JDK新参数时经常出问题。我之前遇到过一例插件版本锁在3.2JDK已经换了17properties写的是1.8但插件一直把参数拼错报各种莫名其妙的编译异常。把插件升到3.11.0之后一切恢复正常。如果你不确定选哪个版本可以按JDK粗略对照构建JDK版本推荐编译器插件版本JDK 83.8.1及以上JDK 113.8.1 ~ 3.10.1JDK 173.10.1 ~ 3.11.0JDK 213.13.0这表不是严格的“必须”但照着选基本不会出兼容性问题。新项目我直接锁3.13.0一劳永逸。3.5 极端场景项目真的必须跑在Java 6/7运行时上怎么办确实有些老系统受限于服务器环境运行时只能装Java 6或Java 7。这种情况下你不能直接把目标版本改成8因为生成的class文件拿到Java 6的JVM上跑不了。解决思路是“编译环境”和“运行环境”分离构建机装JDK 8JDK 8还能编译1.6的目标pom里保持1.6编译后产物部署到Java 6的服务器。JDK 8是目前最后一个能编译Java 6目标的JDK如果你的构建机已经升级到JDK 11以上就必须单独准备一套JDK 8环境给这个老项目用。实际项目中我见过更干净的做法给老项目单独配置一套CI任务构建镜像里固定装JDK 8其他新项目用JDK 17互不干扰。人工在本地切换多个JDK很容易切错得不偿失。4. 连带问题依赖、插件、配置文件的几层“版本叠加”4.1 依赖jar的字节码版本怎么反查有时候pom改对了编译报错却变成了另一副面孔bad class file: Xxx.class(class file has wrong version 55.0, should be 52.0)这就是另一类版本冲突了某个依赖jar包是用JDK 11编译的class版本55而你当前javac只能输出到52Java 8。javac读依赖时发现“对方比我新”直接拒绝继续。碰到这种情况先反查class文件的字节码版本javap -verbose target/classes/com/example/Demo.class | grep major如果查看的是jar里的class先把jar解压再执行javap。字节码版本和JDK的对应关系记住常用的几个major versionJDK版本50Java 651Java 752Java 853Java 955Java 1161Java 17定位到是哪个依赖版本太高之后解决方向就两个要么把该依赖降级到能用旧JDK编译的版本要么把整个项目的编译目标提高。真实项目里很多人习惯把依赖升到最新版结果版本追新之后无形中把编译目标也抬高了这就是“maven依赖设置”方面的隐藏雷。4.2 Parent POM的“隐形指挥棒”还有一种很隐蔽的情况项目本身pom没写任何编译版本但继承了某个基础parent pom。比如spring-boot-starter-parent它内部有默认的java.version属性并且它的pluginManagement把maven-compiler-plugin的source和target绑到了这个属性上。这时候你在子项目里只改maven.compiler.source是不生效的因为parent的插件配置优先级更高。正确做法是覆盖parent定义的属性比如properties java.version11/java.version /properties如果你继承的不是Spring Boot这种大众parent而是公司内部自研的parent同样先看parent里定义了哪些版本属性再决定改哪里。总之遇到“改了pom但不生效”的诡异情况第一反应应该是去看继承链上层的配置不要死磕当前pom。4.3.mvn/maven.config和settings.xml里的隐藏参数工程目录下可能有个.mvn/maven.config文件里面可以写Maven命令行参数。有人为了统一构建基线在里面塞了-Dmaven.compiler.source6 -Dmaven.compiler.target6这个文件的优先级相当高它会直接覆盖pom.properties里的同名配置。你检查pom时发现什么都对但编译还是按6走问题很可能就在这些“看不见的文件”里。同理本地~/.m2/settings.xml也可能通过profile定义编译版本。公司内部私服环境尤其常见统一在profile里指定了一套JDK参数。排查时用mvn help:active-profiles看看当前生效的profile别忽略这一层。5. 常见问题速查与一条龙排查命令5.1 高频疑难问答速查表现象排查方向典型结论改了pom保存后报错依旧IDE是否重新导入Maven项目需要刷新Maven项目命令行编译正常IDE编译报错IDE的Language Level或Java Compiler模块设置单独设置成6改成8即可JDK 8环境但pom配了release参数javac 8不识别--release改用source/target或升级构建JDK父pom改了子模块不生效子模块是否强制覆盖了属性检查子模块properties依赖更新后突然报class文件版本错误某依赖使用了更高JDK编译降依赖版本或提高项目编译目标项目能稳定编译但打开就报错检查.mvn/maven.config和settings.xml profile有-D参数写死了版本JDK 17编译时source写8仍报错确认javac supporting table该版本号本身合法需插件版本匹配5.2 一条龙排查命令把整个排查流程整理成可复制的命令序列遇到类似问题照着跑就行# 1. 确认当前生效的JDK和Maven java -version mvn -version echo $JAVA_HOME which java # 2. 查看最终生效的pom配置 mvn help:effective-pom -Doutputeffective-pom.txt # 打开effective-pom.txt搜索compiler相关配置 # 3. 看实际传给javac的参数 mvn clean compile -X | grep -Ei source|target|release # 4. 如果怀疑依赖或编译产物版本 javap -verbose target/classes/你的类路径/类名.class | grep major第3步里的-X会打印Maven的调试信息里面包含maven-compiler-plugin最终拼出的javac命令参数。很多“明明不知道版本从哪来”的情况在这一步就能看到真凶。写在最后的一点体会这类编译版本问题的本质是“构建环境”和“项目预期”之间出现了断层。我踩过几次坑之后养成了两个习惯第一接手任何项目先跑mvn -version和mvn help:effective-pom确认环境底牌和最终生效配置第二新项目一律用release参数并配合CI固定JDK版本让编译目标成为项目里一条明确的基线而不是留给IDE或某个父pom去猜。再分享一个小技巧如果你负责维护多个项目可以把这套检查命令写成一个脚本新同事入职遇到编译报错时先跑一遍十个问题里能自己解决八个。这个坑不大但每次出现都特别容易让人在环境变量和pom之间反复横跳尽早摸清机制后期是真的能省下大把时间。