资讯动态

IDEA打包JAR提示“找不到或无法加载主类”?一文彻底排查与修复

发布时间:2026/10/22 7:43:12 来源:尧图企业网站定制
简介一份面向Java开发者的PDF资料针对IntelliJ IDEA中打包可执行jar时提示“找不到或无法加载主类 main”的常见报错系统整理从原因判断到配置修复的完整思路。文档先解释JVM查找入口方法的机制再给出Maven项目通过maven-jar-plugin在pom.xml中声明mainClass与addClasspath的配置示例同时覆盖IDEA自带的Build Artifacts图形化打包方式。对命令行运行java -jar后仍失败的情况也分析了Windows下CLASSPATH环境变量可能造成的干扰并提供修改或移除该变量的建议。资源包含1个PDF文件大小约258KB内容精炼、步骤明确适合正在用IDEA开发或部署Java应用的初中级开发者快速查阅。目前已有20877人学习下载对排查同类启动异常有直接参考价值。1. 为什么 IDEA 打包的 jar 总是提示「找不到或无法加载主类 main」IDEA 打包 jar 后双击运行结果弹窗提示「找不到或无法加载主类 main」这个报错几乎每个用 IntelliJ IDEA 做桌面工具或命令行小程序的开发都撞到过。明明代码在 IDEA 里能跑main 方法就写在眼皮底下打包后却像失忆一样找不到入口。这个问题不是玄学根源在于 jar 包内的 MANIFEST.MF 里没有正确记录入口类或者记录的位置和你实际的包名不一致JVM 按图索骥却找错了门牌号。本文从打包机制讲到三种修复路径再给你一份验证清单跟着走完能让你从报错就慌变成看一眼 jar 内部结构就知道问题在哪。适合所有用 IDEA 做 Java 应用、又被构建产物折磨过的开发者。2. 理解 MANIFEST.MF 与 Class-Path主类加载失败的根本机制2.1 jar 包启动时 JVM 到底在找什么当你执行java -jar app.jar时JVM 不会去扫描 jar 里的所有 class 文件猜哪个有 main 方法它只读取META-INF/MANIFEST.MF这个清单文件从中找到Main-Class属性。这个属性的值必须是类的全限定名比如com.example.Main而且不能带.class后缀。如果这个属性缺失、拼写错误、包名写错JVM 就直接抛出「找不到或无法加载主类」。更隐蔽的是即便Main-Class写对了如果你的main方法签名不是标准的public static void main(String[] args)JVM 也能找到类但报main方法找不到。有次帮同事排查他写的 main 方法带了throws Exception这没问题问题是他把方法写成了public static void main(String args)少写了一个中括号IDEA 里能运行是因为它直接调用了编译后的字节码容错机制和命令行的严格校验不一样。这种情况报错信息还是老样子但根本原因完全不同。还有一个容易忽略的细节JDK 9 之后的模块化系统对 jar 的启动路径也有影响但大多数普通项目用默认的 classpath 模式不会触发模块化限制。只要你的项目不是模块化 JavaFX 或 JPMS 结构的排查重点还是放在Main-Class和实际编译输出目录是否一致上。具体来说JVM 启动时的查找顺序是这样的打开 jar 包读取 MANIFEST.MF解析Main-Class属性然后根据该类的类加载器去搜索对应 class 文件。如果 class 文件存在于 BOOT-INF/classes 这种非标准位置Spring Boot 项目还需要依赖 Spring Boot 的自动配置机制这又引出了另外一类问题后面章节会提到。2.2 IDEA 打包的三种方式哪个最容易翻车IDEA 里打包 jar 常见有三种方式直接用 Artifacts 构建、用 Maven 的 package 生命周期、用 Gradle 的 jar 任务。我实际经验是最容易翻车的是 Artifacts 方式因为它高度依赖你手动勾选的路径选择器Classpath 和 Output Layout任何一步选错打出来的 jar 要么没有 Main-Class要么依赖没打进去。Artifacts 方式下IDEA 会生成一个META-INF/MANIFEST.MF但它的位置默认在项目的根目录而非src/main/java里而且当你选择copy to output directory and link via manifest时IDEA 只在编译输出里放一份。如果你在 Project Structure 里改了模块名或者移动了源码包的根路径之前生成的 manifest 里的Main-Class值不会自动跟着变于是就打出了残废 jar。Maven 的 package 就比较直白它依据maven-jar-plugin生成 manifest默认情况下mainClass属性是空着的需要显式在 pom 里声明。另一个坑是 Maven 打出的 jar 默认不带第三方依赖如果你直接java -jar就会遇到NoClassDefFoundError这是依赖缺失不是主类找不到但新手很容易混在一起。Gradle 的是三种里最灵活的jar任务的manifest属性可以直接指定Main-Class而且配合shadow插件能出 fat jar。但 Gradle 默认生成的 jar 里也没有 Class-Path 信息如果你用dependencies的implementation而不是api在某些配置下打包时会忽略运行时依赖同样会造成启动失败。所以你在选择打包方式时先想清楚应用要不要带依赖要带就优先 Maven Shade 或 Gradle Shadow不要硬用 IDEA 原生 Artifact 去手动拉全部依赖那是给简单单人工具用的不是给多模块项目用的。2.3 手动检查 jar 内部结构一条命令看清黑匣子排查问题不能靠猜你得把 jar 这个黑匣子打开。jar 本质上就是个 zip用任何解压工具都行但命令行更直接。jar tf app.jar | findstr /i mainclass manifest这个命令列出 jar 里的所有文件并用 findstr 过滤出包含 manifest 和 MainClass 字样的路径。注意 Linux/macOS 上要把findstr换成grep -i。执行后你会看到META-INF/MANIFEST.MF这样的条目这说明 manifest 存在但有没有Main-Class行必须再看具体内容。unzip -p app.jar META-INF/MANIFEST.MFunzip -p是直接把压缩包里的指定文件内容打到标准输出不需要先解压到磁盘。看到的内容类似Manifest-Version: 1.0 Main-Class: com.corp.demo.Main这里Main-Class的值就是关键。如果这一行没出现或者值和你代码的包路径对不上那「找不到或无法加载主类」就是铁定的结果。还有一种情况jar 里确实有Main-Class但类文件在BOOT-INF/classes/下面Spring Boot 打包出来的而Main-Class指向的是org.springframework.boot.loader.JarLauncher而不是你的业务类。这种结构需要 Spring Boot 的 loader 来引导你用普通方式运行当然找不到你的 main因为你的 main 类被移到了BOOT-INF/classes下。这时不要用 IDEA Artifact 手动改解压目录直接用现成构建插件的 repackage 命令。3. 复现与排查先定位问题再动手修3.1 最小复现从一个最简单的 Java 项目开始为了让你能快速动手验证我们先构建一个最小项目专门复现这个报错。我一般会建一个模拟项目X包名用com.demo.run主类叫RunMain。代码就三行package com.demo.run; public class RunMain { public static void main(String[] args) { System.out.println(Hello from jar); } }然后打开 Project Structure → Artifacts → Add → JAR → From modules with dependencies。这里主类下拉框里手动选中com.demo.run.RunMainIDEA 就会帮你把 Main-Class 写进 manifest。问题在于很多人点击了 Build → Build Artifacts 后跑去 target 目录拿 jar却不知道 IDEA 构建出的目录可能有两个一个exploded是文件夹一个才是最终 jar如果你拿错了 exploded 目录里的文件java -jar自然找不到 manifest。在实际的复现步骤里我还踩过更深的坑IDEA 的 Module 在改名后之前的 Artifact 配置会失效。我有个项目从demo改名成demo-appArtifact 的输出目录还是旧的demo.jar但编译输出已经换到了新模块的target下结果打出来的 jar 只是个空壳连自己的 class 都没有更别说 Main-Class。那次我花了一个多小时反复重新配置才意识到模块引用断了。所以复现问题前先确认三件事项目使用的 JDK 版本、Artifact 的 Output directory 是否在 Modules 有实际 class 输出、manifest 是在哪个目录生成的。3.2 常见引发「找不到主类」的四种配置错误第一种错误是Main-Class写成带.class后缀例如com.demo.run.RunMain.class。这种错误 IDEA 里通常不会发生但手动编辑 manifest 时极易出现。JVM 老实你给它什么它就找什么多一个.class就去文件系统找RunMain.class.class自然失败。第二种错误是把Main-Class写成内部类的名字。假如你的 main 在RunMain$Helper这个内部类里你应该写com.demo.run.RunMain$Helper注意不是点号是美元符号。这种写法在命令行下尤其容易出错因为 shell 会把$Helper当变量展开建议在 manifest 里写或者对$加转义。第三种错误是 manifest 文件编码问题。某些 Windows 环境下 IDEA 自动生成的 MANIFEST.MF 可能是 UTF-8 带 BOM或者 GBK而 JDK 读取时默认按 UTF-8 解码如果包名里有中文注释或类名包含非 ASCII 字符路径解析就会错位。我见过两次这种情况一次是类名用了中文拼音缩写加数字另一次是注释里出现了全角空格结果Main-Class行的行尾多了字符JVM 把回车符也算进了类名里。第四种错误比较隐蔽你在 Project Structure 里选对了 Main Class但 IDEA 生成 manifest 时用的是旧的out目录下的类文件。因为out和target同时存在Artifact 默认把编译输出指向out而 Maven 的编译输出是target两处的 class 文件可能不一致。如果你之前手动跑过 mvn cleanout目录就变成旧版了IDEA 又从out去读类就会造成明明源码有新类打包出来却没有的诡异局面。3.3 用命令行验证 jar 是否可执行复现到这一步你可以用一条最基础的命令行来验证 jar 能不能跑java -jar app.jar如果你得到的是类名堆栈而不是简洁的 Hello 输出先不要急着改 manifest把 stdout 留一份到文件里因为有些报错信息会被日志系统吞掉。我建议加-verbose:class参数看 JVM 到底去哪个路径加载主类java -verbose:class -jar app.jar 21 | tail -50-verbose:class会打印每个类的加载来源如果是file:/path/to/app.jar!com/demo/run/RunMain说明类加载器找到了 jar 内部的类如果只看到file:/path/to/app.jar!/却没有你的包说明 manifest 里的 Main-Class 名字不对。还有一种验证思路直接用java -cp app.jar com.demo.run.RunMain绕过 manifest 调用主类。如果这次能正常运行那就说明类的字节码没问题纯粹是 manifest 里的入口写错了。这个区分对后面修问题很有用前者修 manifest后者可能是构建过程把类漏掉了。4. 三种可靠修复方案从快速救命到工程化4.1 方案一IDEA 的 Artifact 配置里把 Main Class 选对如果你只是做个临时工具坚持用 IDEA 的 Artifact 也没问题关键是搞清楚每个选项的含义。打开 Project Structure → Artifacts选中你的 jar 类型 Artifact右侧有个 Main Class 输入框和 Create Manifest 按钮。最稳妥的做法是先点 Create Manifest在弹窗里选择项目根目录下的src/main/resources作为 manifest 存放位置IDEA 会自动写一个空内容文件。然后你在 Main Class 输入框里点那个文件夹图标选择你真实的主类。这里有个容易忽略的细节IDEA 会问你 Extract to the target JAR 和 Copy to the output directory and link via manifest 两种模式。选 Extract 会把依赖的包内容解压进你的 jar选 Copy 则是把依赖的 jar 作为外部文件放在输出目录旁边。我一般建议选 Extract生成的是单文件 fat jar拷到哪都能跑但副作用是 META-INF 下同名配置文件会被覆盖遇到多依赖中有相同的spring.schemas时会出问题。如果项目依赖很少Copy 模式更干净但你要保证输出目录里所有的依赖 jar 一起携带。选完后重新 Build → Build Artifacts → Rebuild。注意要 Rebuild不要选 Build后者可能只增量编译留下旧的 manifest。rebuild 完成后用上面unzip -p的命令看 manifest确认Main-Class值正确。不过 Artifact 方式终究是手工维护一旦新增依赖模块就得重新去 Output Layout 里手动把新模块的 jar 拖进 Available Elements。这一步很容易漏漏了就会出现能启动但运行到某段功能时报NoClassDefFoundError的现象。所以它只适合小项目对工程化项目我更推荐后面两种方案。4.2 方案二用 Maven Shade 插件打包可执行 fat jar如果你的项目是 Maven 管理我强烈建议放弃手动 Artifact改用maven-shade-plugin。它能自动扫描所有依赖把第三方类也塞进同一个 jar并且帮你生成带 Main-Class 的 manifest。在 pom 里添加如下配置plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-shade-plugin/artifactId version3.4.1/version executions execution phasepackage/phase goals goalshade/goal /goals configuration transformers transformer implementationorg.apache.maven.plugins.shade.resource.ManifestResourceTransformer mainClasscom.demo.run.RunMain/mainClass /transformer /transformers filters filter artifact*:*/artifact excludes excludeMETA-INF/*.SF/exclude excludeMETA-INF/*.DSA/exclude excludeMETA-INF/*.RSA/exclude /excludes /filter /filters /configuration /execution /executions /plugin配置里的mainClass节点就是你真实的启动类路径。filters里排除签名文件是必须的否则多个依赖的签名文件冲突会把整个 jar 的校验破坏掉导致 JVM 认为文件被篡改拒绝加载类。这个坑我踩过一次当时没有加 filters打包后运行报SecurityException: Invalid signature file digest for Manifest main attributes花了很久才意识到是签名冲突。配置好后执行mvn clean package然后在 target 目录下找到类似模拟项目X-1.0-SNAPSHOT.jar的 fat jar注意 Shade 插件的默认替换行为是把原 jar 重命名为original-xxx.jar而生成的最终包就是你期望的名字。执行java -jar target/模拟项目X-1.0-SNAPSHOT.jar就能正常跑。Shade 插件也有一个问题它把所有类的包路径合并到一个层级如果两个依赖里有同名类比如不同版本的日志库会出现类重复导致的随机错误。但主类找不到这种问题它确实彻底解决了因为 manifest 是它自动算的不会像人一样漏写包名。4.3 方案三修正 MANIFEST 后手动重新打包如果前两种方式你都不想用还有一个最朴素的方法适合 sandbox 环境不能跑 Maven 的情况下救急。先把原始 jar 解压到临时目录编辑 manifest然后重新打包。mkdir extracted cd extracted jar xf ../app.jar vi META-INF/MANIFEST.MF在 manifest 里加上这样一段Manifest-Version: 1.0 Main-Class: com.demo.run.RunMain注意Main-Class后面必须有一个空格接着是全限定类名最后以换行结束。尤其最后一行必须换行否则 JDK 容易解析失败。改完后用 jar 命令重新打包jar cfm new-app.jar META-INF/MANIFEST.MF .cfm中m的意思是使用指定 manifest 文件生成 jar且new-app.jar后面必须紧跟 manifest 路径然后再加打包的目录。这个顺序很严格写反了不会报错但生成的 manifest 会是默认模板导致你的修改丢失。这里有个细节如果你把 manifest 放在extracted目录的META-INF下命令里写META-INF/MANIFEST.MF就行因为当前目录正好是解压后的根目录。重新生成的 jar 里所有类文件的相对路径保持不变所以只要 manifest 正确能立即解决主类找不到问题。但是手动重打包没法解决依赖问题。如果你的 project 依赖了第三方库在 manifest 里还需要写Class-Path: lib/foo.jar lib/bar.jar并且这些依赖 jar 要放在新打包后的 jar 同级目录的lib子目录里。很多人漏掉这个手动打完包后依旧启动失败只不过报错从找不到主类变成了找不到依赖类。5. IDEA 打包避坑主类缺失、依赖缺失与中文路径的典型坑5.1 现象双击 jar 无反应命令行报错却说主类存在有次某开发者遇到怪事双击 jar 没有任何反应但用java -jar跑却正常。他开始以为打包坏了检查 manifest 也没问题后来发现他的系统默认打开 jar 程序不是javaw.exe而是某个压缩工具把 jar 关联到了解压软件。双击时实际上是在解压根本没启动 JVM。解决方法很简单右键 → 打开方式 → Java(TM) Platform SE binary。如果列表里没有这个选项说明 JDK 没正确安装或者 JRE 的注册表缺失重新装 JDK 并勾选关联 jar 文件即可。还有另一种类似情况jar 在 Windows 上被安全软件拦截双击后文件被隔离了打开文件位置看 jar 变成 0KB这时检查杀毒软件隔离区就行。这个问题和代码无关但因为它发生时间常在打包之后很误导人。5.2 现象打包成功但运行时报 NoClassDefFoundError这个现象最典型主类成功加载执行到某一行代码时突然报NoClassDefFoundError: com/example/dependency/Util。说明你打的是瘦 jar第三方依赖没有被包含进去而且 manifest 里也没有Class-Path指向依赖位置。IDEA Artifact 默认就是这种做法。解决方式取决于你选哪种模式。如果是 Extract 模式重新确认 Output Layout 里有 All Libraries 节点被正确拖入如果是 Maven改用 Shade 插件做 fat jar如果使用 Gradle添加 shadow 插件配置shadowJar的mainClass。一句话依赖缺失和主类找不到是两个问题但修复路径不同。5.3 现象换了电脑或目录后突然打不开你把自己的 fat jar 拷到另一台电脑上运行报找不到主类。第一反应是 Java 版本差异。比如你在本机用 JDK 17 编译的 class 文件如果目标机器只有 JRE 8JVM 会抛UnsupportedClassVersionError这和找不到主类表现相近但不是同一个。解决是安装匹配的 JDK或降低编译目标的release版本。但更隐蔽的是路径问题。如果你的 project 使用了自定义类加载器它按绝对路径读取某些资源文件jar 换个目录后就找不到资源这个资源加载的失败会被包装成类加载异常。排查时加上-verbose:class看是否有文件系统路径的输出能帮你快速定位到具体是哪个资源。另外中文目录也容易出问题jar 放在C:\Users\张三\Desktop下某些老版本 JDK 对非 ASCII 路径支持不好会出现Could not find or load main class实际是编码问题把路径改成纯英文目录就能跑。5.4 现象IntelliJ 能运行命令行 java -jar 不行这大概是出现频率最高的困惑。IDEA 里点击 Run 绿色按钮没问题打包完就不行。原因有三层第一IDEA 运行时不读取 MANIFEST.MF它直接使用模块的 classpath 和主类字节码定位第二IDEA 的 Run Configuration 里会自动把你项目依赖和 JDK 的 tools.jar 加进 classpath但打包时这些都不会自动进 jar第三IDEA 运行时会用编译输出out目录下的最新 class而Build Artifacts可能是从target目录取的旧产物两个目录不同步导致打包内容缺失。修复时用mvn clean package重新构建能避免 out/target 混乱。如果你坚持用 Artifact先执行 Build → Rebuild Project让所有模块的out刷新再 Build Artifacts。但即便这样主类仍可能加载失败因为 IDEA 生成 manifest 的时机和编译顺序在不同版本 IDE 之间表现不一致。终极方案还是把构建过程交给 Maven/GradleIDE 只负责写代码和启动调试。6. 进阶让 main 方法在复杂依赖下也能被稳定找到6.1 动态生成 MANIFEST 的构建脚本方案到工程化阶段我不再依赖 IDE 的图形界面而是在构建脚本里统一生成 manifest。以 Maven 为例用maven-jar-plugin的archive配置配合执行参数可以让 manifest 里的Main-Class从项目版本变量中读取避免写死。plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-jar-plugin/artifactId configuration archive indextrue/index manifest mainClass${project.build.mainClass}/mainClass addClasspathtrue/addClasspath classpathPrefixlib//classpathPrefix /manifest /archive /configuration /pluginaddClasspath为 true 时插件会自动计算依赖并把相对路径写进 manifest 的Class-Path段classpathPrefix指定依赖 jar 放在lib/子目录下。但注意addClasspath生成的路径是相对于 jar 所在目录的所以你在发布时要把所有依赖也拷进同级的lib目录否则还是运行不了。这种做法的好处是 jar 体积小启动速度快坏处是分发时要连目录带 lib 一起打包不适合单文件交付。如果既想单文件又想避免 Shade 的类冲突可以改用maven-assembly-plugin的jar-with-dependencies模式它同样会自动生成 Main-Class但处理方式是把依赖直接展开到原 jar 里效果和 Shade 类似遇到同名配置文件时它会按顺序覆盖规则简单。6.2 验证清单三步确认你的修复真的生效每次改完构建配置我都按下面三步走避免出现这次跑通了但发布到生产又挂了的尴尬。第一步用unzip -p检查 manifest 内容确认Main-Class不是旧值第二步java -jar跑一个空跑场景确认能打印入口日志第三步如果加了依赖打包运行jdeps --ignore-missing-deps -summary your.jar检查依赖是否完整。unzip -p app.jar META-INF/MANIFEST.MF | grep Main-Class java -jar app.jar jdeps --ignore-missing-deps -summary app.jar 21 | grep not found第三条里jdk.jdeps模块是 JDK 自带的依赖分析工具能列出 jar 中引用了但不在 jar 里的类。如果它标记not found说明依赖缺失主类就算加载成功运行一半也会崩。这里我踩过最深的坑是jdeps会把 Java 标准库也列出来比如java.lang只要不是not found就行不需要全部通过。最后还有一个习惯要说发布前不要只跑一次java -jar就完事我一般会换一个目录路径比如放到/tmp/或者带空格的名字里再跑一遍。因为路径含空格时某些老脚本引号处理不当会导致 classpath 被截断这时候报错又变成找不到主类。提前做一次目录切换测试能帮你规避掉最恶心的发布环境问题。希望这些经验帮到你少折腾几个晚上。我自己的教训是永远不要相信打包就好了这句话每一份交付物都得用命令亲手验证过才踏实。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑