こんにちは、さるまりんです。
今回はKotlinで作ったAPIのバリデーションについてです。
Kotlin × Spring Boot の API を作ります。
まずは JSON を受け取る → チェックする → 正常 or エラーを返す という API の基本の流れを作りたいところです。
フレームワークが全部やってくれるとはいえ、
「どう組み合わせたら ちゃんとした形 になるのか?」
は最初に向き合うところです。
そこで今回は Docker 上で最小構成の Spring Boot API を作り、
入力 → バリデーション → 例外 → 整形された JSON
の流れを全部動かしてみました。
1. Docker で Kotlin / Spring Boot を動かす環境を作る
まず、この2ファイルを同じフォルダに置きます。
project/
├── docker-compose.yml
└── Dockerfile
docker-compose.yml
services:
app:
build: .
ports:
- "8080:8080"
volumes:
- ./:/workspace
tty: true
2. Spring Boot の Dockerfile を作成
同じディレクトリに Dockerfile を作ります。
Dockerfile
FROM gradle:8.7-jdk17 AS builder
WORKDIR /workspace
COPY . .
RUN gradle bootJar --no-daemon
FROM eclipse-temurin:17-jre
WORKDIR /app
COPY --from=builder /workspace/build/libs/*.jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]
ポイント:
- builder ステージで
bootJarを作る - 本番ステージは JRE だけなので軽い
3. Spring Boot プロジェクトのファイルを配置する
最小構成はこれ👇です
src/main/kotlin/com/example/demo/
│── DemoApplication.kt
│── api/
│ ├── CreateComposerRequest.kt
│ ├── ComposerController.kt
│ └── GlobalExceptionHandler.kt
build.gradle.kts
settings.gradle.kts
3-1. build.gradle.kts
plugins {
id("org.springframework.boot") version "3.3.2"
id("io.spring.dependency-management") version "1.1.5"
kotlin("jvm") version "1.9.23"
kotlin("plugin.spring") version "1.9.23"
}
group = "com.example"
version = "0.0.1-SNAPSHOT"
java {
sourceCompatibility = JavaVersion.VERSION_17
}
repositories {
mavenCentral()
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-validation")
testImplementation("org.springframework.boot:spring-boot-starter-test")
}
3-2. settings.gradle.kts
rootProject.name = "kotlin-validation-api"
3-3. DemoApplication.kt
package com.example.demo
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
@SpringBootApplication
class DemoApplication
fun main(args: Array<String>) {
runApplication<DemoApplication>(*args)
}
4. JSON リクエストモデル + バリデーション
CreateComposerRequest.kt
package com.example.demo.api
import jakarta.validation.constraints.NotBlank
import jakarta.validation.constraints.Size
data class CreateComposerRequest(
@field:NotBlank(message = "name は必須です")
@field:Size(max = 100, message = "name は100文字以内です")
val name: String?,
@field:Size(max = 50, message = "nationality は50文字以内です")
val nationality: String?
)
- Kotlin の場合
@field:が必要 (Javaでは@NotBlankとか@NotNullでした) - null 許容で “来なかったとき” もエラーにできる
5. API コントローラ
ComposerController.kt
package com.example.demo.api
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
import jakarta.validation.Valid
@RestController
@RequestMapping("/api/composers")
class ComposerController {
@PostMapping
fun create(
@Valid @RequestBody body: CreateComposerRequest
): ResponseEntity<Any> {
val response = mapOf(
"id" to 1,
"name" to body.name,
"nationality" to body.nationality
)
return ResponseEntity.ok(response)
}
}
@Valid が入ることで入力エラー時に例外が飛ぶようになります。
6. 例外ハンドリングでエラー JSON を整形
GlobalExceptionHandler.kt
package com.example.demo.api
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.ControllerAdvice
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.MethodArgumentNotValidException
@ControllerAdvice
class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException::class)
fun handleValidationError(ex: MethodArgumentNotValidException): ResponseEntity<Map<String, Any>> {
val errors = ex.bindingResult.fieldErrors.map {
mapOf(
"field" to it.field,
"message" to (it.defaultMessage ?: "invalid value")
)
}
val body = mapOf(
"error" to "validation_error",
"details" to errors
)
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(body)
}
}
7. Docker でビルド & 起動
さあ動かしてみます。僕はやりながら“つまずいた”ので手順も載せておきます。
1. ビルド
docker compose build
成功すると、こんなログが流れます(抜粋):
[+] Building 20.0s (11/11)
> [builder 4/4] RUN gradle bootJar --no-daemon:
...
2. 起動
docker compose up
起動ログ:
Tomcat initialized with port 8080 (http)
Tomcat started on port 8080 (http) with context path '/'
Started DemoApplicationKt in 2.139 seconds
ここまで来たら、もう API が動いています。
8. 動作確認(curl)
動いているAPIをcurlで叩いてみます。
OKパターン
curl -X POST http://localhost:8080/api/composers \
-H "Content-Type: application/json" \
-d '{"name": "Beethoven", "nationality": "German"}'
レスポンス:
{"id":1,"name":"Beethoven","nationality":"German"}
NGパターン(バリデーションエラー)
curl -X POST http://localhost:8080/api/composers \
-H "Content-Type: application/json" \
-d '{"name": "", "nationality": "German"}'
レスポンス:
{
"error": "validation_error",
"details": [
{
"field": "name",
"message": "name は必須です"
}
]
}
入力 → バリデーション → 例外 → 整形された JSON
この流れがバッチリ確認できました。
9. おわりに
今回は、
- Dockerで最小の Kotlin × Spring Boot API を構築
- JSON を受け取る仕組み
- バリデーション(Jakarta)
- 例外ハンドリング(@ControllerAdvice)
- 一貫したエラーレスポンス設計
までを まとめて動く形 にしました。
API が「大丈夫な時も、ダメな時も、ちゃんと返す」ことは
とても大事なポイントです。
次は ビジネスロジック上の NG(例:登録済みなど)をどう返すか?
という“アプリケーションエラー設計”もやっていきたいと思います。
読んでくださってありがとうございました。
それでは、また!