Maven本身不解决冲突,仅按规则选择一个版本;所谓解决需主动干预:用mvn dependency:tree -Dverbose看清真实依赖结构,再通过<exclusion>排除 unwanted 传递依赖,或用<dependencyManagement>统一锁定版本。

直接说结论:Maven本身不“解决”冲突,它只是按规则选一个版本;所谓“解决”,是你主动干预依赖树的结果——要么排除掉你不想要的传递依赖,要么用dependencyManagement统一锁定版本。
怎么看清谁在偷偷引入冲突包?
不看mvn dependency:tree就动手改pom.xml,等于蒙眼调参。执行这个命令是排查的第一步,而且得加-Dverbose参数:
mvn -Dverbose dependency:tree | grep "some-artifact-id"
关键点在于识别输出里的omitted for conflict或version managed from这类提示——它们不是警告,是Maven在告诉你:“这里我本该引入A版本,但因为B版本路径更短/声明更早,所以我用了B”。
- 如果某行末尾带
(omitted for duplicate),说明该jar已被其他路径引入过,当前路径被跳过 - 如果看到
(version managed from X.X.X),说明dependencyManagement已介入,实际生效的是括号外的版本 - 只用
mvn dependency:tree不加-Dverbose,会漏掉被省略的冲突节点
怎么精准排除一个传递依赖?
<exclusion>不是万能膏药,用错地方反而让运行时崩得更突然。它只该用于你**明确知道该依赖不该出现**的场景,比如:
立即学习“Java免费学习笔记(深入)”;
- 项目已用
spring-boot-starter-jetty替换嵌入式容器,就必须排除spring-boot-starter-tomcat - 两个SDK都传入了不同版本的
guava,而你决定由主工程统一提供,那就逐个排除
写法必须严格匹配被排除依赖的groupId和artifactId,大小写、连字符都不能错:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</exclusion>
</exclusions>
</dependency>
注意:<exclusion>里不能写<version>,写了也无效;它只认groupId和artifactId。
为什么dependencyManagement比到处写exclusion更可靠?
当你在多个模块里反复排除同一个包(比如commons-lang3),说明你已经在重复对抗Maven的默认行为——这时候该用dependencyManagement了。它不引入依赖,只做版本仲裁:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
</dependencies>
</dependencyManagement>
这样所有子模块只要声明commons-lang3(哪怕不写version),都会强制使用3.12.0。但要注意:
- 它只对当前POM及其子模块生效,父POM里声明才真正起作用
- 如果某个子模块用
<exclusion>把commons-lang3排除了,dependencyManagement就管不到它 - Spring Boot项目通常已有
spring-boot-dependencies做顶层管理,盲目覆盖可能破坏自动配置
最常被忽略的一点:mvn dependency:tree输出里带compile、runtime、test等scope标识,不同scope的相同jar可能共存而不冲突——但一旦你在代码里显式import了某个类,JVM加载的是哪个版本,取决于最终打包进lib/目录的那一个,而不是编译期看到的那一个。


















