Kotlin + Spring Boot 深度指南
知识结构
一、为什么选择 Kotlin 做后端
1.1 Kotlin vs Java:代码量对比
同样的功能,Kotlin 通常减少 20-40% 的代码量。
Java(传统 POJO):
public class User { private Long id; private String name; private String email;
public User(Long id, String name, String email) { this.id = id; this.name = name; this.email = email; }
public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; }
@Override public boolean equals(Object o) { /* ... */ } @Override public int hashCode() { /* ... */ } @Override public String toString() { /* ... */ }}Kotlin Data Class:
data class User(val id: Long, val name: String, val email: String)一行代码自动生成 equals()、hashCode()、toString()、copy()、componentN() 方法。
Java 21 的 Record 可以缩减代码,但 Record 是不可变的且不支持继承,使用场景比 Data Class 更受限。
1.2 空安全类型系统
Kotlin 将可空性编码到类型系统中,在编译期消除整类 NPE 问题:
// 编译错误:不能将 null 赋给非空类型var name: String = null // Error!
// 显式声明可空var name: String? = null // OK
// 安全调用链val length = user?.address?.city?.length
// Elvis 操作符val displayName = user?.name ?: "Anonymous"
// 非空断言(谨慎使用)val length = name!!.length // 如果 name 为 null,抛出 NPESpring Boot 4.0 通过 JSpecify 注解,使 Spring API 的空安全信息自动映射到 Kotlin 类型系统,消除了 JVM 平台类型(Type!)的歧义。
1.3 扩展函数
在不修改原始类的情况下为其添加方法:
// 为 String 添加转换方法fun String.toSlug(): String = lowercase().replace(" ", "-").replace(Regex("[^a-z0-9-]"), "")
// 使用"Hello World!".toSlug() // "hello-world"
// Spring 提供的扩展函数import org.springframework.data.repository.findByIdOrNull
val user = repository.findByIdOrNull(id) // 返回 User? 而非 Optional<User>Spring Framework 本身大量使用扩展函数来提升 Kotlin 开发体验,如 findByIdOrNull 将 Java 的 Optional 转换为 Kotlin 友好的可空类型。
1.4 Sealed Class 错误处理
sealed class ApiResult<out T> { data class Success<T>(val data: T) : ApiResult<T>() sealed class Failure : ApiResult<Nothing>() { data class NotFound(val message: String) : Failure() data class ValidationError(val errors: List<String>) : Failure() data class ServerError(val cause: Throwable) : Failure() }}
// 编译器强制穷举匹配 --- 漏掉任何分支都会报错fun handleResult(result: ApiResult<User>): ResponseEntity<*> = when (result) { is ApiResult.Success -> ResponseEntity.ok(result.data) is ApiResult.Failure.NotFound -> ResponseEntity.notFound().build() is ApiResult.Failure.ValidationError -> ResponseEntity.badRequest().body(result.errors) is ApiResult.Failure.ServerError -> ResponseEntity.internalServerError().build() }1.5 作用域函数
| 函数 | 对象引用 | 返回值 | 典型场景 |
|---|---|---|---|
let | it | Lambda 结果 | 空安全调用链、转换 |
run | this | Lambda 结果 | 对象配置 + 计算结果 |
apply | this | 对象本身 | 对象初始化 |
also | it | 对象本身 | 附加操作(日志、验证) |
with | this | Lambda 结果 | 对已有对象分组调用 |
// let:空安全 + 转换val dto = user?.let { UserDto(it.name, it.email) }
// apply:对象初始化val user = User().apply { name = "Alice" email = "alice@example.com" role = Role.ADMIN}
// also:附加操作(如日志)return repository.save(entity).also { logger.info("Created user: ${it.id}")}1.6 Kotlin DSL 能力
Kotlin 的 Lambda with receiver 语法支持构建类型安全的 DSL:
// Spring Security DSLhttp { authorizeHttpRequests { authorize("/api/public/**", permitAll) authorize(anyRequest, authenticated) } oauth2ResourceServer { jwt { } }}
// Spring Bean Definition DSLval myBeans = beans { bean<UserService>() bean { OrderService(ref(), ref()) } profile("prod") { bean<ProdDataSource>() }}
// coRouter DSL(协程路由)coRouter { "/api/users".nest { GET("", handler::findAll) GET("/{id}", handler::findById) POST("", handler::create) }}二、Kotlin 协程 vs Java Virtual Threads
flowchart LR
subgraph KT["Kotlin Coroutines"]
K1["库级实现"] --> K2["suspend 函数"]
K2 --> K3["结构化并发"]
K3 --> K4["Flow 流式处理"]
end
subgraph VT["Java Virtual Threads"]
V1["JVM 原生"] --> V2["兼容阻塞 API"]
V2 --> V3["一行配置开启"]
V3 --> V4["无需改写代码"]
end
| 维度 | Kotlin 协程 | Java Virtual Threads |
|---|---|---|
| 实现层级 | 库级别(kotlinx.coroutines) | JVM 原生(Java 21+) |
| 编程模型 | suspend 函数 + 结构化并发 | 与传统线程 API 兼容 |
| 100 万并发创建 | ~2 ms | ~4 ms |
| 学习曲线 | 需要学习协程概念 | 几乎零学习成本 |
| 取消机制 | 内建协作式取消 | 需手动 interrupt |
| 错误传播 | 结构化(父子关系自动传播) | 无结构化保证 |
| 流式处理 | Flow(冷流,惰性) | 无内建流式 API |
| Spring 集成 | WebFlux + suspend + Flow | spring.threads.virtual.enabled=true |
| 最佳场景 | 复杂异步编排、流式处理 | 简单 I/O 阻塞场景 |
性能对比:多个基准测试表明,Spring WebFlux + 协程与纯 WebFlux 响应式性能无显著差异,协程的开销低于 0.1 秒。核心优势在于可读性:用命令式风格写出非阻塞代码。
Virtual Threads 和 Coroutines 并非互斥。在 Spring Boot 4 中,你可以同时使用两者 --- Virtual Threads 处理简单阻塞 I/O,Coroutines 处理复杂异步编排。
来源:Making WebFlux code readable with Kotlin coroutines (Allegro Tech)
三、Spring Boot + Kotlin 深度集成
3.1 Spring Boot 4.0 --- Kotlin 的黄金时代
Spring Boot 4.0(2025 年 11 月 GA)将 Kotlin 2.2 设为官方基线,这意味着:
- 所有 Spring API 使用 JSpecify 注解标注空安全,Kotlin 编译器自动识别
- 平台类型(
Type!)从 Spring、Reactor、Micrometer API 中消除 - 内建 kotlinx.serialization 支持
- 协程上下文传播(Micrometer Tracing)一等支持
- K2 编译器作为默认,编译速度提升高达 94%
2025 年 5 月,JetBrains 与 Spring 正式达成战略合作,共同推进 Kotlin 在服务端的发展。
来源:Strategic Partnership with Spring (JetBrains)、Spring Boot 4.0 Release Notes
3.2 JSpecify 空安全集成
Spring Boot 4 采用 JSpecify(@NullMarked、@Nullable、@NonNull)替代之前的 JSR 305:
// Spring 接口标注了 @NullMarked,返回类型默认非空// 标注 @Nullable 的方法在 Kotlin 中返回可空类型
// 以前(Spring Boot 3.x)--- 平台类型,可能 NPEval user: User! = repository.findById(1).orElse(null)
// 现在(Spring Boot 4.0)--- 编译器知道可空性val user: User? = repository.findByIdOrNull(1) // 明确可空IntelliJ IDEA 2025.3 提供原生 JSpecify 支持,带有完整的数据流分析。
3.3 kotlinx.serialization 支持
Spring Boot 4.0 新增 spring-boot-starter-kotlin-serialization starter:
plugins { kotlin("plugin.serialization") version "2.2.21"}
dependencies { implementation("org.springframework.boot:spring-boot-starter-kotlin-serialization")}@Serializabledata class UserDto( val id: Long, val name: String, val email: String)
// 标注 @Serializable 的类自动使用 kotlinx.serialization// 未标注的类仍使用 Jackson(Actuator 等仍需 Jackson)通过 spring.kotlinx.serialization.json.* 属性可配置 pretty-print、ignore-unknown-keys 等选项。
3.4 Spring Security Kotlin DSL
import org.springframework.security.config.annotation.web.invoke // 关键 import!
@Configuration@EnableWebSecurityclass SecurityConfig {
@Bean fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { http { authorizeHttpRequests { authorize("/api/public/**", permitAll) authorize("/api/admin/**", hasRole("ADMIN")) authorize(anyRequest, authenticated) } httpBasic { } csrf { disable() } cors { } } return http.build() }
@Bean fun userDetailsService(): UserDetailsService { val user = User.withDefaultPasswordEncoder() .username("user") .password("password") .roles("USER") .build() return InMemoryUserDetailsManager(user) }}注意:
import org.springframework.security.config.annotation.web.invoke是 Kotlin DSL 生效的必须导入,缺少会报 “Too many arguments” 编译错误。
3.5 WebFlux + 协程
Spring WebFlux 原生支持 suspend 函数和 Flow 返回类型:
@RestController@RequestMapping("/api/users")class UserController(private val service: UserService) {
// suspend 函数 --- 返回单个结果 @GetMapping("/{id}") suspend fun getUser(@PathVariable id: Long): User = service.findById(id) ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
// Flow --- 返回流式结果 @GetMapping fun getAllUsers(): Flow<User> = service.findAll()
// 并行调用 --- 结构化并发 @GetMapping("/{id}/profile") suspend fun getUserProfile(@PathVariable id: Long): UserProfile = coroutineScope { val user = async { service.findById(id) } val orders = async { service.getOrders(id) } val stats = async { service.getStats(id) } UserProfile( user = user.await() ?: throw ResponseStatusException(HttpStatus.NOT_FOUND), orders = orders.await(), stats = stats.await() ) }}四、协程深入
4.1 结构化并发
结构化并发保证两件事:
- 父协程始终等待所有子协程完成
- 父协程绝不会”丢失”任何子协程 --- 当作用域取消时,所有子协程自动取消
// coroutineScope:任何子协程失败,所有子协程取消suspend fun fetchAllData(): CombinedData = coroutineScope { val users = async { fetchUsers() } // 并行 val orders = async { fetchOrders() } // 并行 CombinedData(users.await(), orders.await()) // 如果 fetchUsers 抛异常,fetchOrders 自动取消}
// supervisorScope:子协程失败互不影响suspend fun fetchBestEffort(): PartialData = supervisorScope { val users = async { fetchUsers() } val orders = async { fetchOrders() } PartialData( users = runCatching { users.await() }.getOrDefault(emptyList()), orders = runCatching { orders.await() }.getOrDefault(emptyList()) )}4.2 Flow vs Flux
| 特性 | Kotlin Flow | Reactor Flux |
|---|---|---|
| 编程风格 | 顺序/命令式 | 声明式/函数式 |
| 背压 | 基于挂起(suspension) | Reactive Streams 规范 |
| 学习曲线 | 对 Kotlin 开发者较低 | 较高(Reactor API 复杂) |
| 可读性 | 更好 | 嵌套 operator 可能复杂 |
map 操作 | 接受 suspend 函数(map 内可异步) | 异步需 flatMap |
| 性能 | 与 Flux 相当 | 与 Flow 相当 |
互转桥接(kotlinx-coroutines-reactor):
import kotlinx.coroutines.reactor.asFluximport kotlinx.coroutines.reactive.asFlow
// Flow -> Fluxval flux: Flux<User> = flow.asFlux()
// Flux -> Flowval flow: Flow<User> = flux.asFlow()4.3 错误处理模式
| 场景 | 工具 |
|---|---|
| 所有子任务是一个整体 | coroutineScope { } |
| 子任务可独立失败 | supervisorScope { } |
| 发射后不管的任务 | launch { } |
| 需要异步结果 | async { } + await() |
| 未捕获异常日志 | CoroutineExceptionHandler |
| 阻塞/非阻塞桥接 | runBlocking(仅用于测试/main) |
注意:在
async中捕获await()的异常不会阻止异常向父 Job 传播。如需独立处理子任务失败,必须使用supervisorScope。
4.4 Spring Boot 4 协程上下文传播
Spring Boot 4 / Spring Framework 7 提供一等的协程上下文传播支持:
spring.reactor.context-propagation=autoimplementation("io.micrometer:context-propagation")PropagationContextElement 自动在协程边界之间携带 Micrometer Tracing 上下文,使分布式追踪在协程中透明工作。
在 Spring Boot 3.x 中需要手动处理(MDCContext、ContextSnapshot 等)。
五、JPA + Kotlin 最佳实践
5.1 Entity 类:不要用 Data Class
// 错误:Data Class 的 equals/hashCode 基于所有属性// 自增 ID 在 persist 前后变化,会破坏 HashSet/HashMapdata class User( @Id @GeneratedValue val id: Long = 0, // 危险! val name: String)
// 正确:使用普通 class@Entityclass User( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) var id: Long? = null,
@Column(nullable = false) var name: String,
@Column(nullable = false, unique = true) var email: String)5.2 必须的 Gradle 插件配置
plugins { kotlin("plugin.spring") // 打开 Spring 注解的类(@Configuration、@Service 等) kotlin("plugin.jpa") // 为 JPA 实体生成无参构造函数}
// kotlin-spring 不会打开 JPA 实体类,必须手动配置allOpen { annotation("jakarta.persistence.Entity") annotation("jakarta.persistence.MappedSuperclass") annotation("jakarta.persistence.Embeddable")}为什么需要 allOpen:Kotlin 类默认是 final 的,而 Hibernate 的延迟加载需要创建代理子类。不配置 allOpen 会导致 @ManyToOne、@OneToOne 的延迟加载失效,引发 N+1 问题。
为什么需要 no-arg(plugin.jpa):JPA 要求实体类有无参构造函数。该插件生成的是 synthetic 构造函数,仅供反射使用,不能在代码中直接调用。
来源:How to Avoid Common Pitfalls With JPA and Kotlin (JetBrains)
5.3 Repository 示例
// 同步(Spring MVC)@Repositoryinterface UserRepository : JpaRepository<User, Long> { fun findByEmail(email: String): User? fun findByNameContaining(name: String): List<User>}
// 协程(Spring WebFlux + R2DBC)@Repositoryinterface UserRepository : CoroutineCrudRepository<User, Long> { fun findByLastName(lastName: String): Flow<User>}Spring Data 提供的 Kotlin 扩展函数 findByIdOrNull 将 Optional<T> 转换为 T?,更符合 Kotlin 习惯:
import org.springframework.data.repository.findByIdOrNull
val user: User? = repository.findByIdOrNull(id)六、完整项目配置
6.1 Gradle Kotlin DSL
| 特性 | Kotlin DSL (.kts) | Groovy DSL |
|---|---|---|
| 类型安全 | 编译时检查 | 运行时报错 |
| IDE 支持 | 优秀(IntelliJ 原生) | 自动补全有限 |
| 构建速度 | 脚本编译略慢 | 脚本编译更快 |
| 未来方向 | Gradle 9.0 推荐新项目使用 | 采用率下降 |
6.2 完整 build.gradle.kts 示例
plugins { id("org.springframework.boot") version "4.0.2" id("io.spring.dependency-management") version "1.1.7" kotlin("jvm") version "2.2.21" kotlin("plugin.spring") version "2.2.21" kotlin("plugin.jpa") version "2.2.21" kotlin("plugin.serialization") version "2.2.21"}
group = "com.example"version = "0.0.1-SNAPSHOT"
java { toolchain { languageVersion = JavaLanguageVersion.of(21) }}
repositories { mavenCentral()}
dependencies { implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.boot:spring-boot-starter-data-jpa") implementation("org.springframework.boot:spring-boot-starter-security") implementation("org.springframework.boot:spring-boot-starter-validation") implementation("com.fasterxml.jackson.module:jackson-module-kotlin") implementation("org.jetbrains.kotlin:kotlin-reflect")
// 协程 implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")
// kotlinx.serialization(Spring Boot 4.0) implementation("org.springframework.boot:spring-boot-starter-kotlin-serialization")
runtimeOnly("com.h2database:h2") runtimeOnly("org.postgresql:postgresql")
testImplementation("org.springframework.boot:spring-boot-starter-test") testImplementation("io.mockk:mockk:1.13.16") testImplementation("com.ninja-squad:springmockk:4.0.2")}
allOpen { annotation("jakarta.persistence.Entity") annotation("jakarta.persistence.MappedSuperclass") annotation("jakarta.persistence.Embeddable")}
kotlin { compilerOptions { freeCompilerArgs.addAll("-Xjsr305=strict") }}
tasks.withType<Test> { useJUnitPlatform()}6.3 推荐目录结构
src/main/kotlin/com/example/app/ Application.kt config/ # SecurityConfig, WebConfig 等 controller/ # REST 控制器 service/ # 业务逻辑 repository/ # 数据访问 model/ entity/ # JPA 实体 dto/ # 数据传输对象 extension/ # Kotlin 扩展函数 exception/ # 自定义异常、全局错误处理大型应用建议按功能模块而非按层组织:
src/main/kotlin/com/example/app/ user/ UserController.kt UserService.kt UserRepository.kt User.kt order/ OrderController.kt OrderService.kt ...七、测试:MockK + Kotest vs JUnit + Mockito
| 特性 | MockK + Kotest | JUnit 5 + Mockito |
|---|---|---|
| 协程 Mock | 内建(coEvery、coVerify) | 无原生支持 |
| Final 类 Mock | 默认支持 | 需 mockito-inline |
| 空安全 | Kotlin 原生 | Java 风格 |
| 断言语法 | actual shouldBe expected | assertEquals(expected, actual) |
| 测试风格 | FunSpec、DescribeSpec、BDD 等多种 | 单一 @Test 风格 |
| 属性测试 | 内建 | 需外部库 |
@ExtendWith(MockKExtension::class)class UserServiceTest {
@MockK lateinit var repository: UserRepository
@InjectMockKs lateinit var service: UserService
@Test fun `should find user by id`() { // Given val user = User(1, "Alice", "alice@test.com") every { repository.findByIdOrNull(1) } returns user
// When val result = service.findById(1)
// Then result shouldBe user verify(exactly = 1) { repository.findByIdOrNull(1) } }
@Test fun `should return null when user not found`() { every { repository.findByIdOrNull(999) } returns null
service.findById(999) shouldBe null }}协程测试:
@Testfun `should fetch user profile with parallel calls`() = runTest { coEvery { userRepository.findByIdOrNull(1) } returns mockUser coEvery { orderClient.getOrders(1) } returns listOf(mockOrder) coEvery { statsClient.getStats(1) } returns mockStats
val profile = service.getUserProfile(1)
profile.user shouldBe mockUser profile.orders shouldHaveSize 1 coVerify { userRepository.findByIdOrNull(1) }}建议:Kotlin 项目优先使用 MockK + Kotest,尤其是涉及协程的场景。两者可以在同一项目中共存。
来源:MockK (official)、Unit Testing Kotlin with MockK vs Mockito (LogRocket)
八、从 Java 迁移到 Kotlin
8.1 渐进迁移策略(JetBrains 推荐五步法)
flowchart LR
A["1. 测试先行\n用 Kotlin 写新测试"] --> B["2. 新功能用 Kotlin\n保留已有 Java 代码"]
B --> C["3. 逐文件转换\n从外层(Controller)开始"]
C --> D["4. 渐进迁移\n不做大爆炸重写"]
D --> E["5. 重构为地道 Kotlin\ndata class / 扩展函数 / sealed class"]
- 测试先行 --- 在测试中引入 Kotlin(零生产风险),使用 MockK + Kotest
- 新功能用 Kotlin --- 新类/服务用 Kotlin 写,已有 Java 代码不动
- 逐文件转换 --- 从外层(Controller)开始,调用已有 Java 领域代码
- 渐进迁移 --- 不做大爆炸重写,End-of-Life 的应用保持原样
- 重构为地道 Kotlin --- 转换后重构:data class、扩展函数、sealed class、协程
8.2 常见迁移模式
| Java 模式 | Kotlin 替代 |
|---|---|
| POJO + Getter/Setter | data class |
Optional<T> | T?(可空类型) |
| Java Streams | Kotlin 集合函数(map、filter、groupBy) |
| Builder 模式 | 命名参数 + 默认值 |
| 工具类(Utils) | 扩展函数 |
switch 语句 | when 表达式(更强大、可穷举) |
try-catch 异常层次 | sealed class 结果类型 |
8.3 IntelliJ 自动转换
IntelliJ IDEA 提供自动 Java-to-Kotlin 转换器(Ctrl+Alt+Shift+K)。转换后务必手动重构为地道 Kotlin --- 转换器生成的代码正确但不一定优雅。
来源:The Ultimate Guide to Successfully Adopting Kotlin (JetBrains)
九、生产案例
9.1 企业采用情况
| 公司 | 场景 | 规模 |
|---|---|---|
| Mercedes-Benz.io | 云原生应用 | 日活 350 万用户 |
| ING | 支付引擎 | 600 万移动用户,年处理 45 亿笔支付 |
| N26 | 微服务 | 60% 服务已转为 Kotlin |
| Expedia | GraphQL API | 全球旅游平台 |
| Atlassian (Jira) | 服务架构 | Jira Software 云版 |
| Adobe | Experience Platform | 企业级 SaaS |
| Allegro | 电商后端 | 波兰最大零售商 |
KotlinConf 2025 上,Meta、AWS、Duolingo、Uber 分享了生产环境 Kotlin 实践。27% 的 Spring 开发者已在使用 Kotlin。
来源:Kotlin Case Studies、Industry Leaders on KotlinConf’25 Stage
9.2 GraalVM Native Image
Spring Boot 支持 Kotlin 的 GraalVM Native Image 编译:
- 启动时间:50-75 ms(vs JVM 3-5 秒)
- 内存占用:50-80 MB(vs JVM 180-500 MB)
- Docker 镜像:50-170 MB(vs JVM 400-1000 MB)
限制:
- 封闭世界假设:无运行时 Bean 变更,固定 classpath
- 反射需要显式配置文件
- Kotlin Gradle Plugin 顺序很重要(先应用 Kotlin 插件)
- Spring Boot 4.0 要求 GraalVM 25+
- 函数式 Bean DSL 在 AOT 模式下支持有限
9.3 已知限制与解决方案
| 问题 | 解决方案 |
|---|---|
| JPA Entity 不能用 data class | 使用普通 class + var 属性 |
| Entity 类需要 allOpen 配置 | 配置 allOpen 打开 JPA 注解类 |
| 协程可观测性(Boot 3.x) | 手动上下文传播 / 升级 Boot 4 |
| kotlinx.serialization + Jackson 共存 | @Serializable 类用 kotlinx,其余用 Jackson |
| Native Image 反射限制 | GraalVM 配置文件 + Spring AOT |
十、Kotlin 2.x 新特性速览
Kotlin 2.0(2024.5)--- K2 编译器
- K2 编译器正式稳定,编译速度提升高达 94%
- 改进的智能类型推断和类型收窄
- 统一多平台编译管线
// K2 改进的智能类型转换fun process(value: Any) { if (value is String && value.length > 0) { // K2 在更多场景下自动推断 value 为 String println(value.uppercase()) }}Kotlin 2.1(2024.12)--- 实用增强
// when 表达式守卫条件sealed class Animal { data class Cat(val name: String, val moulesPerDay: Int) : Animal() data class Dog(val name: String) : Animal()}
fun feedAnimal(animal: Animal) { when (animal) { is Animal.Cat if animal.moulesPerDay > 2 -> println("Fat cat!") is Animal.Cat -> println("Normal cat") is Animal.Dog -> println("Good dog") }}
// 非局部 break/continuefun processItems(items: List<String>) { for (item in items) { item.let { if (it == "skip") continue // Kotlin 2.1 支持 println(it) } }}Kotlin 2.2(2025.6)--- Spring Boot 4 基线
- 统一编译器警告管理
- 接口函数默认方法生成变更(JVM)
- K2 模式在 IntelliJ IDEA 2025.3+ 默认启用
来源:What’s new in Kotlin 2.0.0、What’s new in Kotlin 2.1.0、Kotlin 2.2.0 Released
参考资源
- Spring Framework Kotlin Reference (official)
- Spring Boot Kotlin Support (official)
- Kotlin Server-Side Overview (official)
- Next Level Kotlin Support in Spring Boot 4 (Spring Blog)
- Kotlin on the Backend from KotlinConf 2025 (JetBrains)
- Strategic Partnership: JetBrains + Spring (JetBrains)
- The Ultimate Guide to Adopting Kotlin (JetBrains)
- Coroutines :: Spring Framework (official)
- JPA and Kotlin Pitfalls (JetBrains)