news 2026/9/30 10:49:11

JAR包没有主清单属性?从原理到打包修复全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JAR包没有主清单属性?从原理到打包修复全解析

先别急着怀疑人生,这个报错在Java开发路上基本人人都会撞上一次——在控制台敲下java -jar xxx.jar想启动程序,结果终端无情地回了一句no main manifest attribute, in xxx.jar,中文环境则显示“jar中没有主清单属性”。我第一次遇到时真以为JAR包组装坏了,后来才明白问题出在入口信息上。这篇就把这事的来龙去脉、最快应急手段、以及一劳永逸的打法彻底讲清楚,适合刚入门的Java新手,也适合已经遇到过、但没弄明白原理的同学参考。

1. 这个报错到底在说什么:主清单属性是什么

1.1 一次完整的java -jar执行流程

要理解这个报错,先得知道java -jar启动一个JAR包时发生了什么。JAR包本质上就是ZIP压缩包,里面装着编译好的.class文件和其他资源文件。但在JAR包的固定位置META-INF/MANIFEST.MF里,有一个清单文件,里面可以写各种元信息。

你执行java -jar xxx.jar的时候,JVM会先把这个JAR包打开,找到META-INF/MANIFEST.MF,然后从里面查找一个叫Main-Class的属性。这个属性写的是入口类的完整类名,比如com.example.MyApplication。JVM拿到这个类名后,才会去加载这个类,然后调用它的public static void main(String[] args)方法。

如果清单文件里没有Main-Class,或者这个属性当前为空,就会直接抛出no main manifest attribute,程序压根不会运行。报错信息里还会把JAR包的文件名一起列出来,算是帮你定位了是哪个包出了问题。

1.2 正常JAR包与可执行JAR包的区别

很多人被这个报错坑到,是因为把“普通JAR包”和“可执行JAR包”搞混了。Maven或Gradle打包出来的JAR,默认只是“库文件”,也就是给别人项目引用的工具包,它们根本不需要入口。比如你日常引入的commons-lang3.jar、mysql-connector-java.jar,清单文件里一般只有基本描述信息,没有Main-Class,也没必要有。

可执行JAR包则是在清单里明确写了入口类的包,用java -jar能直接启动。打个比方,普通JAR是“零件箱”,可执行JAR是“整机”。你把零件箱插上电想开机,当然没反应——里面根本没有“启动按钮”对应的那个类。

所以遇到这个错误,第一反应不是去找什么灵丹妙药,而是判断:你手上这个JAR到底是不是设计成可执行的。如果不是,要么换一条运行方式,要么重新打包成可执行JAR。

1.3 触发这个报错的五种典型场景

结合我在控制台摸爬滚打的经验,这个报错通常出在下面几种情况里:

  • 直接拿项目依赖的普通库JAR包用java -jar运行,这最常见,新手尤其容易犯。
  • Maven项目打包时没有配置maven-jar-plugin,或者配置文件里漏写了主类,打出来的包自然没有入口。
  • Spring Boot项目没有引入spring-boot-maven-plugin,导致Maven默认打的只是个普通JAR。
  • 从网上反编译别人的JAR,改完代码后重新打包,忘了写回Main-Class。
  • 把多个JAR包“缝合”到一个包里时,解压覆盖过程中把META-INF/MANIFEST.MF弄坏或覆盖成空文件。

后面几种场景在下面几个章节都会给到具体解法。先讲应急方案,让你先把程序跑起来。

2. 最快速的应急方案:不重新打包也能跑起来

2.1 用java -cp直接指定主类

如果手头只有一个JAR包,又不想重新走一遍构建流程,最快的办法就是绕过java -jar,改用java -cp指定入口类。命令长这样:

java -cp yourjavajar.jar com.example.MainClass

这里-cp(即-classpath)指定JAR包路径,后面直接跟上入口类的全限定名。这种方式完全不需要读清单文件,你只是告诉JVM“这个类的main方法就是入口”,于是能正常启动。

前提是,你得先知道入口类到底叫什么。实在不知道的话,可以用一条命令把JAR包里的class列表翻出来看个大概:

jar tf yourjavajar.jar

如果输出里能看到类似com/example/App.class,那入口类多半就是com.example.App。如果App.class没有挂在com.example包下面,而是直接躺在根目录,那入口类就是App。当然这只是猜入口,更靠谱的是解压看清单和反编译确认,这在后面章节细说。

2.2 如何准确获取正确的入口类名

有些JAR包的清单文件其实有Main-Class,只是你运行的方式不对。比如清单里写了入口,但你硬要用java -cp去加载别的类,也会报Could not find or load main class。遇到这种混淆情况,建议先直接看清单文件内容:

unzip -p yourjavajar.jar META-INF/MANIFEST.MF

用unzip -p的好处是只把指定文件内容打印到控制台,不用把整个JAR包解压出来。你会看到类似输出:

Manifest-Version: 1.0 Created-By: Maven Jar Plugin 3.3.0 Built-By: user Main-Class: com.example.Launcher

如果看到了Main-Class,直接复制后面的类名去执行:

java -cp yourjavajar.jar com.example.Launcher

如果清单文件里压根没有Main-Class,那说明这个包设计之初就不是可执行的。这时候你得从代码层面确认入口,看项目源码里哪个类带main方法,或者用反编译工具打开反编译看看。

2.3 带依赖JAR运行的Class-Path处理

用-cp运行的一个常见坑是:你的主类依赖了第三方库,光把主程序JAR放在classpath里还不够,一运行就可能报ClassNotFoundException或NoClassDefFoundError。这种情况要手动把依赖JAR也加进classpath,命令变成:

java -cp "yourjavajar.jar:lib/*" com.example.MainClass

Linux和macOS环境下多个路径用冒号:分隔,Windows下用分号;。lib/*表示把lib目录下的所有JAR都加进去,注意星号只匹配JAR包,不会递归子目录,也别写成lib/*.jar,那种写法在某些JDK版本上反而可能不生效。

如果你的系统里装了多个版本的JDK,建议先看一眼当前默认版本:

java -version

不少时候程序跑不起来不是入口类问题,而是JDK版本和编译级别不匹配,这个在排错时也要纳入考虑。

3. 治本方案:重新打包让主清单属性完整

应急方案只能临时顶着,要想java -jar能直接跑,还得从构建配置上下手。按你的项目用的构建工具分三种情况来解决。

3.1 Maven项目的一次性修复

Maven项目里,控制JAR包清单的是maven-jar-plugin。如果构建配置里没写主类,打出来的包就没有Main-Class。在pom.xml的<build><plugins>里加上这一段配置:

<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>3.3.0</version> <configuration> <archive> <manifest> <mainClass>com.example.MainClass</mainClass> </manifest> </archive> </configuration> </plugin> </plugins> </build>

重新执行mvn clean package,再用unzip -p看清单,Main-Class就会出现。这种配置适合简单项目,主类没有依赖第三方库的情况。

如果你的程序依赖了很多第三方库,直接java -jar一样会报ClassNotFoundException,因为JVM找不到依赖。两种常见解法:一是把Class-Path也写进清单,但这种做法要在配置里维护相对路径,很麻烦;二是用maven-shade-plugin把依赖全打到一个“胖JAR”里,生成的包体积大,但用起来省心。shade插件配置如下:

<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.4.1</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass>com.example.MainClass</mainClass> </transformer> </transformers> </configuration> </execution> </executions> </plugin>

这里关键是用ManifestResourceTransformer来把主类写进合并后的清单,否则shade出来的包同样会丢入口。

3.2 Spring Boot项目的特殊处理

Spring Boot项目如果用了spring-boot-starter-parent作为父工程,打包时默认带spring-boot-maven-plugin,所以mvn package直接能打出可执行JAR。但如果你在<plugins>里手动覆盖了打包插件,却不小心漏掉了它,就会打出普通JAR,启动必然报“jar中没有主清单属性”。

Spring Boot的正确打包配置是这样:

<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>

如果项目没有继承spring-boot-starter-parent,需要额外指定执行目标:

<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin>

repackage这个goal会把你原来的普通JAR再处理一遍,把三方依赖塞进去,并在清单里写入真正的启动类。注意观察打包输出,Spring Boot生成的original JAR和最终可执行JAR一般是两个文件,别拿错。

3.3 Gradle项目的配置方式

Gradle项目如果没做额外配置,默认的jar任务打出来的也是没有入口类的普通包。最简单的修复是在build.gradle里给jar任务加上manifest配置:

jar { manifest { attributes 'Main-Class': 'com.example.MainClass' } }

同样地,如果依赖复杂,建议用application插件配合installDist,或者用 Shadow 插件生成胖JAR。用Shadow的话配置是:

plugins { id 'com.github.johnrengelman.shadow' version '8.1.1' } shadowJar { manifest { attributes 'Main-Class': 'com.example.MainClass' } }

执行./gradlew shadowJar,得到的xxx-all.jar就能直接java -jar运行。Spring Boot的Gradle项目更简单,加上org.springframework.boot插件后,运行bootJar任务即可生成可执行JAR,这块和Maven的repackage逻辑类似。

3.4 手动修改已有JAR包清单(zip/解压/重新打包)

有些情况下你没有源码,只有别人给的JAR,而且它确实可以修改,那就直接在二进制层面把清单补上。这种操作我做过很多次,思路就是三步:解压 → 改清单 → 重新打包。

第一步,解压JAR到临时目录:

unzip -d tempdir yourjavajar.jar

第二步,编辑tempdir/META-INF/MANIFEST.MF,确保里面有这一行,并且入口类路径写对:

Main-Class: com.example.MainClass

强烈建议用支持UTF-8的文本编辑器,别用系统自带记事本瞎改,后面我会讲编码坑。

第三步,重新打成JAR包,这里要注意命令参数顺序。最容易踩坑的是jar cfm的顺序,它和jar cfe不一样:

jar cfm new.jar tempdir/META-INF/MANIFEST.MF -C tempdir .

-C tempdir表示进入tempdir目录后处理剩余文件,末尾的.表示把当前目录下所有文件打进包。如果输出的JAR包缺了资源文件,多半是这一步没转对目录。不打-C的话,打出来的JAR内部会多一层tempdir前缀,运行起来各种资源路径错乱。

如果你不想用命令行,也可以用7-Zip直接打开JAR包(它本质是ZIP),找到META-INF/MANIFEST.MF文件,拖出来改好再放回去。7-Zip会重新压缩,这招在Windows上可以避免装一堆命令行工具。

4. 特殊场景:反编译、合并JAR包后的清单修复

4.1 反编译后重新打包为什么容易丢主类

反编译JAR再重新打包的场景在外包和二次开发里很常见。有人用如JD-GUI、CFR反编译拿到.java文件,改完代码后用javac重新编译,再手动jar cf打出一个新包。打完之后java -jar直接报错,原因就是反编译的时候只关注了.class文件,压根没人去管MANIFEST.MF。

反编译后重新打包的正确做法是:打包前先把原JAR的META-INF目录完整保留下来,手工编译完.class文件后,使用包含原来清单文件的方式重新打包。命令顺序再强调一遍:

jar cfm new.jar META-INF/MANIFEST.MF -C ./classes .

如果原来的清单里还有签名信息,比如META-INF/*.SF、*.RSA这些文件,直接沿用会引发SecurityException。这种场景建议把META-INF目录下除MANIFEST.MF之外的签名文件全删掉,因为改过代码后签名本来就是失效的。

4.2 多个JAR包合并时的Main-Class保留技巧

“JAR包怎么缝合”是社区里常被翻出来的问题,很多人试图把多个JAR包手动合成一个,直接用解压工具把A包的class扔进B包,或者用jar uf更新进去。这种方式最容易踩的坑就是:覆盖了对方的META-INF/MANIFEST.MF,或者合并之后入口类丢了。

我更推荐用构建工具来做JAR合并,而不是手工复制。Maven用shade,Gradle用shadow,它们都支持多个JAR的META-INF合并,还能让不同JAR的同名配置文件做对应处理。比如shade的transformers里可以配ServicesResourceTransformer来处理服务接口文件,单纯手工合并基本处理不了这种冲突。

如果你实在不方便用构建工具,坚持手工缝合,那请注意:合并前先把第一个JAR的META-INF/MANIFEST.MF复制出来,最后用这个清单重新打包,别指望解压工具自动帮你合并清单。还要留意不同JAR里如果有同名class,后面的会覆盖前面的,可能会导致运行时各种诡异错误。

5. 常见问题与排查技巧实录

5.1 清单文件格式的暗坑:换行、编码、签名

很多人改完清单文件,重新打包后依然报错,那我建议你按下面几个隐藏点逐一排查。

第一,Main-Class这一行必须是冒号+空格+类名的格式。写成Main-Class:com.example.Main(少了空格)、写错大小写、类名多个空格,都有可能解析失败。

第二,清单文件最后一个属性后面必须要有换行符。这是JAR规范要求,很多编辑器保存时默认不在文件末尾加换行,导致最后一行属性读取失败。这个问题非常隐蔽,我之前手动改清单时遇到过两次,花了很长时间才定位到。

第三,文件名别写错。明明写了Main-Class,却写在了META-INF/mf这种不存在的路径下,或者文件名大小写对不上。Linux服务器上是区分大小写的,MANIFEST.MF和manifest.mf是两个文件。

第四,如果原JAR带签名,你重新打包后又想保留签名,必须重新签名,否则会启动失败。签名文件只是覆盖了旧的,实际哈希早已对不上,删掉最省心。

为了验证你的清单是否写对,看一遍输出内容是最快的:

unzip -p new.jar META-INF/MANIFEST.MF

确认最后一行的Main-Class正常,并且末尾确实有空行,再用java -jar试运行。

5.2 改完还是报错怎么办:先分清三类报错

有时候你已经把Main-Class写进清单了,控制台却还在报错。注意,这里要区分三个容易混淆的报错:

  • no main manifest attribute:清单没有主类属性,属于还没找到入口。
  • Could not find or load main class:清单里写了入口,但JVM按这个类名去加载时没找到对应的class,说明类名写错、或class文件确实不在包里。
  • ClassNotFoundException:主类找到了,但它依赖的某个第三方类在运行环境里不存在,这是依赖缺失问题。

第一类和第二类之间的转化特别常见,你要做的是回到上一步,用jar tf your.jar | grep 主类路径确认包内确实存在com/example/Main.class。如果包内没有,说明打包时漏了源文件,重新编译打包。如果第三个报错,优先检查当前classpath是否覆盖了所有依赖,或者直接改用shade打出胖JAR。

5.3 环境与命令的连带问题排查

除了JAR本身,环境层面的问题也可能让你误判成“主清单属性”问题。比如在Windows的PowerShell里执行多路径classpath时,路径分隔符要写成分号,很多人照抄Linux的冒号写法,结果报找不到类。要么换成;,要么在PowerShell里用数组形式,别硬记一个命令到处跑。

另一个高频问题是:控制台显示的中文报错和英文报错对不上。jar中没有主清单属性的英文原文常见是no main manifest attribute,这其实是同一个东西,不用怀疑是不同错误。

再提一个很多人问的jar 包查找调用链相关的点:如果你拿到一个JAR不知道主类在哪,我通常会先看清单,然后直接反编译整个包,搜索所有带public static void main(String[] args)签名的方法。CFR反编译出来之后用IDE的全局搜索,一般几分钟就能定位。这也是搞二次开发时最常用的手法之一。

写在最后的一些大实话

这个报错看起来小,背后涉及的其实是“JAR包结构、清单规范、构建工具配置”这几块知识。我在实际项目里见过不少同事在这个坑上反复栽跟头,总结下来就是一个习惯问题:每次打包完成后,先跑一遍unzip -p xxx.jar META-INF/MANIFEST.MF看一眼,确认Main-Class存在,再执行java -jar。即使不是可执行JAR,也顺手看一眼,心里有数。

另外一个小建议:把常用命令保存成脚本或者笔记,尤其是jar cfm的参数顺序,这种冷门参数一天不用就忘。我第一次手动补清单时,把cfm和cfe的顺序记反了,折腾了半小时才明白是怎么回事,现在每次都会先jar --help确认一遍。希望能帮你少走我走过的弯路。

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

电子图书馆网络设计:TCP/IP全栈实践教学指南

简介&#xff1a;本资源是高校《计算机网络I》课程设计的完整实践报告&#xff0c;面向计算机、网络工程等专业本科生&#xff0c;聚焦电子图书馆网站的综合性网络架构与服务部署。内容覆盖从需求分析、拓扑设计&#xff08;含1000M主干网100M到点、4子网划分&#xff09;、硬件…

作者头像 李华
网站建设 2026/9/30 10:47:39

RustDesk自建中继服务器:从零搭建稳定远程控制方案

这两年我陆续给身边的同事朋友搭了不少远程控制方案&#xff0c;从商业软件到开源工具都折腾过一圈。最后自己日常在用的&#xff0c;反而是一套看起来最不起眼的组合&#xff1a;RustDesk 加上一台便宜的公网云服务器&#xff0c;自建中继节点&#xff0c;稳定远程控制家里的内…

作者头像 李华
网站建设 2026/9/30 10:45:02

ADS射频PA设计中的电容选型与高频模型实战经验

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/30 10:44:16

IEEE 802.3cm 标准解读:400G 多模光纤 100 米链路部署与验收

简介&#xff1a;IEEE Std 802.3cm-2020 是 IEEE 发布的以太网修订标准&#xff0c;聚焦多模光纤上 400Gb/s 的物理层与管理参数&#xff0c;面向光模块研发、数据中心网络架构及高速以太网测试工程师。标准新增 Clause 150&#xff0c;定义了 400GBASE-SR8 与 400GBASE-SR4.2 …

作者头像 李华
网站建设 2026/9/30 10:42:59

STM3流水灯综合实验

实验一 寄存器方式实现四路流水灯实验名称STM32F103C8T6寄存器编程实现四路LED流水灯一、实验目的1. 熟悉STM32F103最小系统板硬件&#xff0c;理解GPIO端口功能与APB2外设时钟作用。​2. 掌握直接操作寄存器配置GPIO&#xff0c;理解CRL、CRH、BSRR寄存器功能&#xff0c;学会…

作者头像 李华
网站建设 2026/9/30 10:40:58

毕业生实测|2026 国内 AI 论文工具横向盘点,按场景选不踩坑

写在前面 临近毕业&#xff0c;很多人一口气收藏七八个 AI 论文网站&#xff0c;用到最后发现&#xff1a;有的只能翻译外文&#xff0c;有的只会简单润色&#xff0c;想走完开题、文献、写作、绘图、自查、排版、答辩 PPT 整套流程&#xff0c;来回切换账号&#xff0c;文件反…

作者头像 李华