Springのバリデーション例外を完全攻略!BindingResultとMethodArgumentNotValidExceptionの使い方
生徒
「Spring Bootでバリデーションチェックを使ってるんですが、エラーになったときの扱いがよくわかりません…」
先生
「それは@ValidとBindingResult、それからMethodArgumentNotValidExceptionの使い分けを知ると解決できますよ。」
生徒
「それってどう違うんですか?どっちを使えばいいんですか?」
先生
「それでは、バリデーションエラーの基本から、例外処理の仕組みまで順番に見ていきましょう!」
1. Springのバリデーションとは?
Spring Frameworkでは、入力チェック(バリデーション)を簡単に行うために@Validや@Validatedを使います。これにより、フォームやAPIで受け取ったデータが正しいかどうかを事前に検証することが可能になります。
例えば、名前が空欄でないか、メールアドレスの形式が正しいか、数値が範囲内かなどをアノテーションで定義できます。
2. BindingResultを使ったバリデーション結果の取得方法
BindingResultは、@Validの直後に定義することで、バリデーション結果を自分で受け取り処理できます。つまり、例外として処理される前に、コントローラー内で柔軟にエラーをハンドリングできます。
@PostMapping("/users")
public String createUser(@Valid @RequestBody UserForm form, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return "バリデーションエラー:" + bindingResult.getAllErrors().get(0).getDefaultMessage();
}
return "登録成功";
}
この方法では、例外を発生させずにバリデーションの判定が可能なので、業務ロジックに応じた細かなレスポンスが作れます。
3. BindingResultを使うときの注意点
BindingResultは、必ず@Validの引数の直後に記述する必要があります。順番を間違えると、バリデーション結果を取得できず、MethodArgumentNotValidExceptionがスローされてしまいます。
// OKな順番
public String method(@Valid UserForm form, BindingResult result)
// NGな順番
public String method(BindingResult result, @Valid UserForm form)
Spring FrameworkやThymeleafを使った Webアプリ開発の全体像をやさしく理解したい人には、 この入門書が定番です。
Spring Framework超入門をAmazonで見る※ Amazon広告リンク
4. MethodArgumentNotValidExceptionの概要と発生タイミング
MethodArgumentNotValidExceptionは、@Validが指定された引数に対してバリデーションエラーが発生したときに、Springが自動的にスローする例外です。これは、BindingResultが定義されていない場合に発生します。
@PostMapping("/users")
public String createUser(@Valid @RequestBody UserForm form) {
// BindingResultがないため、エラー時は例外が発生する
return "OK";
}
REST APIなどでResponseEntityを返すような構成では、この例外を@ExceptionHandlerで受け取って整形レスポンスを返すパターンがよく使われます。
5. MethodArgumentNotValidExceptionをハンドリングする方法
Springでは、@ControllerAdviceを使って、MethodArgumentNotValidExceptionを一括で処理できます。エラー内容をFieldErrorなどから取り出し、APIクライアントに返すことができます。
@ControllerAdvice
public class ValidationExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, String>> handleValidation(MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(error -> {
errors.put(error.getField(), error.getDefaultMessage());
});
return ResponseEntity.badRequest().body(errors);
}
}
これにより、APIのレスポンスをクライアントが扱いやすい形式で返すことができます。
6. バリデーションクラスとエラー定義の実例
ここでは、バリデーション対象となるDTOクラス(データ受け取り用クラス)の記述例を紹介します。
public class UserForm {
@NotBlank(message = "名前は必須です")
private String name;
@Email(message = "メールアドレスの形式が不正です")
private String email;
// ゲッター・セッターを省略
}
このようにアノテーションを指定することで、簡単に入力チェックを行うことができます。
7. フロントエンドと連携するためのレスポンス形式
APIのバリデーションエラーをフロントエンドで扱いやすくするには、以下のようなJSON構造で返すのが一般的です。
{
"name": "名前は必須です",
"email": "メールアドレスの形式が不正です"
}
この形式にすることで、フィールド単位でのエラー表示や、バリデーションの再実行がしやすくなります。
8. BindingResultとMethodArgumentNotValidExceptionの使い分け
BindingResultは、HTMLフォームなどの画面で細かく制御したいときに向いています。一方、MethodArgumentNotValidExceptionは、REST APIのようにエラーを共通処理で返したい場合に向いています。
- 画面処理 → BindingResultでエラーを画面に返す
- API処理 → ExceptionHandlerで一括処理
これらを場面に応じて使い分けることで、より堅牢な入力バリデーションが実現できます。