application插件是最省事的方案,它专为生成可执行jvm应用设计,自动处理main-class、class-path及依赖打包,生成解压即用的install目录结构;需配置mainclass.set("包名.类名"),kotlin顶层main对应文件名kt,如main.kt→mainkt。

用 application 插件是最省事的方案
Gradle 的 application 插件专为生成可执行 JAR 设计,它自动处理 Main-Class、Class-Path 和依赖打包,不用手动拼 MANIFEST.MF 或硬编码路径。适用于大多数 Kotlin 命令行工具或简单服务类项目。
在 build.gradle.kts 中添加:
plugins {
application
kotlin("jvm") version "1.9.24" // 与你实际版本一致
}
application {
mainClass.set("com.example.AppKt") // 注意:Kotlin 顶层函数编译后是 `AppKt`
}
然后运行:./gradlew run 测试是否能跑通;再执行:./gradlew shadowJar(如果你用了 Shadow 插件)或更常见的:./gradlew jar ——但注意:jar 任务默认不打包依赖,必须配合 application 插件才能生成真正可执行的产物。
关键点:
-
mainClass.set(...)必须写全名,Kotlin 顶层fun main()对应的是包名.文件名Kt,比如src/main/kotlin/Main.kt→MainKt - 如果项目混有 Java 类,且 Java 里定义了
public static void main(String[]),则直接写该类全名,如com.example.App -
application插件会把依赖打到build/install/<project-name>/lib/</project-name>下,它生成的是「解压即用」结构,不是单个 JAR;若坚持要单个 JAR,请跳到下一条
要单个 fat JAR,用 shadowJar 而不是默认 jar
Gradle 默认的 jar 任务只打包本模块 class,不包含任何依赖,直接 java -jar xxx.jar 必报 NoClassDefFoundError。想合并所有依赖进一个 JAR,得用 com.github.johnrengelman.shadow 插件。
在 build.gradle.kts 中加:
plugins {
kotlin("jvm") version "1.9.24"
id("com.github.johnrengelman.shadow") version "8.1.1""
shadowJar {
archiveBaseName.set("myapp")
archiveClassifier.set("") // 清空 classifier,避免生成 myapp-all.jar
mergeServiceFiles() // 合并 META-INF/services 下的 SPI 配置(如 Jackson 模块)
manifest {
attributes(mapOf("Main-Class" to "com.example.MainKt"))
}
}
执行:./gradlew shadowJar,产物在 build/libs/myapp.jar。
常见坑:
- 没加
mergeServiceFiles()→ 运行时报com.fasterxml.jackson.databind.JsonMappingException: No serializer found等 SPI 相关错误 - 用了 Kotlin DSL 却写 Groovy 风格配置(如
archiveBaseName = "myapp")→ 编译失败,Kotlin DSL 必须用.set() - Shadow 插件版本和 Gradle 版本不兼容(例如 Gradle 8.5+ 推荐用 Shadow 8.1.x,Shadow 7.x 在 8.5 上会警告或失败)
Kotlin 标准库必须显式包含,不能靠 IDE 自动推断
哪怕你在 IDEA 里看到 kotlin-stdlib 已经出现在 External Libraries 里,Gradle 构建时也不会自动把它打进 JAR —— 因为它是 implementation 依赖,默认不参与 runtimeClasspath 的归档逻辑。
验证方式很简单:解压生成的 JAR,看里面有没有 kotlin/ 开头的 class 文件,或者运行时报 java.lang.NoClassDefFoundError: kotlin/jvm/internal/Intrinsics 就是缺它。
解决办法只有两个:
- 用
shadowJar:它默认就把runtimeClasspath全部拉进来,只要你的dependencies里写了implementation("org.jetbrains.kotlin:kotlin-stdlib"),就一定包含 - 手动在
jar { from(...) }里补:from({ configurations.runtimeClasspath.get().files })(不推荐,易漏、难维护)
别信“IDE 能跑,JAR 就能跑”——IDE 的 classpath 是动态拼的,JAR 是静态快照,差一个字节都启动不了。
Java/Kotlin 混合项目打包没特殊操作,但要注意源码目录声明
Gradle 默认只扫描 src/main/java,如果你的 Kotlin 代码放在 src/main/kotlin,必须显式告诉它:
sourceSets {
main {
java {
srcDirs("src/main/java", "src/main/kotlin")
}
}
}
否则 ./gradlew compileKotlin 会成功,但 ./gradlew jar 可能漏掉 Kotlin 编译出的 .class,导致运行时找不到主类。
另外,混合项目中 main() 可以在 Java 或 Kotlin 里,只要 mainClass.set(...) 指对就行。但注意:Kotlin 的 fun main() 编译后是 static void main(String[]),签名完全兼容 JVM,无需额外适配。
最容易被忽略的一点是:资源文件(如 src/main/resources/config.yml)默认会被打包进 JAR,但如果你在代码里用 File("config.yml") 去读,运行时会失败 —— 因为资源在 JAR 内部,不是文件系统路径。必须改用 javaClass.classLoader.getResourceAsStream("config.yml")。











