Java歴21年のエンジニアが同じAPIをJava・Go・Rust・Kotlinで実装して徹底比較した
はじめに
筆者はJava歴21年のバックエンドエンジニアです。現在は日本のIT企業で働きながら、自社プロダクト(短動画プラットフォーム)のバックエンドを開発しています。
元々Spring Bootで構築していた本番環境をGoに移行し、さらにRustで書き直しました。その過程で「ちゃんと数字で比較したい」と思い、同じAPIをKotlin(Ktor)でも実装し、4言語の実測データを取りました。
本記事では、同一仕様のAPIを4言語で実装し、スループット・レイテンシ・メモリ・起動時間・ビルド時間・コード量を比較した結果を共有します。
テスト環境
| 項目 | 詳細 |
|---|---|
| マシン | macOS Apple Silicon |
| コンテナ | Docker (OrbStack) |
| DB | PostgreSQL 18(Go/Rust/Kotlin)/ MySQL 8.4(Java) |
| キャッシュ | Redis 8 |
| ストレージ | Garage S3 v2.2.0 |
技術スタック
| Java | Go | Rust | Kotlin | |
|---|---|---|---|---|
| フレームワーク | Spring Boot 3.2.4 | Echo v4 + Huma v2 | Axum 0.8 | Ktor 3.4.0 + Netty |
| DB | MyBatis-Plus + MySQL | pgx/v5 + sqlc | SQLx + PostgreSQL | Exposed 1.1.1 + PostgreSQL |
| ランタイム | JVM (Zulu 17) | Go 1.26 | Tokio (Rust 1.94) | JVM 21 (Virtual Threads + ZGC) |
| キャッシュ | Jedis + Redisson | go-redis/v9 | deadpool-redis | Lettuce 7.5 |
1. スループット (QPS)
wrk(4スレッド、200並行、10秒)
| エンドポイント | Java | Go | Rust | Kotlin |
|---|---|---|---|---|
| health(DB無し) | 88,149 | 100,079 | 210,400 | 99,431 |
| 対Java倍率 | 1x | 1.14x | 2.39x | 1.13x |
所感: RustがGoの約2倍。そして意外だったのは、Kotlin(Ktor + Netty + Virtual Threads)がGoとほぼ同等のスループットを出したことです。JVM 21のJITによるホットパス最適化が効いています。
2. レイテンシ分布
wrk --latency(4スレッド、200並行、10秒)
| パーセンタイル | Go | Rust | Kotlin |
|---|---|---|---|
| p50 | 1.79 ms | 0.70 ms | 1.60 ms |
| p75 | 2.80 ms | 0.85 ms | 2.31 ms |
| p90 | 3.91 ms | 1.01 ms | 4.24 ms |
| p99 | 6.13 ms | 1.48 ms | 15.36 ms |
所感: ここが一番興味深いデータです。
- Kotlinのp50はGoより良い(1.60 vs 1.79ms)。JITのホットパス最適化が確実に効いている
- しかしKotlinのp99は15.36msで、Goの2.5倍、Rustの10倍。ZGCの停止時間とJITコンパイルのジッターが原因
- Rustは全パーセンタイルで圧倒的。GCが無いことの恩恵がテールレイテンシに如実に現れている
サービスのSLAを考える上で、p50ではなくp99が重要です。この点でKotlin/JVMは構造的な限界があります。
3. メモリ使用量 (RSS)
| 状態 | Java | Go | Rust | Kotlin |
|---|---|---|---|---|
| アイドル | 354 MB | 25 MB | 19 MB | 254 MB |
| 200並行後 | 370 MB | 46 MB | 21 MB | 638 MB |
| 500並行ピーク | 372 MB | 60 MB | 33 MB | 650 MB |
所感: Rustが500並行接続で33MB。Kotlinの650MBと比較すると約20倍の差。
Kotlinのメモリが高いのはZGCの特性によるものです。ZGCはレイテンシを下げるために、メモリを積極的に確保して回収を遅らせる戦略を取ります。結果として、メモリを犠牲にしているのにp99は改善できていない、という皮肉な状況です。
K8sクラスターでは、1ノードに載せられるPod数がメモリで制約されるため、19MB vs 650MBの差はインフラコストに直結します。
4. コールドスタート
プリコンパイル済みバイナリ/JAR、プロセス起動から初回HTTPレスポンスまで、3回の中央値
| Java | Go | Rust | Kotlin | |
|---|---|---|---|---|
| 起動時間 | 2,714 ms | 69 ms | 153 ms | 914 ms |
| 対Go倍率 | 39x | 1x | 2.2x | 13x |
所感: Goが圧勝。Rustの153msにはsqlxのマイグレーションチェックが含まれており、最適化の余地はあります。
KotlinはJavaの3倍速い(914 vs 2,714ms)。Ktorはリフレクションスキャンやコンポーネントインジェクションが無いため、Spring Bootと比べて起動が大幅に速くなります。それでもGoの13倍遅い。
K8sのオートスケーリングやサーバーレス環境では、この差は実用上の問題になります。
5. ビルド時間
完全クリーンビルド(キャッシュ全削除、依存ダウンロード済み)
| Java | Go | Rust | Kotlin | |
|---|---|---|---|---|
| ビルド時間 | 5.6s | 9.4s | 98.3s | 14.1s |
所感: Rustの98秒は最大の弱点です。CIでは毎回フルビルドになるため、BuildKitのキャッシュマウントやcargo-chefで対策が必要です。
Kotlinの14.1sはGradle起動 + K2コンパイラ + Shadow JAR打包を含みます。Gradle Daemon起動済みの増分コンパイルなら3〜4秒まで縮まり、実開発では快適です。
ちなみに、今回の実装で最も時間がかかったのはKotlinのプロジェクトセットアップでした。Gradleプラグインの互換性問題に何時間も費やし、GoとRustの実装を合わせた時間より長くかかりました。2026年にもなって、JVMプロジェクトを新規作成するのにこれほど苦労するのは正直がっかりです。
6. コード量
tokei統計(空行・コメント除外)
| Java | Go | Rust | Kotlin | |
|---|---|---|---|---|
| コード行数 | 7,001 | 7,087 | 4,179 | 8,607 |
| 手書きのみ | 7,001 | 4,929* | 4,179 | 8,607** |
| ファイル数 | 156 | 86 | 37 | 52 |
* Goはsqlc生成コード(2,158行)を除外
** Kotlinはテストコード約2,000行を含む
所感: Rustが最少。型システムとenum/matchの表現力によって、エラーハンドリングやパターンマッチが簡潔に書けるのが大きい。Goのif err != nilのような定型句も不要です。
7. 成果物サイズ
| Java | Go | Rust | Kotlin | |
|---|---|---|---|---|
| バイナリ | ~50 MB (fat jar) | 44 MB | 19 MB | 44 MB (fat jar) |
| Dockerイメージ | 412 MB | 150 MB | 130 MB | 253 MB |
8. 総合評価
| 観点 | Java | Go | Rust | Kotlin |
|---|---|---|---|---|
| スループット | C | B | A | B |
| テールレイテンシ (p99) | — | B | A | C |
| メモリ効率 | D | B | A | D |
| コールドスタート | D | A | B | C |
| ビルド速度 | A | B | D | B |
| コード簡潔性 | B | B | A | B |
| 開発エコシステム | A | A | B | A |
| 型安全性 | B | C | A | B |
9. どの言語を選ぶべきか
Javaを選ぶ場合
- チームにJava/Spring経験者が多い
- エンタープライズエコシステム(Spring Security、Spring Cloud)が必要
- メモリや起動時間が問題にならない(従来型デプロイ)
Goを選ぶ場合
- 最速のデリバリー(ビルド10秒 + 起動69ms + 1週間で実戦投入)
- K8s/サーバーレス環境(コールドスタート重視)
- チームの急速拡大が必要(学習コスト最小)
Rustを選ぶ場合
- 性能とメモリがコア競争力(インフラコスト重視)
- 長期メンテナンスの基幹サービス(コンパイル時安全性 → 本番障害の削減)
- 3〜6ヶ月の学習期間を投資できる
Kotlinを選ぶ場合
- Java/Android経験があり、より現代的なJVM言語を使いたい
- JVMエコシステムは必要だがSpringは避けたい
- スループットはGo並みでOK、JVMのメモリオーバーヘッドは許容できる
- 開発体験重視(Null安全、コルーチン、DSL、増分コンパイル3〜4秒)
10. 筆者の選択
本番環境はRust(Axum)に移行しました。決め手はインフラコストの削減です。メモリ使用量がGoの半分ということは、同じトラフィックをより小さいインスタンスで処理でき、EC2コストを約半分に削減できる見込みです。
Rustのビルド時間は確かに痛いですが、CIではBuildKitキャッシュマウントで対策し、日常の開発では増分コンパイルで概ね許容範囲です。
ただし、全てのチームにRustを勧めるわけではありません。一人または少人数で長期メンテナンスする基幹サービスだからこそ、Rustの「コンパイル時に安全を保証する」哲学が活きます。チームの規模が大きく、高速なイテレーションが求められる場合は、Goの方がトータルで優れた選択だと考えます。
テスト方法論
ツール
- wrk — スループット + レイテンシ分布(4スレッド、200/500並行、10秒)
- hyperfine — ビルド時間計測(3回平均)
- tokei — コード行数
- ps RSS — メモリサンプリング
- python3 time.time() — コールドスタート計測
公平性の確保
- Go/Rust/Kotlinは同一のPostgreSQL 18インスタンスを共有
- 4言語とも同一のRedis 8インスタンスを共有
- コネクションプールは全てlazyモード
- ビルド時間は全キャッシュクリア + 依存ダウンロード済みの条件
- healthエンドポイントはレート制限ミドルウェアの外に配置
制限事項
- 単一マシンDocker環境(本番相当ではない)
- JavaのみMySQL使用(他はPostgreSQL)
- JavaのAPIパスが異なり、完全な等価テスト未実施
- GraalVM native-image未テスト(Java/Kotlinの起動・メモリを大幅改善する可能性あり)
- Kotlin JITが完全にウォームアップしていない可能性
- healthエンドポイントのみ(DB操作を含むベンチマーク未実施)
おわりに
21年間Javaを書いてきた筆者にとって、この4言語比較は「自分の技術的判断の根拠を固める」作業でした。感覚ではなく数字で語れるようになったことで、言語選択の議論が遥かに生産的になります。
質問やフィードバックがあれば、ぜひコメントでお聞かせください。
Discussion
C#がない
JavaとKotlinでJVMが揃ってない
の2点が気になりました