Gradle默认jar任务不打包第三方依赖,导致java -jar报NoClassDefFoundError;应使用shadowJar插件生成fat jar或application插件生成带lib目录的分发包。

Gradle 默认的 jar 任务不打包第三方依赖,直接 java -jar 必然报 NoClassDefFoundError 或 ClassNotFoundException ——这不是你写错了,是 Gradle 故意没这么做。
为什么 standard jar 里没有 commons-cli、gson 这些类?
Gradle 的内置 jar 任务只收集 sourceSets.main.output(即你自己的 .class 文件),完全忽略 runtimeClasspath 上的所有依赖 JAR。你可以用 unzip -l build/libs/myapp.jar | head -20 看一眼,里面只有 com/example/ 目录,绝不会有 org/apache/commons/。
Java 启动时的 -jar 模式会把整个 JAR 当作 classpath 根,不会自动扫描其内部嵌套的 JAR(那是 Spring Boot 的事),更不会去 ~/.m2/repository 找——它只认这个单文件。
怎么让 jar 带上所有依赖?用 shadowJar 而不是手写 fatJar
别再抄网上那些手动配置 from { configurations.runtimeClasspath.collect { zipTree(it) } } 的旧方案了。它在 Gradle 8+ 上容易出错,且不处理重复资源(比如多个 META-INF/MANIFEST.MF)、不支持 Java 模块路径、也不自动设置 Main-Class。
当用户要求为 Java 代码添加注释、修改注释、审查注释、整理注释格式,或要求按照代码注释规范处理 Java 文件时,使用本技能。本技能用于统一生成和维护 Java 类注释模板、按 Git 对比 release 判断版本号、补充每行代码上方中文块注释、调整注释空行规则,并保持 Java 文件 UTF-8 无 BOM 编码。典型触发场景包括:补充类注释、更新 @version、检查 @author 来源、整理行尾注释、把注释放到代码上一行、检查注释是否符合规范、构建前检查 Java 注释风格。
立即学习“Java免费学习笔记(深入)”;
- 加插件:
plugins { id 'com.github.johnrengelman.shadow' version '8.1.1' '}(适配 Gradle 8.1+ 和 Java 17) - 声明主类:
shadowJar { mergeServiceFiles(); manifest { attributes 'Main-Class': 'com.example.Main' } } - 构建:
./gradlew shadowJar,生成的是build/libs/myapp-all.jar,不是myapp.jar - 运行:
java -jar build/libs/myapp-all.jar,零配置直接跑通
如果不想打胖包,就用 application 插件分发
适合需要保留依赖分离、或后续要加 JVM 参数/脚本包装的场景。它不合并字节码,而是生成带 lib/ 目录和启动脚本的结构。
- 启用插件:
plugins { id 'application' } - 设主类:
application { mainClass = 'com.example.Main' } - 构建:
./gradlew installDist→ 输出在build/install/myapp/ - 运行:
build/install/myapp/bin/myapp(Linux/macOS)或build/install/myapp/bin/myapp.bat(Windows)
注意:installDist 不生成单个 JAR,但生成的目录可直接 tar.gz 分发,且所有依赖都在 lib/ 下,启动脚本已写死 -cp lib/*,比手动拼 java -cp 可靠得多。
最容易被忽略的两个点
一是 shadowJar 默认不包含 test 依赖,如果你在测试代码里用了 assertj 并误塞进主流程,它不会打包进去——得显式加 configurations = [project.configurations.runtimeClasspath];二是 application 插件生成的脚本在 Windows 下对空格路径敏感,若项目路径含空格,installDist 启动会失败,得改用 distZip + 自己写 bat。

















