news 2026/10/1 13:38:59

Spring Boot Maven打包失败:Unable to find main class的排查与解决

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring Boot Maven打包失败:Unable to find main class的排查与解决

1. 打包失败的现场:先搞清楚这个报错在说什么

先说结论:repackage failed: Unable to find main class这个问题,十有八九不是你的代码逻辑写错了,而是 Spring Boot Maven 插件在 repackage 阶段找不到可执行入口。换句话说,你的项目能编译、能跑测试,但一到打包成可执行 jar 的时候就懵了——它不知道该从哪个类的main方法启动。

很多第一次踩这个坑的人会习惯性地去检查代码,以为是自己main方法写错了位置,或者类名拼写有问题。我最初也这样干过,翻来覆去找了很久,最后才发现问题根本不在业务代码里,而在项目结构和插件配置上。

理解这个报错之前,得先弄清楚 Maven 打包的整个链路。mvn package执行时,会经过 compile、test、jar 等多个阶段,最后如果你的项目引入了spring-boot-maven-plugin,它还会多执行一个repackage目标。这个目标的职责是把原本打出来的普通 jar 重新加工成一个"可执行的 fat jar"——也就是说,把项目自身的 class 文件、所有第三方依赖、以及 Spring Boot 的启动器全部打到一个 jar 包里,让你能用java -jar直接启动。

问题就出在这个 repackage 阶段:它需要找到一个带有main方法的类作为启动入口。如果找不到,就会直接报Unable to find main class,并且整个打包流程以失败告终。

有一点需要特别说明,这个报错和 JDK 版本没有必然关系,和 Maven 版本也没有必然关系。我见过有人升级 JDK、换 Maven 版本、甚至重装 IDEA,结果毫无变化。真正的关键在于:Spring Boot 插件在 repackage 时究竟去哪里找主类,以及你的项目结构是否满足它的查找条件。

为了方便理解,可以把 repackage 的过程想象成做一份打包好的外卖:编译好的代码是菜品,依赖是配菜和调料,而main方法就是"订单上写的送达地址"。没有地址,即便菜品再丰盛,外卖员也不知道往哪送。

2. 从根上拆解:为什么插件会找不到主类

2.1 父工程或模块结构带来的隐患

最常见的场景之一,就是多模块项目。假设你有一个父工程parent-project,下面有common-module、service-module、web-module三个子模块,那么问题可能出现在两个层面:

第一个层面,是父工程的 pom 里直接声明了spring-boot-maven-plugin,而且没有做任何精细化配置。一旦父工程自身也执行 package,插件就会尝试在父工程里找主类,但父工程通常只有 pom 文件没有源代码,自然找不到。

第二个层面更隐蔽:某些子模块(比如common-module)本身只是一个公共依赖模块,它既没有main方法,也不应该被打成可执行 jar。但如果子模块的 pom 继承了父工程里的插件配置,repackage 就会在common-module上执行,结果同样报错。

我自己遇到过的实际案例是:一个同事把公共工具类模块也配上了 Spring Boot 插件,每次打全量包的时候,构建任务在第一个模块就红了,日志里就是Unable to find main class。

这个问题的本质,是插件的作用范围没有控制好。合理做法是:只在真正需要打成可执行 jar 的模块上启用 repackage,其他模块要么不继承该插件,要么显式跳过执行。

2.2 打包配置里显式指定或排除

解决方案其实很直白:在需要打包成可执行 jar 的模块 pom 里,给插件配置mainClass。这样插件就不需要自己去"猜"了,直接按照你指定的类来找。

<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <mainClass>com.example.demo.DemoApplication</mainClass> </configuration> </plugin>

有人会问:为什么 Spring Boot 插件不能自己识别主类呢?答案是可以识别的。如果你只有一个main方法,而且类的签名很标准(public static void main(String[] args)),插件通常能通过扫描 classpath 自动找到。但是如果你有多个类带有main方法,或者你的项目结构不够典型,插件就会陷入"选择困难",最终直接报错。

还有一种情况,是项目里确实有多个主类。比如你写了一个DemoApplication作为 Spring Boot 启动类,又写了一个TestMain作为临时测试入口。这种时候插件不知道谁是真正的启动入口,只能失败。

对于多模块里那些不需要打包成可执行 jar 的模块,建议使用skip参数把 repackage 跳过去:

<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <skip>true</skip> </configuration> </plugin>

这样既保留了插件配置的统一性,又让每个模块该干什么干什么,不会互相干扰。

2.3 一个不常见但确实存在的坑:main 方法所在的类不在编译范围内

还有一种比较隐蔽的情况,是main方法存在的类确实写了,但pom.xml里通过<build>中的<resources>或<plugins>配置,把某些目录排除在了编译或打包范围之外。这种情况下,class 文件压根没有进入最终的 jar 包,插件自然找不到主类。

这种问题排查起来比较头疼,因为 IDE 里编译和运行是正常的,只有通过 Maven 打包才会暴露。遇到了别慌,先在命令行执行下面命令,确认主类是否真的被编译了:

mvn clean compile

然后检查target/classes目录里有没有对应的.class文件。如果没有,说明问题出在编译范围配置上;如果有,再往下排查 jar 内容:

jar tf target/your-app.jar | grep DemoApplication

通过这种逐步缩小范围的方式,远比盯着报错日志猜来猜去高效。

3. 实操复盘:我修复过的三种真实场景

3.1 单模块项目:最简单也最容易忽略

先看一个很基础的单模块项目。项目结构如下:

demo-app/ ├── pom.xml └── src/main/java/com/example/demo/ └── DemoApplication.java

pom 里引入了spring-boot-starter-parent,也加了spring-boot-maven-plugin。按理说不会出问题,但有一个细节:DemoApplication类里的main方法可能是这样的:

public class DemoApplication { public void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }

看到了吗?main方法漏写了static关键字。虽然 IDEA 里右键也能运行(它会自动补充一些信息),但 Spring Boot 插件在 repackage 阶段扫描时,要求必须是标准的public static void main(String[] args)签名。漏了static,插件就不会认为它是一个合法的启动入口。

这类问题排查最快的方法,就是打开 IDE 的事件日志或者看编译输出有没有警告。当然,直接检查main方法签名也是一眼就能看出来的事。

3.2 多模块项目:父 pom 插件配置的精准控制

再来看一个多模块项目的实际案例。我参与过的一个项目结构大致如下:

cloud-platform/ ├── pom.xml (parent) ├── common-utils/ ├── order-service/ └── user-service/

父 pom 里做了这些事情:定义依赖管理、统一插件版本、声明spring-boot-maven-plugin。问题是,common-utils其实只是一个工具包,它不应该被 repackage。

当时的报错很典型:

[ERROR] Failed to execute goal org.springframework.boot:spring-boot-maven-plugin:2.7.8:repackage (repackage) on project common-utils: repackage failed: Unable to find main class

原因很清楚:父 pom 里声明了插件,而common-utils作为子模块默认继承了这个插件。解决办法有两种。

第一种,是在父 pom 里把spring-boot-maven-plugin放到<pluginManagement>中,而不是直接放在<plugins>中。<pluginManagement>只做版本管理,不会强制子模块继承。然后,只在真正需要打成可执行 jar 的order-service和user-service模块里显式声明插件。

第二种,是在common-utils的 pom 里显式跳过:

<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <skip>true</skip> </configuration> </plugin>

我个人倾向于第一种,因为从源头管住了插件的传播范围,子模块的职责更清晰。不过如果你的项目已经有很多模块,逐个改 pom 比较麻烦,第二种方案作为临时手段也完全可行。

3.3 微服务启动器与业务代码分离

还有一种模式,是启动类和业务代码分开放在不同模块或者不同目录里。最常见的就是你写了一个Application启动类,但又建了一个config包、controller包、service包,这些都没问题——只要所有代码都在同一个模块的编译范围内,插件就能找到启动类。

但有些项目为了保证启动加速、或者为了应对复杂的部署环境,会把启动类放在一个单独的bootstrap模块里,业务代码放在business模块中。这种情况下,bootstrap模块依赖了business模块,而需要执行 repackage 的是bootstrap模块。

只要你的bootstrap模块 pom 里配置了spring-boot-maven-plugin,并且mainClass指向该模块中的启动类,就不会有找不到主类的问题。关键点是:启动类必须在当前模块的 classpath 中。

如果启动类在business模块里,而你想在bootstrap模块里把它包成可执行 jar,那么bootstrap模块必须依赖business模块。这里需要留意的是,如果business模块也被声明了spring-boot-maven-plugin,且没有skip,它依然可能报错。所以,始终记住一条核心原则:只需要一个可执行 jar,那就只让一个模块做 repackage。

4. 一步步排查:不想再被报错折磨就看这里

4.1 先快速定位问题所在

我整理了一套排查顺序,直接照着走,大多数情况下能在五分钟内定位问题:

第一步,执行mvn clean package,记录完整报错信息,注意是哪个模块报的错。

第二步,检查该模块的 pom.xml,看是否引入了spring-boot-maven-plugin,以及有没有配置mainClass。

第三步,进入该模块源码目录,找到主类,确认main方法签名是标准的。

第四步,检查target/classes目录是否存在主类的.class文件。

第五步,如果以上都没问题,检查构建插件(如maven-compiler-plugin和maven-jar-plugin)是否有覆盖默认行为,比如把主类排除或改变了 jar 的 Main-Class 属性。

这套顺序看起来简单,但效率极高。大多数人的问题在第二步和第三步就能解决。

4.2 对多模块项目的针对性检查

多模块项目需要额外做一次"插件传播"检查。具体方法如下:

在父 pom 中查看spring-boot-maven-plugin是定义在<pluginManagement>里,还是直接定义在<plugins>里。

如果是后者,再逐一查看各子模块是否显式配置了<skip>true</skip>。

然后,针对每个子模块单独执行:

mvn spring-boot:repackage -DskipTests

观察哪些模块会报错,哪些模块成功。这样就能快速圈定问题范围,不用整个项目一起构建。

如果你的项目模块特别多,还可以用 Maven 的-pl参数只构建指定模块:

mvn package -pl order-service -am

-am的意思是同时构建依赖模块。这样执行速度快,排查也方便。

4.3 确认可执行 jar 是否真的可运行

即便报错排除了,打包成功后也建议做一次验证,避免交付出去的 jar 是个"半成品"。

java -jar target/order-service-1.0.0.jar

如果启动顺利,会看到 Spring Boot 的启动日志。如果启动时报错找不到主类或其他依赖,可以用下面命令查看 jar 包内部结构:

jar tf target/order-service-1.0.0.jar

重点检查是否有BOOT-INF/classes/目录和BOOT-INF/lib/目录。正常的 Spring Boot 可执行 jar 都包含这两个目录。如果没有,说明 repackage 没有真正执行成功——这可能是插件在 pom 中的声明位置不对,或者是执行顺序被覆盖了。

5. 避坑经验与实操心得

5.1 插件版本和父依赖版本要匹配

spring-boot-maven-plugin的版本通常会和spring-boot-starter-parent的版本保持一致。如果你手动指定了插件版本,却和 Spring Boot 版本不匹配,repackage 时的行为可能变得很诡异。比如某些较老的插件版本对 JDK 17 支持不佳,会有奇怪的扫描失败问题。

我的习惯是,除非有特殊需求,否则尽量使用 Spring Boot 父依赖统一管理的插件版本,不要手工指定:

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.18</version> <relativePath/> </parent>

这样插件版本自动对齐,省心。

5.2<mainClass>和<start-class>的关系

有读者可能见过另一种配置方式,是在 pom 的<properties>里设置<start-class>:

<properties> <start-class>com.example.demo.DemoApplication</start-class> </properties>

这两种方式最终的作用是相似的,Spring Boot 插件会优先读取<mainClass>配置,其次是<start-class>,最后才是自动扫描。如果你两种都配置了,而且值不一致,请以<mainClass>的配置为准进行排查。

5.3 注意 IDE 的"伪编译"迷惑

IDEA 的 Build 和 Maven 的 compile 并不完全等价。IDEA 有自己的编译器,可能把你修改后的代码编译到target/classes,即便 pom 配置有问题也不会立刻暴露。所以无论你在 IDEA 里构建成功多少次,最终判断标准都应该以命令行执行mvn clean package为准。

如果你习惯用 IDEA 的 Maven 面板操作,建议在 Lifecycle 里先执行clean,再执行package,不要省略clean。不 clean 的话,旧的 class 文件可能残留,掩盖部分问题。

5.4 多个 main 方法并存时的处理

项目里偶尔会写一些工具类,里面带main方法临时测试。这种情况下,建议这类类名不要和*Application结尾的启动类混在同一个包下,或者干脆把临时测试类放到src/test/java目录下,让它们不出现在最终的 classpath 里。

如果无法避免多个main方法并存,就给插件配置mainClass,让它明确知道启动入口在哪。

6. 常见问题速查表

问题现象可能原因解决方案
单模块项目报 Unable to find main classmain 方法签名非法检查是否为public static void main(String[] args)
多模块项目中报错模块没有主类插件在纯工具模块中执行了 repackage在公共模块显式配置<skip>true</skip>
报错模块没有配置任何插件父 pom 插件声明被继承将插件从<plugins>移到<pluginManagement>
启动类存在但插件仍找不到启动类不在当前模块的编译范围内调整项目依赖关系,让启动类所在模块作为可执行模块
打包成功但运行时报错找不到主类repackage 未真正执行检查是否有多个 spring-boot-maven-plugin 定义导致执行顺序异常
所有配置看起来都正常但仍报错本地 Maven 仓库缓存了旧插件配置执行mvn clean package -U强制更新快照

这张表是我在实际排查中最常用到的,很多问题本质上是同一个病因的不同表现。记住一个原则:遇到 repackage 报错,优先怀疑项目结构和插件配置,而不是业务代码,能帮你省下大量排查时间。

最后再分享一个我自己的小习惯:在项目的主 pom 里,我通常会为所有模块统一设置一个属性:

<properties> <java.version>1.8</java.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties>

这不是直接解决Unable to find main class的方法,但能够保证所有模块编译参数一致,避免因为编译版本不统一导致的次生问题。毕竟很多看上去很离谱的报错,背后可能只是某些环境差异被放大了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 13:38:42

医学CT图像肺炎分类实战:从DICOM预处理到Grad-CAM可解释部署

简介&#xff1a;本资源是一份面向高校计算机类专业学生的深度学习与计算机视觉课程设计实践项目&#xff0c;聚焦新冠肺炎医学图像分类预测任务&#xff0c;以Python为开发语言&#xff0c;兼顾教学性与工程可行性。项目完整包含可直接运行的源代码&#xff08;main.py、load_…

作者头像 李华
网站建设 2026/10/1 13:37:58

463个AI视频案例拆解:开源187个可复用Skill与提示语模版

1. 463个AI视频拆成Skill和提示语模版&#xff0c;这件事到底在解决什么问题先说说我为什么要干这件事。过去大半年&#xff0c;我几乎每天都在跟AI视频生成工具打交道——文生视频、图生视频、视频风格迁移、人物替换、超分修复&#xff0c;各种工具轮着用。用得多了就发现一个…

作者头像 李华
网站建设 2026/10/1 13:36:59

LLM智能自助分析系统搭建实战:从RAG到NL2SQL的工程化落地

最近我把内部的数据分析平台做了一次大改造&#xff0c;核心方向就是围绕“基于大模型&#xff08;LLM&#xff09;的智能化自助分析系统”这条路子展开。折腾了几个月&#xff0c;踩了不少坑&#xff0c;也沉淀了一些能直接复用的经验。这次就专门写一篇完整的搭建探索记录&am…

作者头像 李华
网站建设 2026/10/1 13:36:57

Jev哑巴模型爆火背后:代码生成与API接入实操指南

最近几天&#xff0c;打开任何一个人工智能相关的开发者群&#xff0c;几乎都能看到同一个名字&#xff1a;Jev。更魔幻的是&#xff0c;大家给它起了个外号&#xff0c;叫“哑巴模型”。第一次听到这个名字的人基本都会愣一下——哑巴&#xff1f;模型还能哑巴&#xff1f;等真…

作者头像 李华
网站建设 2026/10/1 13:36:57

基于LLM的智能自助分析系统:从Text-to-SQL到语义层落地实践

去年年初我们数据团队接了一个让我头疼很久的活儿&#xff1a;业务部门每天都在钉钉群里追着要数&#xff0c;今天问"华东区上个月退货率为什么涨了"&#xff0c;明天问"新客首单转化掉了几个点"&#xff0c;后天又问"帮我拉一下最近90天高价值用户的…

作者头像 李华
网站建设 2026/10/1 13:36:35

Java后端AI开发实战:LangChain4j核心概念与RAG集成指南

1. 为什么 Java 后端值得认真看一眼 LangChain4j 做 Java 后端的兄弟这两年应该都有同一种感觉&#xff1a;AI 应用这波浪潮&#xff0c;Python 那边热火朝天&#xff0c;LangChain、LlamaIndex 一套接一套&#xff0c;而自己手里攥着 Spring Boot 这套成熟到不能再成熟的技术栈…

作者头像 李华