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,可选值包括:

  • ERROR
  • WARNING
  • IGNORE

建议在团队项目中保持默认严格策略或显式设为 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 的工程策略

推荐流程:

  1. 阅读 Kotlin release notes 和 breaking changes。
  2. 升级 Kotlin Gradle plugin,但先保持原有 languageVersion / apiVersion
  3. 运行完整 CI。
  4. 修复新警告和编译错误。
  5. 再逐步提高 languageVersion / apiVersion
  6. 复查 freeCompilerArgs 中所有 -X 选项。
  7. 对公开库运行 binary compatibility 检查。
  8. 更新文档中的最低 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、languageVersionapiVersion、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