問題点
Spring Bootでのデータバリデーションの設定は、本来簡単であるはずです。@NotNullのようなアノテーションをいくつか追加し、無効なリクエストを送信すれば、分かりやすいエラーメッセージが返ってくることを期待するでしょう。しかし、実際には壁にぶつかることがよくあります。APIが、ログに膨大なスタックトレースを出力する乱雑な 400 Bad Request を返したり、さらに悪いことに、バリデーションルールが完全に無視されたりすることがあります。
これは、バリデーションに失敗したときにSpringが MethodArgumentNotValidException をスローするためです。デフォルトでは、Springはこれらのエラーをフロントエンドにどのように表示すべきかを認識していません。その結果、ReactやAngularのアプリで効果的に解析することがほぼ不可能なデフォルトのレスポンスが生成されてしまいます。
org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public org.springframework.http.ResponseEntity<?> ...
要約:3ステップのチェックリスト
- 依存関係の確認: Spring Boot 2.3以降では、
spring-boot-starter-validationが必要です。webスターターには含まれなくなりました。 - チェックの実行: コントローラーの
@RequestBodyの前に@Validまたは@Validatedを追加する必要があります。 - 出力の整形:
@RestControllerAdviceを使用して、例外をクリーンなJSONマップに変換します。
ステップ1:依存関係の確認
バリデーションアノテーションが無視されている場合、通常はこれが原因です。2020年以降、Spring BootはデフォルトのJARサイズを約1MB削減するために、バリデーションロジックを分離しました。
Mavenユーザーの場合は、pom.xml に以下を追加してください:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
ステップ2:アノテーションの正しい使用方法
バリデーションには、データに対する制約の定義と、それを適用するようコントローラーに指示するという2つのステップが必要です。
DTO(Data Transfer Object)
パッケージ名に注意してください。Spring Boot 3.0以降を使用している場合は jakarta.validation を使用します。それより古いバージョンの場合は javax.validation を使用してください。
public class UserRequest {
@NotBlank(message = "Username is required")
private String username;
@Email(message = "Please provide a valid email address")
private String email;
// ゲッターとセッター
}
コントローラー
@Valid アノテーションがないと、Springは作成したルールを無視します。受信したJSONを、フィールドのチェックを行わずに単なるオブジェクトとして処理します。
@PostMapping("/users")
public ResponseEntity<String> createUser(@Valid @RequestBody UserRequest request) {
return ResponseEntity.ok("User is valid!");
}
ステップ3:エラーのグローバルハンドリング
バリデーションに失敗すると、Springはリクエストを停止して例外をスローします。スタックトレースがユーザーに漏洩するのを防ぐために、グローバルハンドラーを作成します。これにより例外をキャッチし、エラーの単純なキー・値ペアを返します。
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, String>> handleValidationExceptions(
MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(error ->
errors.put(error.getField(), error.getDefaultMessage())
);
return ResponseEntity.badRequest().body(errors);
}
}
技術的な根本原因
Spring BootはBean Validation API (JSR 380)に依存しています。内部では Hibernate Validator がエンジンとして動作します。リクエストがエンドポイントに到達すると、RequestResponseBodyMethodProcessor が引数を解決します。@Valid がある場合、バリデーターを呼び出します。エラーが存在する場合、それらは BindingResult オブジェクトに保存され、その後 MethodArgumentNotValidException にラップされます。
修正のテスト
Postman を起動するか、curl を使用してテストします。ユーザー名を空にしてリクエストを送信します。
curl -X POST http://localhost:8080/users \
-H "Content-Type: application/json" \
-d '{"username": "", "email": "not-an-email"}'
結果:
500 Internal Server Error の代わりに、クリーンな 400 レスポンスが返されるようになります:
{
"username": "Username is required",
"email": "Please provide a valid email address"
}
避けるべき一般的な落とし穴
- ネストされたオブジェクト: DTOに別のオブジェクト(
Addressなど)が含まれている場合、そのフィールド自体に@Validを付ける必要があります。そうしないと、内部のオブジェクトはチェックされません。 - 誤ったインポート:
javaxとjakartaのインポートを混ぜることは、Spring Boot 3プロジェクトでバリデーションが機能しない一般的な原因です。 - バリデーショングループ: 「作成」と「更新」で異なるルールが必要な場合は、
@Validの代わりに@Validatedを使用する必要があります。

