Kotlin 编译器选项、语言版本与迁移策略¶
Kotlin 代码最终由 Kotlin 编译器处理。IDE 运行、Gradle 构建、Maven 构建、命令行 kotlinc 都会把源码交给对应 target 的编译器:JVM、JavaScript、Native、Wasm 等。理解编译器选项,不只是构建工程师的工作;它直接影响语言特性、字节码版本、Java 互操作、警告策略、实验性 API 和升级风险。
本章以 Gradle 为主,因为官方文档推荐在 Gradle Kotlin 项目中使用 compilerOptions {} 配置。命令行选项和 Maven 概念相同,但写法不同。
编译器选项配置层级¶
Kotlin Gradle 插件支持在三个层级配置 compiler options:
- Extension level:
kotlin { compilerOptions { ... } },作为全局默认。 - Target level:例如
kotlin { jvm { compilerOptions { ... } } },覆盖某个 target。 - Compilation unit/task level:例如
compileKotlin,覆盖具体编译任务。
官方文档说明:高层配置作为低层默认值,低层配置会覆盖高层配置。
示例:
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
kotlin {
compilerOptions {
allWarningsAsErrors.set(false)
}
jvm {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_21)
}
}
}
团队建议:
- 通用策略放 extension level。
- target 特有配置放 target level。
- 只有测试、代码生成、特殊任务需要例外时才放 task level。
- 不要在多处配置同一个选项,除非明确需要覆盖。
Java 对比:Java 项目也有 source/target/release 等层级配置,但 Kotlin 多 target 和 Multiplatform 项目更容易出现配置分散。
compilerOptions 取代 kotlinOptions¶
较新的 Kotlin Gradle 插件推荐使用类型安全的 compilerOptions {}。旧的 kotlinOptions {} 已经进入弃用路径。
旧写法:
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
kotlinOptions {
jvmTarget = "17"
freeCompilerArgs += "-Xjsr305=strict"
}
}
新写法:
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
kotlin {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_17)
freeCompilerArgs.add("-Xjsr305=strict")
}
}
新写法的优势:
- 类型安全,例如
JvmTarget.JVM_17。 - 可在 extension、target、task 三层统一建模。
- 更适合 Gradle lazy configuration。
- 与官方文档后续演进保持一致。
languageVersion 与 apiVersion¶
两个选项名字相似,但含义不同。
languageVersion 控制源码语法和语义:
kotlin {
compilerOptions {
languageVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_2)
}
}
这表示即使你用较新的编译器,也只允许使用指定 Kotlin 语言版本支持的语法和语义。
apiVersion 控制能使用的 Kotlin 标准库 API 版本:
kotlin {
compilerOptions {
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_2)
}
}
这会限制代码不能调用更新版本 Kotlin 标准库才引入的 API。
Java 对比:
languageVersion类似 Java 的 source level。apiVersion更接近“只能使用某个版本之前存在的标准库 API”。- Java 的
--release会同时限制语言和 JDK API;Kotlin 将语言和 Kotlin API 版本拆开。
为什么新编译器可以配旧 languageVersion¶
官方编译器参考说明,例如使用 Kotlin 2.4 编译器时,可以通过 -language-version=2.2 让项目只使用 Kotlin 2.2 或更早的语言特性。这有助于渐进迁移。
适合场景:
- 大型项目升级 Kotlin 编译器,但暂时不想引入新语法。
- 多个模块由不同团队维护,需要统一语言基线。
- 库项目希望保持较低调用方要求。
- 新版本编译器带来性能或 bug 修复,但语言特性要延后启用。
不建议:
- 长期锁死旧版本而不迁移。
- 某些模块偷偷启用新版本语法,导致团队读写风格割裂。
- 没有 CI 检查,实际版本和文档约定不一致。
Progressive mode¶
progressiveMode 对应命令行 -progressive。官方解释是:让不稳定代码的弃用和 bug 修复立即生效,而不是等待完整的平滑迁移周期。
Gradle:
kotlin {
compilerOptions {
progressiveMode.set(true)
}
}
适合:
- 活跃维护的应用项目。
- 团队愿意尽早处理编译器新检查。
- 想尽快受益于语言和编译器修复。
不适合:
- 需要最大化源码兼容的公开库。
- 大量历史代码缺少维护人。
- 构建稳定性比尽早迁移更重要的项目。
Java 对比:这类似更激进地开启未来兼容检查或预览迁移警告,但 Kotlin progressive mode 由 Kotlin 编译器明确控制。
optIn¶
Kotlin 的实验性 API 常用 @RequiresOptIn。调用这类 API 时,需要显式 opt-in。
代码级:
@OptIn(ExperimentalStdlibApi::class)
fun useExperimentalApi() {
// ...
}
模块级 Gradle 配置:
kotlin {
compilerOptions {
optIn.add("kotlin.RequiresOptIn")
}
}
更常见的是 opt-in 某个具体实验性注解:
kotlin {
compilerOptions {
optIn.add("com.example.ExperimentalSearchApi")
}
}
建议:
- 应用内部可以在模块级 opt-in,但要写明原因。
- 公开库不要轻易把实验性依赖泄露到稳定 API。
- 如果只是少量调用,优先局部
@OptIn,让风险范围清楚。
freeCompilerArgs¶
freeCompilerArgs 用于传递额外编译器参数,尤其是实验性 -X 选项:
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xjsr305=strict")
freeCompilerArgs.add("-Xannotation-default-target=param-property")
}
}
官方 Gradle 编译器选项页面提示:未来会弃用 freeCompilerArgs 这个属性;如果某个选项还没有类型安全 DSL,可以暂时放这里。
实践建议:
- 能用类型安全 DSL 的选项,不要放进
freeCompilerArgs。 -X选项是实验性或高级选项,必须在代码库文档中说明。- 不要复制网上一长串
freeCompilerArgs而不理解每一项。 - 升级 Kotlin 时复查所有
-X参数。
JVM target¶
jvmTarget 控制生成的 JVM 字节码目标版本:
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
kotlin {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_21)
}
}
它影响:
- 生成字节码能运行在哪些 JVM 上。
- 是否可使用某些 JVM 字节码特性。
- 与 Java 编译任务 target 的一致性。
- 库发布后的最低运行环境。
Java 对比:
- Java
--release 17限制字节码和 JDK API。 - Kotlin
jvmTarget主要控制字节码目标。 - 如果还要限制 JDK API,可关注
-Xjdk-release。
JVM target 与 Java toolchain¶
常见 Gradle 配置:
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(21))
}
}
kotlin {
compilerOptions {
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_21)
}
}
不要混淆:
- Java toolchain:用哪个 JDK 编译/运行构建工具链。
jvmTarget:Kotlin 生成什么版本的 JVM 字节码。
例如,用 JDK 21 构建,但生成 JVM 17 字节码是可能的:
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(21))
}
}
kotlin {
compilerOptions {
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17)
}
}
库项目尤其要明确最低运行 JVM,不要因为本机 JDK 很新就无意中抬高字节码目标。
jvmTargetValidationMode¶
Kotlin Gradle 插件可以验证 Kotlin 与 Java 编译任务的 JVM target 是否兼容。官方 Gradle 编译器选项页面列出 jvmTargetValidationMode,可选值包括:
ERRORWARNINGIGNORE
建议在团队项目中保持默认严格策略或显式设为 error,避免 Kotlin 生成 17 字节码、Java 生成 21 字节码这类混乱。
这个选项是 KotlinCompile 任务上的属性。多数项目保持默认 ERROR 即可;如果你要显式配置,按当前 Kotlin Gradle 插件 API 和 IDE 补全为准,不要从旧博客复制过时类型名。
-Xjdk-release¶
命令行选项 -Xjdk-release=version 会:
- 指定生成 JVM 字节码目标。
- 限制 classpath 中可用 JDK API 到指定 Java 版本。
- 自动设置
-jvm-target。
示例:
kotlinc Main.kt -Xjdk-release=17 -d out
官方文档提醒,这个选项不保证对每个 JDK 发行版都完全有效。Gradle 项目中,通常先用 Java toolchain + jvmTarget 建立清晰基线;需要严格 JDK API 限制时再评估 -Xjdk-release。
javaParameters¶
javaParameters 会生成 Java 1.8 反射需要的方法参数元数据:
kotlin {
compilerOptions {
javaParameters.set(true)
}
}
适合:
- Java 反射框架需要读取真实参数名。
- Spring MVC、依赖注入、序列化、命令行框架等可能依赖参数名。
注意 Kotlin 本身还会生成 Kotlin metadata。Java 反射读取参数名和 Kotlin 反射读取 Kotlin 元数据不是同一件事。
Java 对比:Java 需要 -parameters 才能在反射中保留参数名。Kotlin 的 javaParameters 对应这个能力。
jvmDefault¶
jvmDefault 控制 Kotlin 接口中的函数如何编译成 JVM default methods。
官方列出的模式包括:
ENABLE:默认模式,生成接口 default 实现并包含兼容桥。NO_COMPATIBILITY:只生成接口 default 实现,跳过兼容桥和DefaultImpls。DISABLE:只生成兼容桥和DefaultImpls,跳过 default methods。
库项目要谨慎修改这个选项,因为它会影响二进制 ABI 和 Java 调用方式。
示意:
interface Greeter {
fun greet(name: String): String = "Hello, $name"
}
在不同 jvmDefault 模式下,生成字节码结构不同。对应用项目影响通常较小,对公开库影响可能很大。
allWarningsAsErrors、extraWarnings 与 warning-level¶
把所有警告当错误:
kotlin {
compilerOptions {
allWarningsAsErrors.set(true)
}
}
启用额外检查:
kotlin {
compilerOptions {
extraWarnings.set(true)
}
}
命令行也支持更细粒度的 warning level:
kotlinc -Xwarning-level=DIAGNOSTIC_NAME:error Main.kt
建议:
- 新项目可逐步开启
allWarningsAsErrors。 - 历史项目先清理警告,再强制。
- 对生成代码或第三方插件产生的警告要谨慎处理。
- 不要为了过 CI 全局
suppressWarnings,应该定位具体原因。
Java 对比:这类似 -Werror、Error Prone、Checkstyle、SpotBugs 等质量门禁。Kotlin 编译器警告本身就是重要质量信号。
nullability annotations¶
Kotlin 调 Java 时,Java 注解决定 Kotlin 如何理解空性。-Xnullability-annotations 可以配置特定 Java 注解包的空性严格程度。
另外,Spring 项目常见:
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xjsr305=strict")
}
}
这让 Kotlin 更严格地解释 JSR-305 相关注解。对于 Java 迁移 Kotlin 的项目,这是非常关键的选项,因为它会把部分潜在 NPE 变成编译期问题。
风险:
- 从宽松改严格可能产生大量编译错误。
- 第三方库注解不准确时会影响开发体验。
- 不同模块严格程度不一致会导致迁移混乱。
建议在迁移计划中分阶段启用。
实验性 -X 选项¶
官方 Kotlin evolution principles 明确说明:没有 -X 或 -XX 前缀的支持选项属于更稳定的公开 API;-X 和 -XX 选项是实验性的,可以随时添加或移除。
常见 -X 选项包括:
-Xexplicit-context-arguments:启用 context parameters 的显式 context argument。-Xname-based-destructuring:配置按名解构。-Xreturn-value-checker:配置未使用返回值检查。-Xjvm-expose-boxed:为 inline value class 生成 Java 可访问的 boxed 版本。-Xjsr305=strict:常用于 Java 注解空性严格检查。
实践建议:
- 每个
-X选项都要写注释或文档。 - 升级 Kotlin 前先查 release notes 和官方编译器参考。
- 公共库避免依赖不稳定
-X选项暴露 API。 - CI 中打印或检查实际 compiler args,避免本地和 CI 不一致。
命令行 kotlinc¶
最小编译:
kotlinc hello.kt -include-runtime -d hello.jar
运行:
java -jar hello.jar
常见命令行选项:
-classpath/-cp:指定 classpath。-d:输出目录、JAR 或 ZIP。-include-runtime:把 Kotlin runtime 打进可运行 JAR。-language-version:语言版本。-api-version:API 版本。-progressive:progressive mode。-opt-in:启用 opt-in API。-X:查看高级选项。
命令行适合学习、脚本和工具链调试。正式项目优先把配置写进 Gradle/Maven,保证 CI 可复现。
Maven 项目¶
Maven 中通过 Kotlin Maven plugin 的 <configuration><args> 传选项:
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<configuration>
<args>
<arg>-Xjsr305=strict</arg>
</args>
</configuration>
</plugin>
概念和 Gradle 相同,但缺少 Gradle Kotlin DSL 的类型安全体验。大型 Kotlin 项目如果没有强 Maven 约束,通常优先 Gradle Kotlin DSL。
多平台项目的选项策略¶
Multiplatform 项目更要注意层级:
kotlin {
compilerOptions {
allWarningsAsErrors.set(true)
}
jvm {
compilerOptions {
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17)
}
}
js {
compilerOptions {
// JS 特有选项
}
}
}
建议:
- commonMain 只放跨平台通用策略。
- JVM、JS、Native、Wasm 各自 target 放平台选项。
- 不要把 JVM 专用
freeCompilerArgs放到所有 target。 - 发布 KMP 库时关注 klib 兼容性和 Kotlin 版本要求。
升级 Kotlin 的工程策略¶
推荐流程:
- 阅读 Kotlin release notes 和 breaking changes。
- 升级 Kotlin Gradle plugin,但先保持原有
languageVersion/apiVersion。 - 运行完整 CI。
- 修复新警告和编译错误。
- 再逐步提高
languageVersion/apiVersion。 - 复查
freeCompilerArgs中所有-X选项。 - 对公开库运行 binary compatibility 检查。
- 更新文档中的最低 Kotlin/JVM 版本。
不要把这些动作混在一次提交里:
- 升级 Kotlin 编译器。
- 升级 Gradle。
- 升级 Spring/Android/Compose 插件。
- 批量格式化。
- 大规模语言特性迁移。
拆小变更可以让问题定位更直接。
推荐基线模板¶
应用项目:
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
kotlin {
compilerOptions {
languageVersion.set(KotlinVersion.KOTLIN_2_2)
apiVersion.set(KotlinVersion.KOTLIN_2_2)
jvmTarget.set(JvmTarget.JVM_21)
allWarningsAsErrors.set(false)
extraWarnings.set(true)
freeCompilerArgs.add("-Xjsr305=strict")
}
}
公开库项目:
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
kotlin {
compilerOptions {
languageVersion.set(KotlinVersion.KOTLIN_2_2)
apiVersion.set(KotlinVersion.KOTLIN_2_2)
jvmTarget.set(JvmTarget.JVM_17)
allWarningsAsErrors.set(true)
}
}
这里的版本只是示意。真实项目应根据团队支持矩阵选择 Kotlin 版本、JDK 版本和 JVM target。
常见误区¶
误区一:Kotlin 版本等于 JVM 版本¶
Kotlin compiler version、languageVersion、apiVersion、Java toolchain、jvmTarget 是不同概念。它们可以相关,但不能混为一谈。
误区二:用新 JDK 构建就一定只能在新 JDK 运行¶
不一定。构建 JDK 和目标字节码版本可以分开。关键看 jvmTarget、JDK API 使用和运行环境。
误区三:freeCompilerArgs 越多越专业¶
恰好相反。每个额外参数都是未来升级成本。能不用就不用,能用类型安全 DSL 就不用字符串参数。
误区四:警告不影响运行,可以忽略¶
Kotlin 的很多警告是未来错误、空安全风险、弃用 API 或行为变化提醒。长期忽略会让升级成本指数级上升。
误区五:库项目可以随便提高 languageVersion¶
库的 Kotlin 版本、stdlib API、字节码目标都会影响消费者。公开库应该有明确支持矩阵,并在发布说明中写清楚。
官方参考¶
- Compiler options in the Kotlin Gradle plugin:https://kotlinlang.org/docs/gradle-compiler-options.html
- Kotlin compiler options:https://kotlinlang.org/docs/compiler-reference.html
- Kotlin evolution principles:https://kotlinlang.org/docs/kotlin-evolution-principles.html
- Configure a Gradle project:https://kotlinlang.org/docs/gradle-configure-project.html