Maven 3.9.6升级实战:配置优化、踩坑记录与插件兼容指南
2026/9/9 23:55:40 网站建设 项目流程

作为一个常年跟 Java 项目、CI 流水线、私有仓库打交道的人,我对 Maven 的感情一直很复杂。一方面它稳定、可靠,是 Java 生态的基石之一;另一方面,它偶尔冒出来的诡异报错,也实打实地让人头疼。这次把环境从 3.6.3 和 3.8.x 统一升到 Apache Maven 3.9.6,前前后后折腾了一周,踩了配置的坑,也顺手解决了好几个历史遗留的插件兼容问题。这篇文章不聊虚的,把我升级过程中的实测结果、配置细节、踩坑实录全部摊开讲清楚。

先说结论:Maven 3.9.6 不是一个颠覆性的版本,但它更像是 Apache Maven 团队在 3.9.x 这条线上的“稳定交付物”。如果你还在用 3.6.x 或者更老的版本,又恰好遇到资源过滤、插件版本警告、构建输出混乱这类问题,升级到 3.9.6 往往能让你少掉很多头发。这篇文章适合所有用 Maven 的 Java 开发者,尤其是从老版本升级、切换环境、或者刚接触 Maven 想在 IDEA 里正确配置的人。

1. 3.9.6 到底改了些什么:升级前你得知道的版本逻辑

很多同学看到版本号 3.9.6,下意识会觉得这就是一个“补丁版本”,改点 bug 就完事了。这个理解不算错,但在实际迁移的时候,你会发现它背后有一整套版本策略的变化,搞不清楚这些,升级后配置容易踩坑。

1.1 3.9.x 和 3.8.x 是什么关系

Maven 4 已经规划了很久,但一直没正式铺开,所以 3.9.x 实际上是 Apache Maven 在 3.x 主干上的延续版本。它和 3.8.x 的关系,不是简单的“Bugfix”,而是包含了多项面向未来 Maven 4 的铺垫性改动,尤其是构建日志、依赖解析机制、插件版本默认值、仓库元数据缓存策略这几个方向。

3.9.6 发布在 2024 年初,属于 3.9.x 系列里比较成熟的版本。它的 JDK 最低要求依旧是 JDK 8,但我实测在 JDK 17 和 JDK 21 环境下跑 3.9.6,稳定性明显比 3.6.3 好,尤其是编译插件、surefire 测试插件、resources 插件这些基础组件的默认版本,都被拉到了对 JDK 17+ 更友好的版本上。这一点对现在主流的新项目来说非常关键。

1.2 升级后体感最明显的三个变化

第一,构建日志的格式变“整齐”了。3.9.6 对 Maven 自身的日志输出做了大量清理,很多内部调试信息不再默认打印,同时也屏蔽了一堆老的插件警告。如果你之前被那种“一行构建信息夹着五六个 WARNING”的输出搞到崩溃,升到 3.9.6 会舒服很多。

第二,依赖解析时的元数据缓存策略变了。Maven 3.9.x 对远程仓库的 metadata 缓存、快照版本的更新时间判断做了调整。简单说,就是它不会像老版本那样频繁地去远程仓库核对 SNAPSHOT 版本,所以构建速度会快一些。但你如果习惯了老版本那种“每次构建都尝试拉最新快照”的行为,升级后可能会发现某个快照更新不生效,非得加-U参数强制刷新。这个我后面会专门讲。

第三,对 Maven Wrapper 的支持更顺畅。3.9.6 的mvnw脚本兼容性更好,在 Windows、Linux、macOS 上跑起来基本不会出现奇怪的换行符或者权限问题。你要是维护着多个 Java 项目,强烈建议把项目的 Maven Wrapper 也升级到 3.9.6,这样团队协作时大家的构建版本保持一致,能少掉巨多“我本地能编但 CI 上挂了”的扯皮。

1.3 我的升级建议

如果你现在的项目是纯 Java 8、用的插件版本也比较老、不依赖新的构建特性,那升不升 3.9.6 其实影响不大,因为 Maven 3.x 的 API 兼容性一直做得不错。但如果你的项目是 Java 11 以上、用到了 Spring Boot、多模块聚合、或者你频繁被maven-resources-pluginmaven-compiler-plugin的兼容性问题恶心到,那我建议你直接升。

升级的时候有一点要特别注意:Maven 3.9.6 对某些插件的默认版本进行了提升,但这些默认版本不一定和项目里已经显式声明的插件版本兼容。如果项目 POM 里已经写死了某个很老的插件版本,那升级后首先要关注的就是这些插件能不能正常跑起来。

2. 下载、安装与环境变量配置:从零装好 Maven 3.9.6

这一节写给刚接触 Maven 的同学,也写给那些要在一台新机器上部署 Maven 的老手。因为 3.9.6 的安装过程中有一个非常容易忽略的点:JDK 版本和 settings.xml 的归属关系。

2.1 下载渠道与版本选择

Maven 的下载官网是maven.apache.org,进入 Download 页面后直接选apache-maven-3.9.6-bin.tar.gz或者apache-maven-3.9.6-bin.zip。Windows 用户下载 zip,Linux 和 macOS 用户下载 tar.gz。

这里有个很实用的经验:如果你需要下载历史版本,比如公司强制要求某个老版本,不要只在当前官网翻。Apache 的归档地址是archive.apache.org/dist/maven/maven-3/,里面保留了 3.0 到 3.9.x 的所有版本,连二进制包和源码包都完整。热搜词里提到的“apache 官网下载 maven 3.6.x版本”,就是从这个归档地址里找。很多公司内网环境无法访问外网下载,归档地址反而比官网主页更好用。

2.2 环境变量配置:Windows、Linux、macOS 三平台实操

安装包解压后,建议先把目录名改成maven-3.9.6这样的形式,放到/usr/localD:/dev/这类规范的软件目录里,方便后续维护。

Windows 下的配置流程是:

  1. 右键“此电脑”进入“属性”,打开“高级系统设置”,点击“环境变量”。
  2. 新建系统变量MAVEN_HOME,值设置为 Maven 解压目录,比如D:\dev\maven-3.9.6
  3. 在系统变量Path中新增%MAVEN_HOME%\bin
  4. 打开新的命令行窗口,执行mvn -version验证。

macOS 和 Linux 下,我习惯把配置写到~/.zshrc~/.bashrc里:

export MAVEN_HOME=/usr/local/maven-3.9.6 export PATH=$MAVEN_HOME/bin:$PATH

执行source ~/.zshrc让配置生效,然后mvn -version

验证时请重点关注第一行的输出,它会同时显示 Maven 版本和当前使用的 Java 版本。如果你看到的是Error: JAVA_HOME is not set correctly,说明 JDK 的环境变量没弄好。Maven 3.9.6 需要 JDK 8 及以上,我建议开发环境统一用 JDK 17 或 JDK 21,兼容性和性能都更稳妥。

2.3 settings.xml 三件套:本地仓库路径、镜像、Profile

Maven 的配置文件有两个层级:全局配置在$MAVEN_HOME/conf/settings.xml,用户级配置在~/.m2/settings.xml。两个文件都存在时,用户级的配置会覆盖全局配置。

这里分享一个我自己坚持的规范:全局配置只保留最基础的安全设置,比如默认镜像、本地仓库路径、代理信息;用户级配置放属于个人或项目的 profile,比如某个私服账号、仓库认证信息。这样多台机器同步配置时,不容易把敏感信息带去公司之外的机器。

本地仓库路径建议从默认的~/.m2/repository改到独立目录,比如/data/maven-repoD:/maven-repo。原因很简单:当仓库膨胀到几十 GB 时,系统盘空间会非常吃紧,而且重装系统、切换 SSD 时迁移起来也麻烦。用软链接或者直接在 settings.xml 里指定路径,都能解决这个问题。

<settings> <localRepository>/data/maven-repo</localRepository> </settings>

2.4 阿里云镜像配置细节与 mirrorOf 的坑

国内网络环境下,不配镜像基本没法愉快地拉依赖。热搜词里“maven配置阿里云仓库”“maven配置多个镜像仓库”这两个词条,对应的就是下面这段操作。

主流的镜像配置是在settings.xml里新增一个 mirror:

<mirrors> <mirror> <id>aliyun</id> <name>Aliyun Maven Mirror</name> <url>https://maven.aliyun.com/repository/public</url> <mirrorOf>central</mirrorOf> </mirror> </mirrors>

这里的mirrorOf是关键。central表示只把 Maven 中央仓库的请求转发到阿里云,私服或者其他自定义仓库不受影响。如果你为了省事写成<mirrorOf>*</mirrorOf>,那么所有远程仓库请求都会被转发到阿里云,这在只依赖中央仓库的场景下没问题,但一旦你接入了公司私有 Nexus 或 Artifactory,依赖就会拉不下来或拉到错误快照。

我见过不少人在这个坑里栽跟头,所以要特别提醒:配置多镜像时,最好给每个镜像写清楚mirrorOf的范围,不要图省事用通配符。

如果你要配置多个仓库,另一个思路是使用 profile 里的 repository,而不是镜像:

<profiles> <profile> <id>my-repos</id> <repositories> <repository> <id>aliyun</id> <url>https://maven.aliyun.com/repository/public</url> </repository> <repository> <id>company-nexus</id> <url>https://nexus.company.com/repository/maven-public/</url> </repository> </repositories> </profile> </profiles>

这种方式更适合多私有仓库并行存取的场景,激活 profile 后,Maven 会按顺序依次尝试每个仓库解析依赖,直到找到目标构件。

3. IDEA 里集成 3.9.6:最容易翻车的四个设置

IDEA 对 Maven 的集成已经非常成熟,但正因为 IDE 替我们做了太多事情,导致很多人根本不了解自己项目到底用的哪个 Maven 版本、哪份 settings.xml。这里把最容易翻车的几个点列一下。

3.1 解决“IDEA 默认 Maven 版本”问题

IDEA 新版本往往自带一个 Maven,版本可能不是 3.9.6。这就导致一种很尴尬的情况:命令行里mvn -version显示 3.9.6,IDEA 的 Maven 面板里却是自带的 3.8.x 或 3.6.x,构建行为完全不一致。

正确的做法是在 IDEA 里手动指定 Maven 路径:

  1. 打开File -> Settings -> Build, Execution, Deployment -> Build Tools -> Maven
  2. Maven home path那里,点右侧的文件夹图标,选择你的apache-maven-3.9.6解压目录。
  3. 下方的User settings fileLocal repository会自动读取对应路径,确认指向你的settings.xml和仓库目录。

改完后建议点一下 Maven 侧边栏里的刷新按钮(两个循环箭头的图标),让项目重新导入。

3.2 Maven Runner 参数与 JDK 设置

另一个很容易被忽略的地方是Runner选项卡。在Maven设置页的最下面,有一个Runner子页面,里面有JREVM Options两个字段。

JRE字段决定了 IDEA 里跑 Maven 命令时用的 Java 版本。如果你的项目要求 JDK 17,但这里选的是 JDK 8,那么编译大概率失败。正确做法是选择项目实际使用的 JDK 版本,并且和Project Structure -> Project SDK保持一致。

VM Options里可以设置一些 Maven 运行时的 JVM 参数,比如:

-Xmx2048m -Dfile.encoding=UTF-8

大型多模块项目构建时,默认的 512MB 堆内存经常不够,经常导致 OOM 或者 GC 频繁。保守起见设置 2GB 以上会稳很多。

3.3 多模块项目在 IDEA 中的注意点

多模块项目导入 IDEA 后,父 POM 会显示为一个普通的 Maven 模块,所有子模块会聚合在父模块下面。很多新人会犯的错是:单独对某个子模块执行clean install,结果发现依赖的子模块代码没更新,一直用的是本地仓库里的旧版本。

这是因为子模块之间的依赖,Maven 默认也是从本地仓库解析的,不是自动用 IDEA 的模块依赖。解决办法有两个:

  1. 在 IDEA 的 Maven 面板里,对父工程执行clean install,一次性把整个聚合工程的所有模块都构建一遍。
  2. 开发阶段用mvn -pl 子模块名 -am install-am参数会同时构建该子模块依赖的其他本地模块。

IDEA 里执行这些命令前,先确保 Maven 面板中父工程下的所有模块都已经正常加载,否则很可能出现 Module not found 之类的导入错误。

4. 打包、私服与生命周期:绕不开的几个进阶点

这个标题看起来很大,但实际问得最多的就几个点:clean installpackage有什么区别、jar/war/pom 三种打包方式怎么选、Spring Boot 项目的 repackage 是什么、依赖冲突怎么排查。

4.1 生命周期到底在做什么

Maven 的构建生命周期是三个:cleandefaultsite。日常用得最多的是default生命周期,它包含多个阶段,按顺序依次执行:validatecompiletestpackageverifyinstalldeploy

mvn clean单独执行时只清理 target 目录。mvn package会执行到 package 阶段,也就是编译、跑测试、打 jar/war 包。mvn install则在 package 基础上,把产物安装到本地仓库。mvn deploy则会把包上传到远程私服。

很多人问:我用了mvn package打出来的包为什么不能直接跑?这通常是因为没有执行clean,导致 target 目录里有上次构建的残留文件。所以实际项目操作中,mvn clean package或者mvn clean install才是更严谨的姿势,热搜词里“maven命令行 clean install”说的就是这个。

4.2 jar/war/pom 三种打包方式与 Spring Boot repackage

<packaging>标签里,最常见的三种值分别是jarwarpom。纯工具库和普通应用后端服务用jar,传统 Web 应用需要部署到外部 Tomcat 时用war,多模块聚合的父 POM 用pom,它本身不产出可执行文件,只用来统一管理依赖版本和模块清单。

Spring Boot 项目比较特殊。它的默认打包方式是jar,但打出来的 jar 和普通 Java 库不一样,是 Spring Boot 的可执行 fat jar,里面包含了所有依赖和内嵌 Tomcat。实现靠的是spring-boot-maven-pluginrepackagegoal,它在 package 阶段后把原始 jar 重新加工一遍。

如果你在 IDEA 里对 Spring Boot 项目执行mvn package,然后发现 jar 只有几十 KB,根本跑不起来,多半是spring-boot-maven-plugin没有配置<executions>绑定到repackage。正确配置是:

<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> </plugins> </build>

4.3 依赖作用域与依赖冲突排查

Maven 的依赖常见作用域有compileprovidedruntimetestsystemimport。最容易被忽略的是provided,比如 Servlet API、Lombok 这类只在编译期需要、运行期由容器或编译处理器提供的依赖,用provided能避免打进最终产物里。

依赖冲突是 Maven 项目永恒的痛。冲突的根源是 Maven 的“最近优先、先声明优先”解析原则。当两个不同的依赖树引入同一个框架的不同版本时,Maven 默认选择路径最短的那个,而不是版本最新的那个。这会导致各种奇怪的NoSuchMethodErrorClassNotFoundException

排查依赖冲突最常用的命令是:

mvn dependency:tree

它会打印出完整的依赖树,你可以清楚地看到每个依赖是从哪个传递链引入的。比如看到某个 jar 同时存在 2.5.6 和 3.1.0 两个版本,就可以在 pom 里用exclusion排除冲突项,或者用dependencyManagement统一强制版本。

<dependency> <groupId>com.example</groupId> <artifactId>example-lib</artifactId> <version>1.0.0</version> <exclusions> <exclusion> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> </exclusion> </exclusions> </dependency>

4.4 部署到私服的配置

项目需要发布到公司私服时,光有deploy命令还不够,得在 pom 里配置distributionManagement

<distributionManagement> <repository> <id>company-releases</id> <url>https://nexus.company.com/repository/maven-releases/</url> </repository> <snapshotRepository> <id>company-snapshots</id> <url>https://nexus.company.com/repository/maven-snapshots/</url> </snapshotRepository> </distributionManagement>

私服的账号密码要配置在settings.xml<servers>里,ID 必须和 pom 里的<id>一致:

<servers> <server> <id>company-releases</id> <username>deploy-user</username> <password>deploy-password</password> </server> <server> <id>company-snapshots</id> <username>deploy-user</username> <password>deploy-password</password> </server> </servers>

之所以放在settings.xml而不是 pom 里,是因为distributionManagement在 pom 里是开源可见的,所有人都能看到仓库地址,但账号密码属于敏感信息,放在用户级配置文件里就可以做到“只在本机生效,不进 Git 仓库”。

5. 升级 3.9.6 之后我踩过的坑:报错排查实录

这一节全是真实经历。升级到 3.9.6 后,我的第一反应是“版本号变了,项目应该无感才对”。事实证明,升级带来的插件兼容性问题,比预想中多,但每个问题都能追溯到一个明确的原因。

5.1 NoClassDefFoundError: org/apache/maven/shared/filtering/MavenFilteringException

这个报错在我升级后最先蹦出来,热搜词里也有人在搜,说明它不算罕见。看报错信息,是在执行mvn clean package时,使用了maven-resources-plugin做资源过滤,结果类加载器找不到MavenFilteringException这个类。

根因不是 Maven 3.9.6 本身,而是项目里的maven-resources-plugin版本太老。老版本的maven-resources-plugin依赖的maven-filtering库版本也老,而 Maven 3.9.6 的类加载机制对这种老库的兼容性不如 3.6.x。

解决办法有两种:一是在 pom 里显式升级maven-resources-plugin到 3.3.1 或更高版本;二是在项目的properties里声明maven-filtering的版本,让插件使用新版依赖:

<properties> <maven-resources-plugin.version>3.3.1</maven-resources-plugin.version> </properties>

我最后选择了升级插件版本,因为这样更干净,也避免后续再出现其他 shared 组件兼容性问题。

5.2 mvn validate 失败

validate阶段本身做的事情很少,在大多数项目里它不应该失败。当你在 3.9.6 下遇到mvn validate失败,常见原因有三类:父 POM 解析失败、settings.xml 配置错误、本地仓库元数据损坏。

其中“本地仓库元数据损坏”是最难排查的。Maven 在下载依赖时会把.lastUpdated文件和部分下载失败的临时文件留在本地仓库里。当这些文件损坏后,Maven 解析依赖时会直接判定“该依赖不可用”,反复构建都失败,但原因又不在当前项目里。

遇到这种情况,先检查本地仓库里对应目录下的_remote.repositories.lastUpdated后缀文件,把可疑的一概删除,然后重新执行mvn -U clean validate。如果问题依旧,再看 settings.xml 里是否误配了<offline>true</offline>,这个选项会强制 Maven 不联网,只解析本地仓库已有内容。

5.3 快照版本不更新

这是一类非常容易让人抓狂的问题。项目里依赖了某个内部模块的-SNAPSHOT版本,在 3.6.3 下构建时,Maven 每次都会去远程仓库检查一下快照是否更新。升级到 3.9.6 后,有时同一个版本的快照在远程仓库变了,本地却一直用旧包,构建结果时好时坏。

原理是 Maven 3.9.x 调整了repository system对快照元数据的缓存时间。默认情况下,Maven 不会在每次构建时都去远程仓库核对快照更新时间,而是使用本地缓存的远程元数据。这个改动是为了提升构建速度,但会让很多开发者误以为“升级后拉包有问题”。

解决办法是在执行构建时加上-U参数,强制刷新快照:

mvn clean install -U

如果你希望某个项目每次都检查快照,也可以在 pom 里的repository配置中加<snapshots><updatePolicy>always</updatePolicy></snapshots>

5.4 日常排查工具与思路

除了上面几个具体报错,我还想分享几个 Maven 日常排查思路,因为很多问题都不是“一条命令”能解决的,需要组合工具逐步定位。

先看mvn -X的调试信息。-X参数会输出 Maven 执行过程中的全量调试日志,包括每个依赖是从哪个仓库下载的、哪些 jar 被跳过、哪些插件的哪个 goal 在哪个阶段执行。虽然信息量大,但报错时它往往能直接告诉你定位方向。

其次用mvn help:effective-pom查看最终的生效 POM。这个命令会把当前项目的 POM、父 POM、profile 合并后的完整版本打印出来。有时候配置看起来没问题,但某个 pluginManagement 或 dependencyManagement 在父 POM 里已经写死了老版本,用这个命令一眼就能发现。

最后是mvn dependency:treemvn dependency:analyze的组合。前者解决“依赖从哪来”的问题,后者解决“哪些依赖没用到、哪些用了但没声明”的问题。定期执行这两个命令,能有效减少依赖冲突和隐式依赖带来的潜在风险。

还有一个经验之谈:如果你在 IDEA 里反复执行mvn clean install,但本地仓库的 jar 包内容一直不对,可以关掉 IDEA 的 Maven 自动导入,手动执行一次命令行构建,再用 IDEA 的Reload All Maven Projects重新导入。这一步看着粗暴,却能解决 IDEA 的 Maven 缓存和本地仓库脱节的许多奇怪问题。

我在实际项目里把开发机、CI 服务器、测试服务器全部统一到 Maven 3.9.6 之后,最大的感受是“安静了”。构建日志清爽,依赖解析稳定,插件的默认版本也跟上了 Java 17 的节奏。如果你正在老版本和日常报错之间反复挣扎,与其继续打补丁,不如直接花一个下午把版本升到 3.9.6。最后再分享一个小技巧:升级完别急着删旧版本,先用mvn help:effective-settings对比一下新旧两个版本的配置文件差异,确认没有遗漏的私有仓库和认证信息,再清理旧版。这样切换环境的动作会更平滑,也不容易在第二天上班时被同事一个“仓库拉不下来”的问题打断。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询