1. 先弄明白这个报错到底在说什么1.1 一个典型报错现场我这几年接过不少莫名其妙的构建问题最典型的就是这句java: 错误: 不支持发行版本 6。第一次遇到的开发者通常一脸懵明明自己的电脑上装的 JDK 是 11 或者 17IDE 里跑得好好的怎么一到 Maven 编译就提示我去支持一个不知道哪年的 Java 6还有一部分人是在命令行执行mvn clean compile时突然看到这一行紧接着就是红红的一堆错误仿佛项目瞬间回到了十年前的开发环境。别急这不是你的电脑坏了也不是 Maven 中了邪。本质上这是 Java 编译器的source和target版本号和你当前正在使用的 JDK 版本对不上号导致的。换句话说Maven 告诉 javac“请你用 Java 6 的语法规则去编译这段代码”但你的 JDK 早就更新换代要么不支持这么远古的 target要么 JDK 版本太低根本不认识你 POM 里写的新版参数。我先把这个逻辑捋顺后面给出的解决方案你才能理解为什么有效。1.2 “发行版本 6”就是 Java 1.6Java 的版本号有种奇怪的标记传统。我们平时说的 Java 6正式名称其实是 Java SE 6内部版本号是 1.6。同理Java 7 对应 1.7Java 8 对应 1.8。你肯定见过source1.8这种写法它和source8是一个意思。JDK 的那些编译参数--release、-source、-target后面接的数字用的就是这种“大版本号”或“小版本号”的两种写法。报错信息里说“发行版本 6”其实就是 Maven 最终给 javac 传了一个-source 6或者-target 6甚至可能两个都传了。而当前这台机器上的 JDK如果版本已经比较新比如 JDK 12 开始官方就移除了对 1.6 以下 target 的支持javac 就会直接甩出“不支持发行版本 6”来抗议。这里有个容易混淆的点如果你用的是 JDK 8它完全可以支持 target 6但会有一个警告并不算致命。真正致命的是你用了高版本 JDK12去编译一个低版本 target或者反过来用低版本 JDK 去编译高版本 source。所以看清楚你机器上的真实 JDK 版本是解决一切问题的起点。1.3 Maven 为什么会去找 Java 6很多人会问“我根本没写 Java 6 啊POM 里也没配置Maven 凭什么自作主张”问得好。Maven 自己不编译 Java它只是调用 maven-compiler-plugin 来干活。这个插件的默认行为是如果在 POM 里没有任何关于source和target的显式配置它会使用插件的默认值。而这个默认值在历史上的某些版本里就是 1.5 或者 1.6。更准确地说旧版 maven-compiler-plugin比如 3.1 及更早默认 source/target 是 1.5后来在某些分发版本或父 POM 里这个值被调整到了 1.6。另一个情况是你的项目继承了一个公司内部的父 POMparent父 POM 里固定写了maven.compiler.source和maven.compiler.target为 1.6。子模块默默继承了这一切你在自己模块里不显式覆盖自然就中招了。还有一种情况是系统环境变量MAVEN_OPTS里残留了奇怪的参数或者 IDEA 的 Maven Runner 里曾经设置过-Dmaven.compiler.source6。这些隐蔽配置往往比我们预想得更坑。所以定位报错的本质就是“谁最终定义了编译参数”。找到定义者才能精准修正。2. 最容易忽略的幕后黑手maven-compiler-plugin 默认配置2.1 Maven 的编译插件和 source/target 默认值maven-compiler-plugin 是 Maven 构建生命周期里负责调用 javac 的插件。它是 Maven 内置绑定的一部分不需要手动声明也会执行。但只要你不对它做任何配置它就会按自己的“历史习惯”来干活。我查过几个常用版本插件版本 2.3.2 及以前默认 source/target 为 1.3插件版本 3.8.0 之前默认 source/target 为 1.5插件版本 3.8.0 开始默认 source/target 改为 1.6看到没有3.8.0 的默认值正好是 1.6。如果你的 pom.xml 里没有显式指定 compiler 插件的版本Maven 会走 super POM 里默认绑定的版本。这个版本会随着你使用的 Maven 版本不同而变化。例如较新 Maven 3.8.x 内置的 compiler 插件版本往往已经是 3.8.0 或更高那默认就是 1.6——报错信息和你一摸一样。这就解释了为什么很多人在新装的 Maven 3.8 环境中一编译就遇到“不支持发行版本 6”。换到公司老旧的 Maven 3.2.5 上可能反而没这个问题。你能说这是 Maven 的锅吗也能说但根本原因还是我们没有在 POM 里把 Java 版本的“契约”写清楚。编译参数这种事绝对不能交给插件的默认值去猜。2.2 JDK 版本与 Maven 运行环境的关系还有一个更底层的逻辑需要说透Maven 本身是用 Java 写的所以它运行在哪个 JDK 上直接影响它能调用的 javac 版本和能识别的--release参数。你可以在命令行执行mvn -version来看这行输出Apache Maven 3.8.6 Java version: 11.0.18如果显示 Java version 是 11那么 Maven 运行时用的就是 JDK 11。但是注意——Maven 的编译插件在执行编译时默认会使用“运行 Maven 的那个 JDK”作为 javac。也就是说你命令行启动 Maven 用的 JDK 是 11即使你系统里装了 JDK 17它也不会主动去用。除非你配置了 Maven Toolchains或者单独设置了JAVA_HOME。所以在排查问题的时候不要只看java -version显示什么那只是你当前 shell 下的 JDK。必须先看mvn -version因为 Maven 使用它底下那个 JVM 去执行编译。如果它用了 JDK 8而你的 POM 写了 target 8没问题如果它用了 JDK 12你的 POM 还写着 target 5/6 这种过于老旧的版本直接报错。这里面的因果链是很多资深工程师也容易绕晕的地方。3. 两步就能解决的方案设置 JAVA_HOME 和项目编译级别3.1 第一步确认你真正需要的 JDK 版本动手修改之前先冷静问自己三个问题这个项目的其他依赖比如 Spring Boot、第三方库需要哪个 JDK 版本部署目标服务器上的 JDK 是多少项目代码里用了哪些新语法如果代码里用了varJDK 10、recordJDK 14、text blockJDK 13这类高版本特性那编译 target 只写 8 根本过不了编译。反过来如果代码只是普通的for循环加ListStringtarget 8 或 11 都无所谓。对于大部分公司现网项目JDK 8 和 JDK 11 是绝对主流。如果你的 POM 里没有其他限制我建议直接把编译级别设为8既保险又不会触发各种“不支持发行版本”的兼容问题。当然如果项目要求必须跟上新版本比如基于 Spring Boot 3 开发那目标版本最低也要 17。这一步的判断至关重要因为它决定了你后面 POM 里的具体数字。3.2 第二步在 pom.xml 中显式声明编译参数最直接、最不容易出错的方法就是在pom.xml的properties节点里加上两个属性properties maven.compiler.source8/maven.compiler.source maven.compiler.target8/maven.compiler.target project.build.sourceEncodingUTF-8/project.build.sourceEncoding /properties很多人只设置这两个属性就解决了问题。原因在于Maven 内置的 compiler 插件会自动读取这两个属性作为默认 source 和 target。它们本质上是快捷方式比你自己手动去配置整个maven-compiler-plugin插件块要省事得多。然后你需要重新更新 Maven 项目。命令行执行mvn clean compile之前最好先执行mvn clean清理一次避免旧的 target 目录里残留的 class 文件干扰判断。手动配置插件的话也有对应的写法build plugins plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-compiler-plugin/artifactId version3.11.0/version configuration source8/source target8/target /configuration /plugin /plugins /build这种配置比较啰嗦但如果你需要对不同的 profile 使用不同的编译版本就需要这种玩法。日常项目用前一种 properties 就够了。3.3 通过 maven.compiler.source/target 属性简化配置其实上面已经示范了 properties 的用法但我要再强调一个细节maven.compiler.source和maven.compiler.target是两兄弟必须一起设置。有些人只设置 source 不设置 target结果 javac 的-source 8 -target 11会触发另一个错误“target 1.11 已不再支持”或者奇奇怪怪的不匹配。另外还有一种更现代、更推荐的配置方式就是使用-release参数。在插件配置里可以这样写configuration release8/release /configurationrelease参数其实是 JDK 9 以后引入的它同时控制了 source、target 和系统 API 的版本。也就是说你写了release8javac 在编译时只会使用 JDK 8 的公共 API不会让你意外调用到 JDK 9 才有的方法。这在团队开发里是一个很好的约束。用 properties 实现的话对应的属性是maven.compiler.release。不过要注意一点release参数要求当前 JDK 必须高于你指定的 release 版本。如果你的 Maven 运行在 JDK 8 上你是不能把 release 设成 11 的因为这需要更高版本的 JDK 才能支持交叉编译。反过来运行 JDK 17 设置 release 8 是完全可以的。4. 那些“改了但没用”的坑缓存、IDE 和继承4.1 改完 pom.xml 还是报错先检查 Maven 仓库缓存和更新我见过太多开发者信心满满在 pom.xml 里改了属性一执行mvn compile还是同样的错误。第一反应是配置写错了反复检查语法没问题。实际上极有可能是 Maven 的缓存机制在作怪。Maven 本质上是一个基于“项目对象模型”执行的引擎它会对 POM 做解析。如果你没有执行mvn clean可能用了旧版本的编译输出。可光是 clean 还不够因为你本地仓库里可能缓存了上一次构建的“快照”。特别是当你改了 POM 版本号或者依赖关系时一定要执行一次mvn -U clean compile强制更新快照。还有一种情况是你的项目里同时存在多个 Maven 模块你改了父 POM但子模块还没同步。IDEA 这种 IDE 默认会把所有模块一起读取但命令行环境下你必须从父模块开始执行。或者你直接在父模块的目录下执行mvn clean install让子模块重新读取父配置。4.2 IDEA 和 Eclipse 里的隐藏设置如果你在 IDEA 里点击 Maven 面板里的编译按钮报错内容和命令行一致那么大概率是 IDEA 的 Maven Runner 设置和项目 SDK 设置不一致。IDEA 中有一个“Settings → Build Tools → Maven → Runner”界面里面有个 “JRE” 下拉框。你可以选择“Use Project JDK”或指定某一个 JDK。如果你在这里选了某个 JDK而你的项目 SDK 是另一个就会出现很有意思的现象代码能高亮一运行就报版本冲突。另外还有“Project Structure → Project → Language Level”这个设置。它决定的是 IDEE 的代码提示和编译目标并不会直接影响 Maven 的事实编译。但很多人以为改了这个就能解决 Maven 报错其实没有。真正能影响 Maven 的是编辑器里 Maven 控制台用的 JRE 环境以及 pom 里定义的参数。Eclipse 用户相对更麻烦一点。Eclipse 里有两个地方需要同步一个是 “Window → Preferences → Java → Compiler → Compiler compliance level”另一个是 “Maven → User Settings”。如果你改了 POM 后不想手动同步可以在项目上右键 “Maven → Update Project”让它根据 POM 重新生成 Eclipse 的配置文件。4.3 父 POM 和单模块 POM 的优先级问题再给你们讲一个真实常见的坑公司内部有个公共的 parent POM里面用maven-compiler-plugin的configuration直接写死了版本 1.6。这时候你在自己的模块 properties 里写maven.compiler.source11/maven.compiler.source有时候居然不生效。为什么因为 properties 属性的优先级低于插件里的configuration配置。也就是说如果 parent POM 里给 compiler 插件的 configuration 写了硬编码的 source/target那你的 properties 只是让你自己的模块变量改变但插件配置已经固定了就会忽略 properties。这种情况非常隐蔽。解决办法有两种。第一在你自己模块的插件配置里显式覆盖build plugins plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-compiler-plugin/artifactId configuration source11/source target11/target /configuration /plugin /plugins /build第二如果你有权限修改 parent POM最好把 parent POM 里的插件配置改成用 properties 引用这样每个子模块都能自由覆盖。比如 parent 里写maven.compiler.source${java.version}/maven.compiler.source然后子模块可以定义java.version11/java.version。这套机制才是正确打开方式。5. 进阶处理多模块项目、自定义工具链和 CI 环境5.1 多模块项目的统一配置方式多模块项目可以说是这个问题的高发区。某业务系统拆成了 10 多个子模块每个模块里自己写死一套编译参数久而久之必然不一致。一个模块用 1.8一个用 11模块间互相依赖时版本冲突就冒出来了。正确的做法是在最顶层的聚合 POMpackaging 为 pom里统一定义属性。比如properties maven.compiler.source11/maven.compiler.source maven.compiler.target11/maven.compiler.target project.build.sourceEncodingUTF-8/project.build.sourceEncoding /properties子模块默认继承这些属性无需重复配置。如果你有特殊的某个模块需要更高的编译版本比如一个实验性子模块用了 JDK 17 的新特性那就单独在该模块 POM 里覆盖属性。这样既统一了常规模块又保留了灵活性。另外多模块构建时要特别注意构建顺序。如果你在子模块目录里单独执行编译Maven 会先去找这个子模块依赖的其他模块可能触发 SNAPSHOT 更新。建议所有操作都从根目录执行mvn clean install保持构建的全局一致性。5.2 用 Maven Toolchains 为不同模块指定 JDK版本统一说得很容易但现实中可能出现这种情况项目主体要用 JDK 8 编译某个性能模块必须跑在 JDK 17 上因为要用新 GC 参数测试。如果你不想开两个终端分别去配不同 JAVA_HOME可以用 Maven Toolchains 插件。首先在~/.m2/toolchains.xml文件里定义你机器上的多个 JDKtoolchains toolchain typejdk/type provides version8/version vendororacle/vendor /provides configuration jdkHome/path/to/jdk8/jdkHome /configuration /toolchain toolchain typejdk/type provides version17/version vendororacle/vendor /provides configuration jdkHome/path/to/jdk17/jdkHome /configuration /toolchain /toolchains然后在父 POM 里启用 maven-toolchains-plugin通过版本号匹配选择正确的 JDKplugins plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-toolchains-plugin/artifactId executions execution goalsgoaltoolchain/goal/goals /execution /executions configuration toolchains jdk version8/version /jdk /toolchains /configuration /plugin /plugins这样 Maven 在编译时就会使用工具链指定的 JDK而不是环境变量里的 JDK。不过说句良心话Toolchains 日常小项目用不多但确实能治本。它的机制其实相当于给构建过程套了一层“JDK 选择器”是解决“发行版本不支持”的终极手段之一。5.3 CI 流水线中如何避免类似问题到了 CI 流水线里这个问题出现的频率也不低。原因很纯粹构建机器上装了好几个 JDK默认的JAVA_HOME可能指向一个老版本。负责配环境的人换了一轮导致环境状态难以重复。在 Jenkins 或类似工具里建议明确定义构建所需的 JDK 工具。Jenkins 的 Global Tool Configuration 里可以将 JDK 安装到自动目录后面的构建任务可以通过环境变量或者 Maven 配置指定路径。同时在流水线脚本里写死使用某个 JDK 版本而不是依赖 agent 上的默认JAVA_HOME这个细节特别关键。我常用的做法是在流水线的第一步执行一个检查脚本#!/bin/bash set -e java -version mvn -version把这两个命令的输出打印到日志里。如果构建失败至少要能看见用的是哪个版本的 JDK避免在错误环境上空转半天。另外CI 里还可以加上 profile 机制专门用于不同 JDK 版本的测试任务比如mvn clean install -Djava.version11 -Pjdk11配合 POM 里的 profile 定义让同一套代码在不同 JDK 下跑不同测试。6. 实战排错实录五个高频问题速查6.1 报错 “不支持发行版本 6” 但 POM 里已设置 source 为 11前面我提过父 POM 插件的优先级问题。这里再给一个鲜活的排查流程某同事跑来给我看一个项目POM 里编译插件配置如下plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-compiler-plugin/artifactId version3.8.0/version configuration source11/source target11/target /configuration /plugin但是执行 Maven 依然报“不支持发行版本 6”。我让他执行mvn help:effective-pom把最终生效的 POM 打印出来。结果发现 parent POM 里定义了 3.6.0 的插件版本并且 configuration 里写死了1.6。虽然他自己模块的插件覆盖了版本号但 configuration 会被合并source/target 从 parent 继承保留为 6。合并规则是如果子插件配置里没有显式 list 属性父配置会保留。所以他需要在自己的 configuration 里写source11/source和target11/target覆盖 parent 的 6 才能生效。从此我学到一个铁律改完配置先跑mvn help:effective-pom看最终合并结果比盲猜高效十倍。6.2 报错 “不支持发行版本 5” 等其他版本有时候报错可能是“不支持发行版本 5”或者“无效的发行版本: 5”。原因和版本 6 完全一样只是默认值更老。上面我们提到过某些 Maven 内置插件默认 target 是 1.5或者某个老项目模板里带着source1.5/source发布的。解决方法都一样在 POM 里把编译版本改成你的项目需要的新版本而不是去迁就默认。但有一点要注意如果你用 JDK 8是可以编译 source 5 的只是会有一个 “warning: [options] bootstrap class path not set in conjunction with -source 1.5” 的警告。从 JDK 9 开始这种低版本会变成硬错误。所以升级 JDK 之后老项目必须同步升级编译参数。6.3 Maven 编译成功但 Tomcat 运行报错这不是编译问题但经常和版本混淆一起出现。比如你用 JDK 11 编译出 class 文件但 Tomcat 运行在 JDK 8 上启动时报 “UnsupportedClassVersionError”。错误信息长这样java.lang.UnsupportedClassVersionError: com/example/Test (class file version 55.0)class file version 55.0 对应 Java 11如果你看到 52.0 对应 Java 849.0 对应 Java 5。这种情况就不是 Maven 参数的问题而是构建产物和运行环境不匹配。你需要把编译 target 改成运行环境的 JDK 版本或者升级运行环境的 JDK。好在大多数公司都把开发、编译、运行环境的 JDK 统一了碰到这种问题多半是某个环境忘了更新。6.4 IDEA 里点击运行正常命令行 Maven 失败有的开发者发现一个现象在 IDEA 里右键运行 main 方法完全正常命令行执行 Maven 编译就报错。原因在于 IDEA 在运行 main 方法时用的默认编译工具可能是它的内置编译器或者它会根据项目 SDK 自动设置编译参数。而命令行 Maven 使用的是系统环境和 POM 配置两者解耦得很彻底。你以为 IDEA“正常”只是因为它自动采用了你选的 Project SDK 对应的编译级别。解决的核心思路永远只有一个确保 POM 中的编译参数和实际使用的 JDK 版本匹配。只要 POM 配置正确命令行和 IDE 的结果必然一致。如果还有差异检查 IDE 的 Maven Runner JRE 是否被手动改成了另一个版本。6.5 新项目从 JDK 8 升级到 JDK 17 后的连锁反应这两年新项目往 JDK 17 迁移的不少迁移过程中最容易踩的坑是老的 POM 编译目标还是 1.8但本机默认 JDK 是 17。编译会提示“无效的发行版本: 1.8”其实不然。JDK 17 完全支持 source/target 8因为它支持从 7 开始到 17 的目标。真正的问题反而是某些旧库在 JDK 17 下有反射访问限制导致运行时报错但那已经是另一层问题了。针对编译本身从 JDK 8 升到 JDK 17建议把 POM 编译 level 直接设为 17同时升级 maven-compiler-plugin 到不低于 3.11.0 版本并检查依赖库是否有要求更高版本的情况。还有一个坑是 Lombok 的版本太老的 Lombok 与 JDK 17 不兼容可能导致编译错误。记住报错不只是“不支持发行版本”有时候是 Lombok 错误其实根源是整个 JDK 升级带来的工具链更迭。说实话这种“不支持发行版本 6”的报错解决起来并不难但它每年都能碰到不少新人甚至老手在折腾。我个人总结出的经验就是拿到这个报错第一反应永远是搞清楚“当前 Maven 跑在哪个 JDK 上”和“POM 最终发出去的编译参数是什么”这两个信息一旦明确问题基本就解决了一半。剩下的无非是改 POM、清缓存、重新构建。不要被表面的“不支持发行版本 6”吓到它不是说你代码不行只是构建配置里的版本约定出了问题而已。后面再遇到类似的“发行版本 5”“发行版本 7”你也能一眼看穿本质。