资讯详情

Maven打包报错Unable to find main class:从根源到修复的全面指南

📅 2026/10/1 12:33:55 | 华诺云谱 👁 阅读
Maven打包报错Unable to find main class:从根源到修复的全面指南
先说明一个很常见的尴尬场景你在 IDEA 里写好了一个 Spring Boot 项目Application类里的main方法写得明明白白点击 Maven 面板里的package控制台却甩出这么一行[ERROR] Failed to execute goal org.springframework.boot:spring-boot-maven-plugin:2.7.5:repackage (repackage) on project demo: Execution repackage of goal org.springframework.boot:spring-boot-maven-plugin:2.7.5:repackage failed: Unable to find main class中文翻译过来就是“打包阶段重打包失败找不到主类”。但你心里很清楚主类就在那儿明明昨天还用同样的方式打出来了 jar。问题到底出在哪这个报错在 Maven 使用者的日常里出现频率极高几乎每个做 Spring Boot 多模块项目的人都至少碰过一两次。它牵扯到的不仅仅是“你有没有写 main 方法”而是 Maven 构建顺序、插件继承、编译产物目录、JDK 版本等多个层面的东西。这篇文章我就从底层机制开始把这个报错彻底拆开再按不同根因给出对应的修复方案最后复盘一次完整的多模块排查过程。1. 先还原报错现场这条“Unable to find main class”从哪一步冒出来老规矩先看报错的位置。整条报错的核心是spring-boot-maven-plugin:2.7.5:repackage注意这里的repackage是插件的 goal不是 Maven 的package生命周期。整个流程是这样的Maven 执行mvn clean package时package阶段会把项目编译后的 class 文件打成普通的 jar 包。打完这个普通 jar 后如果pom.xml配置了spring-boot-maven-plugin的repackage目标插件会紧接着对这个 jar 进行“二次加工”把它转换成 Spring Boot 的可执行 JAR。这个二次加工发生在同一个 package 阶段里顺序大致是compile - test - jar - repackage所以你在日志里通常会先看到spring-boot-maven-plugin ... repackage然后才看到失败信息。报错的触发点非常明确repackage 在处理已经生成的 jar 时没能在里面找到适合作为应用入口的主类。这就出现了一个认知上的常见偏差很多人以为“找不到主类”指的是编译器找不到去查源码、查目录结构其实编译器早就把源码编译成 class 了问题出在 later 的重打包阶段。先记住一句话这个错误跟你的源码“里有没有” main 方法没有必然关系它关心的是插件有没有能力从构建产物中“提取到”一个合法的启动类。源码里有不代表插件一定能认出来。另外这个报错并不是只有命令行执行mvn package才会出现。你在 IDEA 的 Maven 面板里双击package、在 CI 脚本里跑构建、甚至某些开发者在配置 Docker 镜像构建流程时启用 Maven 打包步骤都可能踩到完全相同的一行错误。报错虽然只有一句话背后对应的项目状态却可能五花八门下面我们就先把 repackage 的查找逻辑彻底讲透。2. repackage 的查找机制Maven 是怎么在 class 文件里“找”主类的要解决这个报错就必须知道 repackage 到底怎么找主类、按什么规则判断一个类能不能当主类。Spring Boot 的可执行 JAR 和普通 JAR 有一个核心区别普通 JAR 只是把项目自身编译后的 class 文件和资源文件压缩在一起而 Spring Boot 可执行 JAR 需要让java -jar能直接启动整个应用所以它必须额外写入两类信息Main-Class: org.springframework.boot.loader.JarLauncher Start-Class: com.example.demo.DemoApplicationMain-Class是 JVM 启动时的真正入口Spring Boot 会用自己的JarLauncher来负责加载嵌套在 BOOT-INF/lib 里的依赖 jarStart-Class才是你自己写的那个带 main 方法的应用类。repackage 过程中插件必须先拿到Start-Class的完整类名才能把它写进 MANIFEST.MF否则这个可执行 JAR 就缺少启动入口。插件获取主类名的顺序也很关键先看pom.xml里 spring-boot-maven-plugin 的configuration中是否显式配置了mainClass。如果配置了插件直接用这个类名不再扫描。如果没有配置插件会自动扫描target/classes目录下的所有.class文件。扫描规则是找public static void main(String[] args)方法方法名必须是main参数必须是String[]修饰符必须是public static void这个匹配是严格到字节码级别的。找到符合条件的类作为Start-Class写入清单文件。这里有几个非常典型的反例有人把main方法写成了public void main(String[] args)少了static编译不会报错但插件不认。有人写的是public static void main(String args)参数不是数组同样编译能通过插件还是看不出这是入口。有人把主类写在了没有public修饰的类里或者把 main 方法放进了一个普通工具类中识别阶段也可能失败。插件在扫描阶段找不到可以匹配的类就会抛出Unable to find main class。同时如果模块里存在多个 main 方法不同版本的处理方式也不一样有的版本会选第一个有的版本会直接抛异常。但无论哪种情况工程实践上都不该依赖“碰运气”让它随便选一个当入口正确做法永远是显式声明主类或者保证模块里只有一个可执行的 main 方法。所以问题的本质可以归纳成一句话repackage 需要一个明确的启动入口要么你告诉它在哪要么它自己能在编译产物里精准地找到唯一一个合法的入口。3. 按根因分类修复从改配置到重新设计模块结构了解了查找机制后再去看各种报错场景就会清晰很多。下面按照实际项目里最常见的几类根因给出对应的解决方案。3.1 应用模块的主类没被插件识别先显式声明 mainClass最直接的情况项目本身是一个 Spring Boot 应用主类也有但因为某些原因插件扫描不到这时候最省事的办法就是在pom.xml里显式指定主类。build plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId configuration mainClasscom.example.demo.DemoApplication/mainClass /configuration /plugin /plugins /build如果你使用了spring-boot-starter-parent作为父工程还有一个更偷懒的方式是通过属性指定start-classproperties start-classcom.example.demo.DemoApplication/start-class /properties这里要注意一个细节start-class是 Spring Boot 定义的属性名在spring-boot-starter-parent里被声明成了可配置项而configurationmainClass是 spring-boot-maven-plugin 插件自己的配置参数。两者都能影响最终Start-Class的写入但作用范围略有不同。项目中如果同时出现两者且值不一致以插件配置里的mainClass为准。显式声明的另一个好处是如果模块里恰好有多个 main 方法不管插件版本的处理策略是什么你都可以锁定真正的启动类彻底避免插件“选错入口”带来的一系列奇怪问题。3.2 纯库模块被误加了打包插件用 skip 参数跳过重打包这个场景在微服务项目里特别常见。比如你的工程里有common、dal、utils这类基础模块它们内部根本没写 main 方法也不可能单独启动。但如果你在父工程的buildplugins里直接声明了spring-boot-maven-plugin这个插件会被所有子模块继承结果是每一次构建到这些普通 jar 模块时repackage 都会去扫描主类扫不到就直接报错。解决办法有两种选哪一种取决于你的工程结构如果你只希望某个模块不触发 repackage就在该模块的插件配置里加上skipbuild plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId configuration skiptrue/skip /configuration /plugin /plugins /build如果整个父工程下面的子模块只有少数几个是可启动应用我更推荐的做法是把插件从父工程的plugins挪到真正的应用模块中去。这样普通模块连插件都不会继承自然也不会执行 repackage省得为每个库模块都写一遍 skip。!-- 父 pom 中只做版本管理 -- build pluginManagement plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId /plugin /plugins /pluginManagement /build !-- 仅应用模块 pom 中声明使用 -- build plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId /plugin /plugins /build这里特别提醒一句不要以为删除普通 jar 模块里的 main 类就能解决真正的问题是“一个不需要可执行 jar 的模块根本不该执行 repackage 这个动作”。你可以在mvn package之后去看 target 目录下生成的 jar 文件大小如果模块里没有任何依赖但突然冒出一个几十 MB 的 fat jar那基本可以确定是 repackage 在这个模块上也生效了。3.3 多模块依赖顺序和父工程插件继承引起的“连锁反应”再来看一个更容易混淆的情况多模块项目里应用模块 A 依赖普通模块 BB 模块自己又因为继承了上面的插件而需要执行 repackage。构建时 Maven 会先构建 BB 构建到 package 阶段就报Unable to find main class导致整体构建中断。很多人第一反应是去查 A 模块的主类查了半天才发现报错信息里写的是 B 模块。遇到这种情况最有效的方式是第一时间定位报错中的on project xxx字段。比如[ERROR] Failed to execute goal ... on project common: ...这个common才是要处理的对象。然后判断它到底是不是一个需要被打成可执行 JAR 的模块不是就按前面 3.2 节的思路处理是才进入主类排查的流程。还有一种比较隐蔽的坑有些模块本身是已经打好的可执行应用但依赖方把它当成普通 jar 引进来。这种情况项目会莫名其妙地多出一个“启动另一个应用”的副作用同时如果同一个仓库里有多个可执行模块构建顺序也可能不稳定。规范做法是多模块仓库中保证只有最顶层的应用子模块是可执行的其余模块一律按普通 jar 处理。3.4 JDK 版本不匹配引发的“伪报错”这个坑很少有人第一时间想到但它真实存在。Spring Boot 插件在用字节码方式扫描 class 文件时需要能正常读取 class 文件结构。如果你的 Maven 运行在一个 JDK 版本而项目编译参数又指定了另一个版本或者编译器产出的 class 版本过高插件读取阶段就可能出现底层异常偶尔会连带表现为Unable to find main class。最常见的场景是本地JAVA_HOME是 JDK 17项目pom.xml里java.version写的是 8但maven-compiler-plugin没有正确设置release参数。编译产物实际是 JDK 17 的 class 文件而 Spring Boot 2.x 某个版本的 repackage 读取这些 class 时出现兼容问题。所以遇到这个报错不要光盯着主类顺手检查一遍环境也是分内的事java -version mvn -versionmvn -version输出里能看到 Maven 当前使用的 Java 版本。如果它和java.version相差过大先统一版本再重新打包试试。这里我建议在pom.xml里明确加上 compiler 的release参数properties java.version17/java.version maven.compiler.release17/maven.compiler.release /propertiesmaven.compiler.release的作用是告诉编译器生成的 class 文件必须符合对应 JDK 版本的规范这样比只设置source/target更严格也能避免一些跨版本编译导致的问题。4. 命令行 IDEA 双路径排查一次多模块实际踩坑过程演示很多朋友遇到这种报错习惯直接搜解决方案但我更建议你先花五分钟把问题定位清楚。下面是我自己用的排查流程基本能覆盖 90% 的情况。第一步先看报错里的模块名。命令行执行完整构建时注意on project xxx后面的值。如果这个模块根本不该启动直接加skiptrue或移除插件即可。第二步进入报错模块查看编译产物中是否存在主类。先执行mvn clean compile然后确认 class 文件是否生成ls target/classes/com/example/demo/DemoApplication.class如果没有这个文件说明源码编译或目录结构有问题比如src/main/java路径被错误修改、源码被 IDE 排除了编译范围等。从 Git 最近改动里对比一下往往很快能找到原因。第三步class 文件存在继续用javap验证 main 方法签名javap -p -classpath target/classes com.example.demo.DemoApplication正常情况下输出里有这么一行public static void main(java.lang.String[]);如果看到void main(java.lang.String[])缺少public static那插件当然不认。如果看到的是泛型或别的参数类型同样不符合要求。第四步如果这些都没有问题再检查是不是多模块共用父 pom 导致的插件继承问题。最直观的方式是单独进到应用模块目录执行mvn package -pl demo -am然后观察是不是只构建应用模块本身时也会报错。如果单独构建成功、整体构建失败那就是模块之间的插件继承关系在捣乱。在实际排查中我还会经常用到一个快速判断方式直接解压打出来的普通 jar 看看有没有主类声明。因为 repackage 是在普通 jar 的基础上处理的如果生成的原始 jar 里 MANIFEST.MF 都没有Main-Class那 repackage 自然无从下手。在 IDEA 里排查的时候有两点容易踩第一IDEA 自身的 Build 和 Maven 构建不是一回事。如果你在 IDEA 里先执行了 Build ProjectIDEA 会把 class 输出到out/目录但 Maven 打包用的是target/目录。这时候最好先双击 Maven 面板的clean再执行package避免老旧的目标文件干扰判断。第二IDEA 对 Maven 项目的自动导入偶尔会“假成功”。如果pom.xml改了插件配置但没有触发 Maven reload实际生效的仍是旧的依赖关系。看到 IDEA 右下角提示Maven projects need to be imported时点一下刷新再试这个细节省时省力。为了更直观地对照问题根因和解决方案我把常见的几种情况整理成了表格报错场景根因最直接的处理单模块 Spring Boot 应用主类存在但报错插件没扫描到主类显式配置 mainClass / start-class普通工具模块继承打包插件不需要 repackage加 skip 参数或移除插件多模块整体构建失败报错模块不是应用模块模块被错误地执行了 repackage将插件声明限制到真正的应用模块主类存在且签名正常仍扫描失败JDK 版本或编译产物不兼容统一 JAVA_HOME 和 java.versionclean 后重打主类签名不正确main 方法缺 static 或参数错误修正 main 方法签名target 目录旧文件残留重复构建导致元数据混乱先执行 mvn clean 再 package表格里的这些路径基本覆盖了我见过的绝大多数情况剩下的属于插件自定义程度特别高的项目那就需要去对照 spring-boot-maven-plugin 的官方文档里mainClass、skip、classifier这几个参数的具体语义了。5. 避开这块“第 N 次踩坑的感受和之后的习惯”入行这么多年我用 Maven 打包踩过的坑里有三分之一都和 repackage 有关。第一次碰到这个报错的时候我还应当是在给一个老旧的 Spring Boot 1.x 项目升版本彼时对“主类明明存在却被说找不到”的困惑相当大。后来一步步翻源码、对照文档才算把这个机制理顺。这里分享几个我自己固定的例行习惯能大大减少踩坑概率习惯一每个模块的职责要清晰。同一个 Maven 仓库里我会严格区分“可执行应用模块”和“被依赖的库模块”。库模块的 pom 里不继承 spring-boot-maven-plugin除非它确实需要被打成可执行 jar。这样的结构从源头杜绝了库模块触发 repackage 的可能。习惯二不在父 pom 的 plugins 里声明会执行 repackage 的插件。父 pom 只放 pluginManagement负责锁定版本和默认配置具体要不要启用留给各个子模块自己决定。这招对多模块项目的可维护性提升非常明显。习惯三给应用模块显式声明 mainClass。即使当前模块里只有一个 main 方法我也会写上。因为项目后期大概率会加入一些带 main 的调试工具类、批处理入口到时候多入口并存不写 mainClass 的话插件行为就不稳定运气不好就会打包出一个错误入口的可执行 jar。习惯四每次构建前先 clean。这个听起来像是在说废话但实际项目中因为 target 目录残留旧 class 文件导致的诡异问题我至少见过十次以上。Maven 构建不是 IDE 增量编译不 clean 的情况下某些被删除的类可能在旧 jar 里继续存在干扰 repackage 的判断。最后再说一个很多人不知道的小点如果你确实不需要 repackage又不想改太多配置可以用classifier方式绕过configuration classifierexec/classifier /configuration它会额外生成一个xxx-exec.jar作为可执行包原始 jar 则保持普通库 jar 的形态。这样你的模块既能被别人当依赖引用也能独立启动。不过这个方案对团队协作不那么友好因为命名变了之后依赖方引用的坐标可能也要跟着调所以不是特殊情况我一般还是会优先选择按职责拆分模块和 skip 方案。Maven 的报错信息有时候很直接有时候又特别容易让人绕远路Unable to find main class就属于后者。它表面在说“没找到主类”实际上大部分时候是“构建配置没有正确告诉插件主类在哪里”。理解了 repackage 的工作方式再按报错模块、编译产物、插件配置、JDK 版本这个顺序排查问题基本都能在一个小时内解决。下次再看到这行红色报错你已经知道它真正想说的是什么了。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑