Spring Boot 4 の新機能と注意点 - Kotlin 編
Spring Boot 4 と Spring Framework 7 が2025年11月にリリースされました。
すでにいろいろな場所で、新しい機能や変更点は紹介されていると思うのですが、多くが Java で書かれたアプリケーション向けの内容です。
ここでは Kotlin を使っている Spring アプリケーション向けに、特に Kotlin に関連した変更点や注意点をご紹介しようと思います。
KotlinFest 2025 で、JetBrains と Spring の戦略的パートナーシップの発表があった後の最初のリリースで、Spring が抱えるエコシステムの Kotlin 対応が強化されていることを感じ取れるリリースとなっていると思います。
Kotlin 2.2
Spring Boot 4 が対応する Kotlin のバージョンのベースラインが 2.2 になりました。
Spring Boot 3 では 1.9 系だったので、これまでは最新の Kotlin の恩恵を受ける事ができませんでしたが、ついに Spring アプリケーションも Kotlin 2 に対応しました。
Kotlin 2 は、K2 コンパイラの採用や、言語機能の拡張など、Kotlin にとって大きなマイルストーンです。
一部の Kotlin ライブラリは、すでに依存が Kotlin >= 2.1 以上になっていて、最新版を使えなくて困る Spring アプリケーションもあったのではないのでしょうか。
1.9 から比べて、2.2 への機能面での大きな変更には、以下のようなものがあります
JSpecify による Null-Safety
JSpecify とは、従来から使われていて近年進化が止まっていた JSR-305 に変わる Null-Safety のためのアノテーションです。
Spring は最近のバージョンで JSpecify による Null-Safety な API の導入を進めており、Spring 4 リリースのタイミングですでに多くのプロジェクトですでに対応が完了しています。
Kotlin はすでに JSpecify のアノテーションに対応しており、JSpecify に従った Java のコードを Kotlin から操作する際にも Null-Safety の恩恵を受ける事ができます。
これまでは Java で書かれた Spring のクラスを触る際に、Kotlin おいては言語レベルで Null-Safety が強制されているので、Nullable なのか Not-Null なのかが判断ができず T!(例えば String!) のような platform-type に変換されてしまい、Null-Safety の恩恵を受ける事ができない場面が多くありました。
JSpecify 対応が Spring でも浸透した事で、Kotlin において Spring のクラスも Kotlin で書かれたクラスと同様に Null-Safety を保ったまま操作する事ができるようになっています。良いですね!
Jackson 3 サポート
Spring Boot 4 では、これまでの Jackson 2 から Jackson 3 が標準で使われるようになりました。
Jackson 3 では、多くのデフォルト設定が見直されており、Kotlin Module においても例外ではありません。
以下に、特に Kotlin において注意するべき設定の変更を列挙します。
DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES
これまでの Jackson 2 のデフォルト設定は Kotlin でプリミティブ型を Non-Null で指定していても、null をデシリアライズした際に null にならないと言う問題がありました。
data class Some(val value: Int)
val mapper = jacksonObjectMapper()
mapper.readValue<Some>("""{"value":null}""") // => Some(value=0)
特に、Null-Safety が厳密な Kotlin では直感に反する挙動となっていました。この問題が、DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES デフォルト値が true に変更によって改善されています。
KotlinFeature.StrictNullChecks
Jackson 2 では、Kotlin で明示的に Non-Null の型を List など Collection 型の型引数として指定していても、null が入ってきてしまうと言う問題がありました。
val mapper = jacksonObjectMapper()
val json = """["A", null]"""
val list = mapper.readValue(json, jacksonTypeRef<List<String>>()) // エラーにならない
println(list) //=> [A, null]
KotlinFeature.StrictNullChecks は以前から提供されていたのですが、パフォーマンスの問題があり、デフォルトで有効にはされていませんでした。Jackson 3 では、パフォーマンス問題が解決されたと言うことで、デフォルトで有効になり改善されました。
KotlinFeature.SingletonSupport
Kotlin には object というシングルトンオブジェクトを生成するための専用の宣言が用意されていますが、Jackson 2 のデフォルト値では正しく対応されていませんでした。
object Singleton
val mapper = jacksonObjectMapper()
val json = mapper.writeValueAsString(Singleton)
val deserialized = mapper.readValue<Singleton>(json)
deserialized == Singleton // => false!!
このため、object を含むクラスをデシリアライズしたとしても、正しく比較がされず直感に反する結果となっていました。この問題が、KotlinFeature.SingletonSupport がデフォルトで有効になることで改善されています。
kotlinx-serialization
Kotlin は kotlinx-serialization という専用のシリアライズライブラリを持っていて、リフレクションを使わない実装になっていたり、sealed class など Kotlin 特有の型に対応していたりと、Jackson に比べて多くの利点があります。
これまでも、kotlinx-serialization への対応はされていて Spring アプリケーション内でもサポートされていましたが、Spring Boot 4 から kotlinx-serialization のための Spring Boot Starter spring-boot-starter-kotlin-serialization が提供されることとなりました。
このスターターを読み込むことで、JSON への変換を簡単に kotlinx-serialization に対応させる事ができます。コントローラーのリクエストやレスポンスにも問題なく使えます。
@Serializable
data class HelloResponse(val name: String)
@Controller
class HelloController {
@GetMapping("/hello")
fun hello(): HelloResponse = HelloResponse("hello")
}
一方で、Spring が依存する多くのライブラリや、プロジェクトは、Jackson である事が前提で書かれていたりもするので、一部のみを kotlinx-serialization に置き換えることで大きな恩恵があるかは、慎重に検討する必要がありそうです。
JUnit 6
Spring Boot 4 では、JUnit 6 が標準のテストフレームワークになっています。
JUnit 6 からは、Kotlin 対応も強化されていて、テストメソッドを suspend fun で対応する事が可能になっていて、Coroutines 対応が大幅に強化されました!
これまでの、runTest {} や runBlocking {} を使った workaround を止める事ができてテストコードがスッキリしそうです。とはいえ、runTest には仮想時間の対応もあるので、そちらが必要であれば引き続き利用する事もできます。
@Test
suspend fun `should be delayed 1 second`() {
delay(1.seconds)
}
Bean Registration DSL
これまでも Spring の Kotlin サポートの一環として、Bean Definition DSL がサポートされていましたが、完全に Spring の仕組みに取り入れられているわけではなく、若干使いにくいものでした。
Spring Boot 4 から Programmatic Bean Registration としてさらに便利になり、Java と Kotlin へ提供されることとなりました。
BeanRegistrarDsl という DSL が提供され、このクラスを実装した bean を定義することで、これまでのように ApplicationContext に手動で差し込む必要がなくなります。
この Bean Registration DSL を利用する事で、他の Pure Kotlin な DI ライブラリに近い体験を Spring でも実装する事ができるようになり、さらに @ConditionalOnBean などの条件付きの bean の登録などを DSL で確実に記述する事ができるようになります。
class MyBeanRegistrar : BeanRegistrarDsl({
registerBean<Foo>()
profile("baz") {
registerBean { Baz("Hello World!") }
}
registerBean<MyRepository>()
})
まとめ
Spring Boot 4 で、さらに Kotlin 対応が便利になりました。
Spring Boot は、Java のフレームワークと思われがちですが、近年は Kotlin への対応もかなり進んでいて、Kotlin の Web フレームワークとして採用しても問題ない状況になっています。前述のように JetBrains との連携もあり、将来性も期待できそうです。
Kotlin をバックエンドに採用する際のフレームワークとして、Java のフレームワークである Spring は避けられがちですが、そんなことはありません。Kotlin で Spring を使ってみてはいかがでしょうか。
最後に、KotlinConf 2025 であった Spring Boot 4 に関連するセッションを紹介して終わりにします。
明日の、Money Forward Engineers Advent Calendar 2025 は Alex Wilson さんが担当します!お楽しみに〜🎅🎄🤶
Discussion