看到屏幕上一行红字 Plugin 'org.springframework.boot:spring-boot-maven-plugin' not found,我相信不少人的第一反应和我当年一样——复制、粘贴、搜索。但搜出来的解决方案基本分成三派:有人让你在 plugin 里补一个 <version>,有人让你把本地仓库整个删除重新下载,还有人让你换镜像源。问题在于这些答案彼此冲突,而且在某些项目里确实有效,换个项目就彻底失效。
我把这个报错前前后后踩了五六次之后,才意识到一个关键点:“not found”并不是某个单一配置错误导致的,它更像是一类症状,背后对应着完全不同的根因。 找到真正的根因,比盲目照抄修复命令重要得多。这篇文章不打算只给你一个“标准答案”,而是把我完整的排查链路和最终落地的几种方案都讲清楚,希望能帮你少走点弯路。
1. 报错还原:pom 文件飘红的那一刻发生了什么
1.1 我遇到这个报错时的项目背景
先说我自己第一次遇到这个问题的场景。那是一个多模块的 Spring Boot 工程,父 pom 没有继承 spring-boot-starter-parent,而是自定义了一个 company-parent,里面通过导入 spring-boot-dependencies 的 BOM 来管理依赖版本。
子模块 A 需要打成可执行 jar,于是在 <build><plugins> 里加了这样一段配置:
xml复制<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
然后 IDEA 一刷新 Maven 项目,pom 里的 plugin 就飘红了。命令行执行 mvn clean package 也会在构建阶段报错:
code复制Plugin 'org.springframework.boot:spring-boot-maven-plugin' not found
这个报错文本看起来非常直接,仿佛在说“插件不存在”,但实际上插件肯定存在,只是 Maven 在解析坐标的时候,缺少了某个关键信息,导致它不知道该去哪里下载。
1.2 报错文本的三种出现位置
同样是 not found,在不同地方出现,触发机制其实不太一样。我梳理下来主要有三种:
- IDEA 的 pom 编辑器飘红:显示
plugin 'org.springframework.boot:spring-boot-maven-plugin' not found。这种情况通常是 IDEA 的 Maven 索引里没有解析到插件坐标,要么是没刷新,要么是本地仓库确实没有对应文件。 - 命令行执行
mvn clean package报错:出现在构建生命周期的插件解析阶段。这种情况基本可以判定是 Maven 本身无法从仓库解析到插件。 - CI 流水线日志里报错:通常还会伴随下载超时、校验和失败、
Could not transfer artifact之类的前置错误。这往往是网络或私服仓库配置问题。
这三种位置虽然错误文本相似,但排查方向完全不同。如果你连报错出现在哪里都没搞清楚就乱试方案,大概率会把问题越弄越复杂。
1.3 先泼冷水:别急着加版本号
网上最流行的答案就是加 <version>。这个做法在部分场景下是对的,但如果你继承的是 spring-boot-starter-parent,再手动加版本不仅冗余,还可能造成版本混乱。
我见过有人把插件版本写成 3.1.0,但项目里的 Spring Boot 依赖版本其实是 2.7.18,结果打包出来的可执行 jar 行为非常奇怪,排查了半天才意识到是插件版本和框架版本不一致。所以,在动手改 pom 之前,先搞清楚 Maven 到底是怎么找到这个插件的,比什么都重要。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Maven 是怎么定位这个插件的:坐标解析背后的逻辑
2.1 坐标模型与插件解析入口
Maven 里的任何构件,包括依赖、插件、BOM,都靠三组坐标定位:groupId、artifactId、version,合起来叫 GAV。
插件本质上也是构件,只是由 Maven 核心在某个生命周期阶段调用。当你在 pom 里声明了一个插件并且没有写 version 时,Maven 不会拿着“空版本号”直接去中央仓库下载,它会走一套固定的版本解析逻辑。顺序大致是这样:
- 检查当前 pom 的
<plugin>节点里有没有显式声明<version>。 - 如果没写,检查当前 pom 的
<pluginManagement>里有没有管理这个插件的版本。 - 如果还没有,向上检查 parent 的
<pluginManagement>。 - 如果整个 parent 链条里都没有,最后去看 Maven 超级 POM(super POM)里为内置生命周期插件绑定的默认版本。
问题就出在第四步。Maven 超级 POM 里默认绑定了 maven-clean-plugin、maven-resources-plugin、maven-compiler-plugin 等一批核心插件,但 spring-boot-maven-plugin 不在这个列表里。所以当你没有通过任何上层机制提供版本号时,Maven 就只能在最后报一句 not found。
2.2 pluginManagement 才是“不写版本”的真正靠山
理解了上面的流程,就能明白为什么很多人说“继承 spring-boot-starter-parent 就不用写版本”。
打开 spring-boot-starter-parent 的 pom,你会看到它的 <pluginManagement> 里已经声明了 spring-boot-maven-plugin 的版本。也就是说,parent 提前帮你把版本号写好了,你在子模块里只需要写 groupId 和 artifactId,Maven 沿着 parent 链条就能找到 version。
这里有一个非常容易混淆的概念,必须单独拎出来说:spring-boot-dependencies BOM 管理的是项目依赖的版本,但它并不负责管理 spring-boot-maven-plugin 的版本。
很多人会在自定义 parent 里导入 spring-boot-dependencies,然后天真地以为插件版本也会跟着被管理。我当时就是这么踩坑的:依赖的版本全部正常,唯独插件 not found,因为插件版本这块根本没人管。Spring Boot 的 BOM 解决的是“依赖版本统一”问题,而插件版本属于“构建插件管理”问题,两者不在同一个维度。
想要在自定义 parent 里实现“子模块不用写插件版本”,正确做法是在自定义 parent 的 <pluginManagement> 里手动声明插件版本。这个操作和 spring-boot-dependencies 没有关系。
2.3 插件没有版本号时,Maven 到底会去哪里找
很多人的误区是:认为 Maven 会从中央仓库直接下载最新版插件。实际上,Maven 的安全机制决定了它不会主动拉取“最新版”,因为它无法保证最新版一定兼容当前构建。
如果有效 POM 里插件没有 version,Maven 会在本地仓库和远程仓库里尝试查找 Maven 元数据(maven-metadata.xml),看看有没有可用的 release 版本。但这个查找仅限于那些声明在 pluginRepositories 里的仓库。也就是说,插件默认只会从 Maven 中央仓库找,不会从你在 <repositories> 里配置的依赖仓库找。
这个细节也是某些“诡异 not found”的来源:你明明配置了私有仓库,依赖也能正常下载,但插件就是找不到。因为插件走的是另一套仓库配置,叫 <pluginRepositories>。后面讲方案的时候会再提到。
3. 完整排查链路:从现象到根因的五步定位
3.1 第一步:看 effective-pom,别猜
排查任何 Maven 问题,我第一个命令永远是:
bash复制mvn help:effective-pom -DskipTests
这个命令会把当前项目所有继承关系、依赖管理、插件版本解析后的最终结果输出出来。简单说,它展示的是 Maven“真正看到的”POM 内容,而不是你手写的那个。
执行之后,在输出里搜 spring-boot-maven-plugin,会有两种情况:
- 插件节点下没有
<version>,或者整个插件根本没出现,说明问题出在版本来源缺失。 - 插件节点下有
<version>,但构建依然 not found,说明问题不在 pom 配置,而在仓库下载环节。
这一步能快速把“配置问题”和“环境问题”分开,后面的排查方向就不会跑偏。
3.2 第二步:检查本地仓库和 settings.xml
确认 Maven 使用的本地仓库路径,可以用:
bash复制mvn help:evaluate -Dexpression=settings.localRepository -q -DforceStdout
拿到路径后,进去看 org/springframework/boot/spring-boot-maven-plugin 目录是否存在。如果存在,再看目录里面有没有 .lastUpdated 后缀文件或 .part 文件。
.lastUpdated 是 Maven 下载失败后留下的标记文件,它的存在意味着之前有一次下载动作没有完成。更讨厌的是,Maven 对这类失败是有缓存策略的,即使你立刻重跑,它也不会马上重新下载,所以很多人觉得“重试没用”。
同时检查两个配置文件:~/.m2/settings.xml 和 Maven 安装目录下的 conf/settings.xml。重点看三个地方:
<offline>是不是被改成了 true。<mirror>是否配置了某个不可用的镜像。<localRepository>是否指向了一个不存在的路径。
很多项目为了让构建提速会配置镜像,但镜像地址如果写错或者对应的仓库里没有 Spring Boot 构件,同样会报 not found。
3.3 第三步:区分“下载失败”和“坐标不存在”
这是最容易被忽视的一步。所有“not found”都长一个样,但原因分两种:
- 下载失败:网络超时、SSL 证书校验失败、镜像地址返回 404。
- 坐标不存在:版本号写错、groupId 写错、artifactId 写错。
推荐用一条简单的命令单独验证插件坐标是否能正常下载:
bash复制mvn dependency:get -Dartifact=org.springframework.boot:spring-boot-maven-plugin:3.2.4
如果这条命令能成功,说明仓库环境没问题,问题出在你的项目 pom。如果这条命令也报错,那么错误信息里会明确告诉你到底是连接失败还是坐标不存在,排查起来就有的放矢了。
常见的拼写错误包括:org.springframework.boot 写成 org.springframe.boot,版本号写成 2.7.19(实际上 2.7.x 最高到 2.7.18),以及把 spring-boot-maven-plugin 和 spring-boot-starter-web 搞混。这些错误从文本上很难一眼看出来,但用 dependency:get 一测就现原形。
3.4 第四步:验证 IDEA 还是 Maven 本身
还有一种特别迷惑的情况:命令行 mvn clean package 跑得好好的,但在 IDEA 里 pom 就是飘红。
这时候就要检查 IDEA 里配置的 Maven 是不是和你命令行用的是同一个:
Settings -> Build Tools -> Maven -> Maven home path,看看是不是指向了你命令行用的那个 Maven。User settings file是不是同一个settings.xml。Local repository是否一致。
IDEA 自带的 Maven 有时候会和你本机安装的 Maven 版本不一致,加载到的 settings 也可能不同,最终导致本地仓库路径不同、插件解析结果不同。最常见的就是 IDEA 里配置了多个 Maven 环境,项目打开时用了旧环境,而旧环境对应的本地仓库里根本没有这个插件。
3.5 第五步:确认插件 jar 是否完整
如果本地仓库目录里明明有 spring-boot-maven-plugin 的文件夹,里面也有 jar,但 Maven 依然报 not found,那有可能是 jar 文件损坏了。
这种情况常见的诱因是:之前下载到一半被杀毒软件拦截、磁盘空间不足、IDE 占用文件导致写入不完整。处理方式很简单,找到对应版本的目录整体删除,然后重新构建让 Maven 重新下载。
注意:在 Windows 上如果 IDEA 正开着并且项目被加载,文件会被进程锁住,删除会失败。最好先关闭相关项目窗口,或者直接退出 IDEA 再删。
4. 解决方案:不同根因对应不同动作
4.1 方案 A:继承 spring-boot-starter-parent
如果你的项目没有特殊自定义 parent 的需求,这是最省事、最不容易出错的方案。
xml复制<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.4</version>
<relativePath/>
</parent>
继承之后,spring-boot-maven-plugin 的版本会由 parent 的 <pluginManagement> 自动提供,子模块里写插件节点时不需要再关心版本号。
适合场景:新建项目、单体应用、团队没有统一自定义 parent 的情况。代价是 parent 被锁定了 Spring Boot 的默认依赖管理,如果你要覆盖某些依赖版本,需要在 properties 里手动覆盖,灵活性稍差。
4.2 方案 B:自定义 parent 时,在 pluginManagement 里声明插件版本
如果你的公司有统一的 parent pom,不能轻易改成 spring-boot-starter-parent,那就在自定义 parent 的 <pluginManagement> 里手动声明插件版本:
xml复制<pluginManagement>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
这里的 <spring-boot.version> 可以放在自定义 parent 的 <properties> 里统一管理。这样所有子模块依然只需要写 groupId 和 artifactId,父 pom 的 pluginManagement 会自动填充版本。
我在实际项目里最推荐这一种,因为它兼顾了“统一管理”和“不强制继承 starter-parent”两个需求。核心思路是:把“依赖版本管理”和“插件版本管理”分开看,BOM 管前者,pluginManagement 管后者。
4.3 方案 C:显式指定插件版本
如果项目不是多模块,或者你只是临时修一个构建问题,直接在插件节点里写完整版本是最直接的办法:
xml复制<properties>
<spring-boot.version>3.2.4</spring-boot.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
</plugin>
</plugins>
</build>
这里有两个细节需要注意:
- 版本号建议用 property 管理,而不是直接写死。这样后续升级 Spring Boot 版本时,只需要改一处。
- 如果项目同时继承了
spring-boot-starter-parent,又在<plugins>里显式写了版本,必须保证这个版本和 parent 的 Spring Boot 版本一致,否则依赖和插件版本对不上,构建时可能出现奇怪的兼容性问题。
4.4 方案 D:本地仓库缓存损坏,强制重下
如果是下载失败、.lastUpdated 文件残留导致的问题,先用强制更新参数重试:
bash复制mvn -U clean package
如果不行,就要手动去本地仓库目录删除对应缓存。不仅要删 spring-boot-maven-plugin,如果连 parent 或 BOM 也没下载完整,还要删:
org/springframework/boot/spring-boot-starter-parentorg/springframework/boot/spring-boot-dependenciesorg/springframework/boot/spring-boot-maven-plugin
删除后重跑构建。记住,删除目录是比加 -U 更彻底的重试方式,因为 -U 对某些仓库元数据不一定每次都重新拉取。
4.5 方案 E:镜像或插件仓库配置导致拉取失败
如果项目在国内网络环境下构建,访问中央仓库偶尔会超时,最常用也最稳妥的做法是在 settings.xml 里配置公共镜像:
xml复制<mirrors>
<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>
</mirrors>
配置完成后,重跑一次:
bash复制mvn -U clean package
如果项目用了私有仓库,注意 <mirrorOf> 不要轻易写 *。* 表示所有仓库请求都走镜像,这会把你私有仓库的请求也拦截掉,导致私有构件拉取失败。更合理的写法是 external:*,意思是只镜像外部仓库,本机私有仓库流量放行。
另外,如果公司有 Nexus 或 Artifactory 私服,并且插件只存在于私服,那么你需要在 pom 或 settings 里配置 <pluginRepositories>。这是很多人容易漏掉的一个点,因为在大多数项目里,依赖仓库和插件仓库可以分别配置,IDEA 的界面里也分两个 Tab:
xml复制<pluginRepositories>
<pluginRepository>
<id>company-repo</id>
<url>https://repo.example.com/repository/maven-public/</url>
</pluginRepository>
</pluginRepositories>
如果你的私有仓库确实有这个插件,但 Maven 一直 not found,检查一下是不是只配置了 <repositories> 而忘了 <pluginRepositories>。
4.6 方案 F:IDEA 层面的刷新与缓存清理
这个方案针对的是“命令行能过,IDEA 飘红”的情况。操作步骤按顺序来:
- 在 IDEA 里检查 Maven 配置:
Settings -> Build Tools -> Maven,确认 Maven home path、User settings file、Local repository 三项和命令行一致。 - 打开 Maven 工具窗口,点击左上角的刷新按钮,执行
Reload All Maven Projects。 - 在 Maven 面板里执行
clean和compile,确认 IDEA 内部构建能通过。 - 如果还是飘红,执行
File -> Invalidate Caches / Restart,重启 IDEA 后重新加载项目。
这一步放在最后,是因为它耗时最长,而且实际作用是“刷新 IDEA 对 Maven 模型的认知”,并不能解决真正的坐标解析问题。如果前面的排查已经确认 Maven 命令行能正常构建,那这里基本就是缓存问题。
5. 这些边角问题最容易复发,一并讲清楚
5.1 parent 被覆盖:多模块工程的“版本来源”到底是什么
多模块工程最容易被坑的一点是:你以为某个模块的 parent 是父 pom,但有效 POM 可能完全不是那么回事。
比如子模块的 <parent> 节点里 <relativePath> 写错了,Maven 可能从本地仓库解析到另一个同名但完全不同的 parent pom,导致插件版本管理不是你以为的那套。
排查方法是回到 mvn help:effective-pom,先看输出开头部分的 <parent> 节点。如果看到的父 pom 坐标和你预期不一致,那就要检查 relativePath 和本地仓库里的 parent 版本。
还有一种情况是:父 pom 确实声明了插件版本,但某个子模块自己在 <pluginManagement> 里重复声明了一个没有版本的插件节点,把父 pom 的版本覆盖成了空。这种情况非常隐蔽,因为你看有效 POM 时能看到插件,但版本为空,构建依然报 not found。
5.2 插件能解析但 repackage 不生效
还有一种“插件没报错,但效果不对”的情况。spring-boot-maven-plugin 的核心功能是 repackage,它把普通的 jar 重打成可执行 jar。如果你只声明了插件,没有配置 execution,某些版本下插件并不会主动执行 repackage 操作。
所以如果你发现构建没报任何错,但打出来的 jar 不能用 java -jar 启动,检查一下插件节点里有没有这一坨:
xml复制<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
没有的话补上,重新构建即可。这个和 not found 没有直接关系,但在排查“打的包不对”时经常会牵扯到插件配置,顺手提一下。
5.3 Maven Wrapper 与系统 Maven 版本错位
不少项目用 Maven Wrapper(mvnw)来固定构建版本,但开发机上也装了系统 Maven。两个 Maven 版本可能不同,使用的本地仓库也可能不同。
有时候你命令行直接敲 mvn 没问题,但项目偏偏用 ./mvnw 构建,结果 Wrapper 下载的 Maven 版本较老,或者它的 settings 配置指向了另一个本地仓库,导致同一段 pom 在两种命令下表现完全不同。
排查时先确认项目根目录有没有 .mvn 目录,然后看 .mvn/wrapper/maven-wrapper.properties 里的 distributionUrl,和你系统里的 mvn -version 对比一下。团队协作时,大家尽量统一用同一种方式构建,要么都 mvn,要么都 ./mvnw,否则问题很容易在个人电脑上随机出现。
5.4 Spring Boot 3.x 与 2.x 的差异
Spring Boot 3.x 要求 Java 17+,对应的 spring-boot-maven-plugin 也必须是 3.x 版本。Spring Boot 2.x 使用的插件版本是 2.x。两者坐标完全一样,但内部实现和依赖要求不同。
如果你把 Spring Boot 依赖升到了 3.x,但插件版本还在 2.x,构建时轻则无法解析,重则出现奇怪的类加载问题。所以升大版本时,插件版本一定要跟着一起升级。反过来也一样。
判断自己项目属于哪个版本,最简单的方式是看 spring-boot-starter-parent 或 spring-boot-dependencies 的版本号,保持一致即可。
6. 最后说几句个人习惯
6.1 我的排查顺序
这个报错遇到多了之后,我现在基本按固定顺序排查,时间成本从低到高排序:
- 先跑一次
mvn -U clean package,排除偶发下载失败。 - 再跑
mvn help:effective-pom,确认插件版本是否被解析出来。 - 检查本地仓库对应目录和
settings.xml的 mirror、offline 配置。 - 用
mvn dependency:get单独拉插件坐标,判断是坐标问题还是仓库问题。 - 根据根因选择用 parent、pluginManagement 还是显式版本。
大多数项目到第 2 步就能定位问题,真正需要删除本地仓库或改镜像的反而占少数。
6.2 团队协作时的规范建议
最后分享一个团队层面的建议:把插件版本统一放到父 pom 的 <pluginManagement> 里,子模块只写 groupId 和 artifactId;Spring Boot 版本统一用 <properties> 管理;不要一边继承 spring-boot-starter-parent,一边又到处手动写插件版本。
这样做的核心目的是让“版本来源”只有一个。不管是新同事加入还是 CI 环境重建,只要父 pom 稳定,子模块就不会出现 not found。而在 CI 流水线里,建议把 mvn help:effective-pom 的输出留一份归档,环境问题排查时能省很多时间。
这个报错本身不可怕,可怕的是在不理解 Maven 坐标解析机制的情况下,把每一种“修复方案”都试一遍。希望这篇能帮你在下次看到 not found 时,直接从根因入手解决问题。
