Gradle默认jar任务只打包项目自身.class文件,不含第三方依赖,故java -jar运行会因NoClassDefFoundError失败;需通过shadow插件生成fat jar或配置Class-Path分离依赖并指定Main-Class方可执行。

直接打 gradle jar 生成的 jar 默认不含依赖,也不能直接用 java -jar 运行——这是绝大多数人第一次打包就踩坑的地方。
默认 jar 任务只打项目自身 class 文件
Gradle 的内置 jar 任务(来自 java 插件)默认行为很“干净”:只把 src/main/java 编译出的 .class 和 src/main/resources 下的资源打进 jar,完全不碰任何依赖。
- 运行
gradle jar后,输出在build/libs/- .jar - 这个 jar 没有
Class-Path,也没有把第三方 jar 解压进去 - 如果主类依赖了
gson或slf4j,直接java -jar xxx.jar会报NoClassDefFoundError - 它适合做 SDK 库发布(比如供其他项目
implementation引入),不适合当可执行程序分发
要可执行,必须显式配置 Main-Class 和依赖处理方式
可执行 jar 的核心是两件事:声明入口类、让 JVM 能加载到所有依赖。Gradle 不自动做后者,得手动选策略。
- 方案一(推荐新手):用
shadow插件打 fat jar
加这段到build.gradle:plugins { id 'com.github.johnrengelman.shadow' version '8.1.1'' } shadowJar { archiveClassifier = '' manifest { attributes 'Main-Class': 'com.example.Main' } }然后运行gradle shadowJar,输出build/libs/,直接- -all.jar java -jar就行 - 方案二:保留依赖分离,靠
MANIFEST.MF的Class-Path
适合依赖稳定、想避免每次重打大 jar 的场景。需额外任务复制依赖:task copyLibs(type: Copy) { from configurations.runtimeClasspath into 'build/libs/lib' } jar { dependsOn copyLibs manifest { attributes 'Main-Class': 'com.example.Main' attributes 'Class-Path': configurations.runtimeClasspath.files.collect { "lib/${it.name}" }.join(' ') } }注意:Class-Path值里不能有空格或特殊字符,路径必须是相对路径(lib/xxx.jar),且 jar 运行时当前目录得是build/libs
configurations.* 选错会导致依赖漏打或签名失败
很多人抄配置时把 configurations.runtimeClasspath 写成 configurations.compile 或 configurations.implementation,结果要么打不全(缺 runtime-only 依赖),要么打不进(compileOnly 依赖根本没参与打包)。
-
runtimeClasspath是最稳妥的选择:它包含编译 + 运行时所有实际参与 classpath 的 jar - 如果用了
shadow插件,它内部默认就用runtimeClasspath,不用手动改 - 若项目含已签名的 jar(如某些商业 SDK),用
zipTree(it)解压合并会破坏签名,触发SecurityException: Invalid signature file digest;此时只能选「依赖分离」方案,或手动排除签名文件(不推荐) - 打 big jar 时可能遇到
archive contains more than 65535 entries错误,必须加zip64 = true(shadowJar默认已启用,自定义jar任务需显式写)
真正麻烦的从来不是“怎么打”,而是“打完能不能跑”。建议每次生成 jar 后,立刻用 jar tf xxx.jar | head -20 看一眼内容结构,再试一次 java -jar —— 依赖路径、主类名、Manifest 格式,三处错一个就起不来。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











