简介IDEA打包可执行jar文件时“找不到或无法加载主类 main”是高频报错之一这份PDF面向Java开发者与Maven项目使用者系统梳理从原因定位到实际解决的完整路径。文档重点涵盖三种方案在pom.xml中配置maven-jar-plugin并声明主类全限定名、检查与修正Windows系统CLASSPATH环境变量包括直接删除该变量以恢复自动类路径、以及通过Project Structure的Artifacts功能构建可运行jar。包体为1个PDF文件容量约258KB内容紧凑附有可复制的XML配置示例与分步操作说明。资源发布至今已有20876人浏览学习除了打包阶段的排错还补充了已编译class文件仍报找不到类时的环境变量处理技巧对需要快速定位配置问题、理解Jar清单与类路径机制的开发者而言是一份可直接对照操作的实用参考。1. IDEA打包jar报“找不到或无法加载主类”先分清三类原因再动手在 IntelliJ IDEA 里点一下 Build Artifacts看到控制台把 main 方法的结果打印出来你以为这个 jar 已经能交付了结果换台机器java -jar xxx.jar迎面就是“错误: 找不到或无法加载主类”。这个问题不是玄学也不是 IDEA 坏了而是可执行 jar 的三道门槛里至少有一道没到位MANIFEST.MF 里有没有写对 Main-Class、这个类在 jar 里到底存不存在、它依赖的库有没有一起带上。本文就沿着这三条线把 IDEA 打包 jar 的完整路径拆开从原理到最小复现再到 Maven 脚本和一套能落地的校验命令新手照着做能交差熟手也可以拿来当排查清单用。2. 从“能编译”到“能运行”可执行 jar 的构成与最小复现2.1 java -jar 到底在看什么Main-Class、Class-Path 与 jar 包结构很多人有个误解在 IDEA 里能运行就等于打出来的 jar 也能运行。这两件事的类加载路径完全不同。IDEA 运行程序时是把编译后的target/classes目录和你在 Project Structure 里导入的依赖 jar 一个不落地拼进 classpath然后 JVM 按 classpath 去加载com.example.demo.Main而java -jar启动规则是另一套JVM 只打开你指定的 jar 文件读取里面的META-INF/MANIFEST.MF先看Main-Class属性再去 jar 包内部找这个类。也就是说jar 包本质是个 zip里面装的是目录结构化的.class文件。JVM 对“可执行 jar”的期望是入口类的全限定名写在清单文件里入口类本身以对应路径存在 jar 内。如果这两个条件不满足它就只能对着命令行抛一句“找不到或无法加载主类”。至于你用 IDEA 右键导入的那些外部依赖 jar它们叫编译期依赖只在你 IDE 的 classpath 里不会自动进你的输出物除非打包时明确处理。顺便提一句java -jar模式下命令行里的-cp参数会被忽略jar 包的 Class-Path 才有效。这是很多人第一次翻车的地方——本地怎么加 classpath 都没用因为 jar 内部的清单没有告诉你 JVM 该去哪儿找包。理解了这一层后面所有坑都能对上号。2.2 复现报错的最小工程用 IDEA 的 Artifacts 打一次 jar要搞懂这个问题最快的办法是自己复现一次。新建一个空 Java 工程写一个最简单的入口类package com.example.demo; public class GreetingMain { public static void main(String[] args) { String name args.length 0 ? args[0] : IDEA; System.out.println(hello, name); } }逻辑很简单main 方法接收一个可选参数没有就用默认值。注意这个类带了包名com.example.demo这个包名后面会是 MANIFEST 里 Main-Class 的关键部分。接着在 IDEA 里按下面步骤操作File → Project Structure → Artifacts点选JAR → From modules with dependencies。Main Class 选com.example.demo.GreetingMain。注意Directory for META-INF/MANIFEST.MF这一项千万别指到src/main/java下否则会污染源码目录我一般新建src/main/resources/META-INF。回到编辑器Build → Build Artifacts → Build输出文件默认在out/artifacts/项目名_jar/下。java -jar out/artifacts/demo_jar/demo.jar很多人在这一步就会看到“错误: 找不到或无法加载主类”。原因可能是 IDEA 给你生成的 MANIFEST 写错了入口也可能你只拷走了单个 jar、漏掉了同目录的lib/文件夹。IDEA 的默认布局会把依赖放在 jar 外的lib目录再靠 MANIFEST 里的Class-Path指过去这意味着整个输出目录必须一起挪不能只搬 jar。2.3 先验证再谈修复用 jar tf 和 unzip -p 看清单文件报错之后别急着改配置先把 jar 里到底有什么看一遍。jar 就是 zip所以jar tf和unzip都能用。我一般固定跑这一组命令jar tf demo.jar | grep -E META-INF/MANIFEST.MF|com/example/demo/GreetingMain.class unzip -p demo.jar META-INF/MANIFEST.MF | tr -d \r第一条命令检查两件事清单文件在不在、主类对应的.class文件在不在。第二条命令直接看 MANIFEST 内容tr -d \r是为了去掉 Windows 换行符避免后面用脚本解析时踩到回车符的坑。如果看到Main-Class: com.example.demo.GreetingMain且.class文件路径确实是com/example/demo/GreetingMain.class那问题多半出在依赖上如果 Main-Class 后面是空的或者写的是GreetingMain这种没有包名的简写问题就出在清单文件上。这一步做完锅在谁身上基本就清楚了。3. 改对 MANIFEST.MF主类配置的四种写法与三个坑3.1 包名别落下Main-Class 必须写全限定名最常见的低级错误是 Main-Class 只写了类名没写包名。比如你的类是com.example.demo.GreetingMain但 MANIFEST 里写的是Main-Class: GreetingMain。JVM 在 jar 内部找GreetingMain.class当然找不到因为实际路径是com/example/demo/GreetingMain.class。正确写法就一行Manifest-Version: 1.0 Main-Class: com.example.demo.GreetingMain注意两点第一不能带.class后缀第二如果类在默认包下直接写类名没问题但只要你在真实项目中几乎都会有包名所以这个坑比想象中高发。还有一种写法是复制 IDEA 运行配置里的“Main class”下拉框值那个值通常是对的但手工敲的时候容易漏漏了就从“能编译”变成“运行不了”而且编译期不会给你任何提示。3.2 main 方法的签名为什么“编译能过、运行报错”Java 编译器检查的是语法不会校验“这个类适不适合当程序入口”。比如下面这三个写法都能编译通过public class WrongMain { public void main(String[] args) { } }public class WrongMain2 { public static void main() { } }public class WrongMain3 { static void main(String[] args) { } }但 JVM 查找入口时要求的是精确签名public static void main(String[])一个修饰符都不能少。void返回值、String[]参数、public static缺一不可。否则你会收到“错误: 在类 com.example.demo.WrongMain 中找不到 main 方法”。用javap可以验证编译后的真实签名javap -classpath demo.jar com.example.demo.GreetingMain它会打印出类的公开方法列表你看一眼有没有public static void main(java.lang.String[])。这个方法比反编译 jar 更快不需要打开任何工具命令行一行就解决。以后但凡怀疑入口签名有问题先跑 javap别靠肉眼猜。3.3 在 IDEA 里设置 Main Class 的生效边界IDEA 的 Artifacts 配置界面里有一个 Main Class 选择框很多人以为选了就万事大吉。实际上它的作用是帮你生成 MANIFEST而生成时机是 Build Artifacts 的那一刻。如果你后来改了入口类的包名、改了类名或者把入口类从一个模块移到了另一个模块旧 jar 里仍保留旧配置。我遇到过最典型的情况项目重构后把Main类从demo.old.Main挪到demo.new.LauncherIDEA 里运行没问题因为运行配置自动修正了但 Artifacts 配置里的 Main Class 还是旧值重新 Build 出来的 jar 依然写着Main-Class: demo.old.Main自然找不到。解决办法每次大改结构后回 Project Structure 里确认一遍 Artifacts 的 Main Class。另外如果你同时用 Maven 或 Gradle 构建IDEA Artifacts 的产物在out/Maven 的产物在target/两者没关系别搞混。3.4 手动修改 MANIFEST.MF用 jar 命令打补丁如果你拿到了一个别人打好的 jar不想重新打包只想临时修正 Main-Class可以手写一个 manifest 文件然后用jar命令更新进去。printf Main-Class: com.example.demo.GreetingMain\n\n manifest.txt jar ufm demo.jar manifest.txtufm三个字母解释一下u是更新已有 jarf指定 jar 文件名m表示使用外部 manifest 文件。这个命令会把manifest.txt里的内容合并进 jar 内部的META-INF/MANIFEST.MF效果等价于重新打包一次但快得多。这里有一个必须注意的细节manifest.txt最后要有换行很多手写文件漏了结尾回车导致最后一行属性被忽略jar命令也不报错你查 MANIFEST 却看不到 Main-Class。所以我上面在printf里专门加了两个\n一个结束本行一个保证文件以空行收尾。这个习惯能帮你避开一个很隐蔽的坑。4. 依赖没带上从 java -cp 到带依赖的 fat jar4.1 最朴素的验证用 java -cp 把 classpath 摆平如果 MANIFEST 和主类签名都没问题但运行还是报“找不到或无法加载主类”下一个嫌疑就是依赖缺失。主类本身可能没问题但它引用的第三方类在 jar 里没有JVM 在加载主类时解析符号引用失败照样会报错。这时候最直接的验证办法是绕开java -jar自己显式拼 classpathmvn dependency:copy-dependencies -DoutputDirectorylib java -cp target/classes:lib/* com.example.demo.GreetingMain第一条命令把 Maven 依赖拷到lib目录第二条命令把编译产物和依赖一起放进 classpath然后手动指定完整主类名。lib/*这个通配符是 Java 6 以后支持的 classpath 通配不是系统 shell 展开的别加引号反而会被 shell 拆成多个参数。如果这样能跑起来就说明代码本身没问题问题100%在打包环节依赖没进 jar或者 MANIFEST 的 Class-Path 没指向依赖所在位置。4.2 用 maven-jar-plugin 把依赖写进 Class-Path解决依赖问题有一个过渡方案用maven-jar-plugin生成标准 jar再用maven-dependency-plugin把依赖拷贝到 jar 旁边的lib目录最后让 MANIFEST 里写入Class-Path。plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-jar-plugin/artifactId version3.4.1/version configuration archive manifest mainClasscom.example.demo.GreetingMain/mainClass addClasspathtrue/addClasspath classpathPrefixlib//classpathPrefix /manifest /archive /configuration /plugin plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-dependency-plugin/artifactId executions execution idcopy-dependencies/id phasepackage/phase goals goalcopy-dependencies/goal /goals configuration outputDirectory${project.build.directory}/lib/outputDirectory /configuration /execution /executions /plugin这里addClasspath让 Maven 自动生成 Class-Path 属性classpathPrefix告诉它依赖都放在lib/相对目录下。打包后target/下会出现demo.jar和lib/文件夹两个必须一起拷走。这个方案的问题很明显交付物不是一个文件而是多个文件目录结构一乱Class-Path 就失效。所以我只在快速验证时用正式交付我还是偏好下一步的 fat jar。4.3 用 maven-assembly-plugin 打带依赖的 fat jarfat jar 的意思是所有依赖的.class都解开后重新塞进同一个 jar这样最终只有一个文件。maven-assembly-plugin是干这件事最省事的方式plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-assembly-plugin/artifactId version3.6.0/version configuration archive manifest mainClasscom.example.demo.GreetingMain/mainClass /manifest /archive descriptorRefs descriptorRefjar-with-dependencies/descriptorRef /descriptorRefs /configuration executions execution idmake-assembly/id phasepackage/phase goals goalsingle/goal /goals /execution /executions /plugin执行mvn clean package -DskipTests产物在target/下文件名是项目名-jar-with-dependencies.jar。这个 jar 可以直接拷走不需要额外带任何目录。参数说明descriptorRefs里的jar-with-dependencies是插件内置的描述符含义就是把 project 的依赖全部解压合并phasepackage绑定了生命周期节点跑mvn package就会自动触发不用额外执行assembly:single。如果你的项目里某些依赖有签名文件META-INF/*.SF合并时会有安全警告可以在插件配置里加filters排除掉但一般命令行工具不用管。4.4 Spring Boot 项目打包repackage 与 Main-Class 的自动改写如果你用 IDEA 建的是 Spring Boot 项目上面所有方案都不适用。Spring Boot 的 jar 是特殊结构入口类不是你的业务 Main而是它的启动器。plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId executions execution goals goalrepackage/goal /goals /execution /executions /plugin这个插件在你的 Mavenpackage阶段做一次 repackage把项目的普通 jar 改造成 Spring Boot 可执行 jar。打完之后看 MANIFEST你会发现 Main-Class 是org.springframework.boot.loader.JarLauncher而你的业务主类被写进了另一个属性Start-Class。很多人第一次看到这里就以为打错了其实是正常的。Spring Boot 的启动机制是JVM 先加载 JarLauncher由它去处理BOOT-INF/classes和BOOT-INF/lib的内部类路径再反射调用你写的Start-Class。所以 Spring Boot 项目里依赖永远不会丢因为它的 loader 设计就是专门解决这个问题的。如果你不想用 Spring Boot 插件的 repackage 而想用 assembly能打出来但跑起来会有一堆问题不值得。Spring Boot 项目直接用插件默认配置就好IDEA 的 Artifacts 对它基本无用。5. IDEA 打包 jar 避坑检查单4 条血泪经验5.1 现象IDEA 里能跑命令行 java -jar 就报错这是我看到最多的求助帖IDE 里运行一切正常打成 jar 后命令行立刻“找不到或无法加载主类”。原因通常是两个。一是 IDEA 运行时用的 classpath 是编译产物的路径加依赖列表跟 jar 没关系二是 IDEA Artifacts 默认把依赖放 jar 外的lib/目录只拷单个 jar 等于裸奔。解决先按 2.3 的命令查 MANIFEST确认 Main-Class 正确再把out/artifacts/整个目录拷走包括lib/如果交付要求只是单个文件改用 4.3 的 assembly 插件重新打 fat jar。5.2 现象用 Maven 打包后自定义 MANIFEST 被覆盖你在src/main/resources/META-INF/MANIFEST.MF里手写了 Main-Class跑完mvn package打开生成的 jar发现配置没了或者变成 Maven 默认的版本号。原因Maven 的maven-archiver会重新生成 MANIFEST默认不读 resources 目录下的那个文件。你放在 resources 下的 MANIFEST 会被原样复制到 jar 里但 Maven 自己生成的清单在打 jar 时会被maven-jar-plugin覆盖掉或合并掉。解决别在 resources 下手写 MANIFEST把入口配置写进maven-jar-plugin的manifest配置段这是 Maven 的正式入口resources 下那份只用来放自定义属性。如果你真的很依赖手写文件用maven-archiver的manifestFile参数指定来源但优先级逻辑很绕不建议第一次就碰。5.3 现象主类加载失败错误信息却是 ClassNotFoundException 或 NoClassDefFoundError“找不到或无法加载主类”是一个统称它背后的原始异常可能是ClassNotFoundException、NoClassDefFoundError。如果你的主类在静态代码块里引用了外部类而那个外部类不在 jar 里异常会在入口类加载阶段提前抛出来。原因JVM 加载主类时如果主类的常量池里引用了一个缺失的类且这个引用在验证或解析阶段就需要被解析就会触发 NoClassDefFoundError。表面看是“找不到主类”实际是主类的依赖丢了。解决不要只在入口类上做文章打开 MANIFEST 的 Class-Path 检查依赖位置或者用 4.3 的 fat jar 彻底解决。还有一种廉价排查法把main方法里的业务代码全部注释只留一个空壳如果空壳能跑说明问题就在依赖或静态初始化逻辑上。5.4 现象换电脑后报错 UnsupportedClassVersionError这类场景在团队协作里很常见你本机跑得好好的同事拿到 jar 一跑直接报“无法加载主类”或UnsupportedClassVersionError。原因你用的 JDK 版本比对方高。比如你在 JDK 17 下编译的 class 文件是 major version 61对方只有 JDK 8JVM 不认这个版本格式自然加载不了。解决在 Maven 里显式统一编译版本加上maven.compiler.source和maven.compiler.target并且交付时告诉对方最低 JDK 要求。我现在的习惯是项目根 pom 里写死properties maven.compiler.source1.8/maven.compiler.source maven.compiler.target1.8/maven.compiler.target /properties这样不管本地装了多少版本的 JDK编译出的 class 文件都是 Java 8 兼容格式。注意这只是编译器指令不代表你能在代码里用 JDK 17 独有的 API那种情况编译期就会报错。6. 把“能跑”变成“能交付”一个校验脚本与两个验证习惯我现在每打出一个可执行 jar都会顺手跑一个小脚本确认三件事清单存在、主类存在、签名正确。脚本贴在下面保存成check-jar.sh直接用。#!/usr/bin/env bash set -euo pipefail JAR${1:?usage: check-jar.sh your.jar} echo META-INF/MANIFEST.MF unzip -p $JAR META-INF/MANIFEST.MF || { echo FAIL: no manifest; exit 1; } main_class$(unzip -p $JAR META-INF/MANIFEST.MF \ | tr -d \r \ | sed -n s/^Main-Class:[[:space:]]*//p \ | head -n1) if [ -z $main_class ]; then echo FAIL: Main-Class missing exit 1 fi class_path${main_class//./\/}.class if unzip -l $JAR | grep -q ${class_path}$; then echo PASS: ${class_path} exists else echo FAIL: ${class_path} not found exit 1 fi if javap -classpath $JAR $main_class \ | grep -q public static void main(java.lang.String\[\]); then echo PASS: main signature ok else echo FAIL: main signature not recognized exit 1 fi脚本逻辑不复杂sed从 MANIFEST 里抽出 Main-Class 的值main_class把包名里的点替换成路径分隔符拿去和 jar 条目比对最后用javap确认入口签名。Spring Boot 项目看 Main-Class 会过但 Start-Class 没验证配合 4.4 自己加一段即可。两个验证习惯是我翻车多年换来的。第一个习惯任何入口类的修改打包前先在命令行用java -cp跑一次完整类名确认 classpath 方案能通再做 jar运行时错和打包时错不要混在一个阶段查。第二个习惯每次打完 jar把 2.3 和 6 的命令当成打包动作的一半脚本过了才发给别人脚本没过立刻查清单或依赖不靠运气试。IDEA、Maven、依赖管理这些工具说到底只是帮你生成文件真正决定 jar 能不能跑的是清单和类路径。现在每遇到“找不到或无法加载主类”我都按这套流程走一圈基本十分钟内定位。希望帮到你。本文还有配套的精品资源点击获取