BindingResultの使い方と@Validの直後に置く理由を解説する記事のアイキャッチ。若手エンジニアがJava道場風の学習空間で入力チェック結果を学ぶイラスト。

Spring

BindingResultの使い方と注意点|@Validの直後に置く理由

AIによる要約

BindingResultは、@Validで検出した入力エラーをControllerで受け取るための引数です。重要なのは、@Validを付けたFormの直後に置くことです。順番を間違えると、期待通りにエラー処理できません。

新人SE
新人SE
BindingResultは何となくControllerに書いています。順番ってそんなに大事なんですか?
ポンコツSE
ポンコツSE
かなり大事です。@Valid対象の直後にBindingResultを置かないと、Springがどの入力エラーを渡せばよいか判断できず、画面に戻す前に例外になることがあります。

Spring MVCで入力チェックを実装すると、@Validと一緒にBindingResultが出てきます。

新人のうちは、サンプルコードを写して動かすことはできても、なぜその順番で書くのかまでは分かりにくいです。この記事では、現場でよくレビューされる注意点に絞って整理します。

この記事のポイント

  • BindingResultは入力チェック結果を受け取るために使う
  • @Validを付けたFormの直後に置く
  • エラーがあればServiceへ進まず入力画面に戻す
  • REST APIでは例外ハンドリングに寄せる現場もある

BindingResultは入力チェック結果を受け取る

BindingResultは、FormクラスやRequest DTOに対する入力チェック結果をControllerで受け取るためのオブジェクトです。

@PostMapping("/users")
public String register(@Valid UserRegisterForm form,
                       BindingResult bindingResult) {
    if (bindingResult.hasErrors()) {
        return "users/new";
    }

    userService.register(form);
    return "redirect:/users";
}

bindingResult.hasErrors()trueなら、入力値に問題があります。この時点でServiceへ進まず、入力画面へ戻すのが基本です。

@Validの直後に置く理由

最重要ポイントは、BindingResult@Valid対象の直後に置くことです。Springは、直前の検証対象に対するエラー結果としてBindingResultを扱います。

// OK
public String register(@Valid UserRegisterForm form,
                       BindingResult bindingResult,
                       Model model) {
    // ...
}

この順番なら、UserRegisterFormの入力チェック結果をbindingResultで受け取れます。

順番を間違えるとどうなるか

よくあるミスは、@ValidBindingResultの間に別の引数を挟むことです。

// NG
public String register(@Valid UserRegisterForm form,
                       Model model,
                       BindingResult bindingResult) {
    // ...
}

この形は、入力エラーを画面で受け止める前に例外になったり、期待通りにエラーを扱えなかったりします。新人のレビューでかなり指摘されやすいポイントです。

エラーがあればServiceへ進まない

形式チェックでエラーがある状態でServiceを呼ぶと、Service側で想定外の値を扱うことになります。Controllerでは、エラーの有無を見て処理を止めます。

if (bindingResult.hasErrors()) {
    return "users/new";
}

userService.register(form);

ここでやっているのは、業務処理ではありません。入力画面に戻すための制御です。Serviceの前に止めることで、Serviceは正常な形式の入力を前提に処理しやすくなります。

Thymeleafでエラーを表示する

画面側では、Formのフィールドに紐づくエラーを表示します。

<form th:action="@{/users}" th:object="${userRegisterForm}" method="post">
  <input type="text" th:field="*{name}">
  <p th:if="${#fields.hasErrors('name')}" th:errors="*{name}"></p>

  <input type="email" th:field="*{email}">
  <p th:if="${#fields.hasErrors('email')}" th:errors="*{email}"></p>
</form>

ControllerでBindingResultを受け、同じ画面に戻すと、Thymeleaf側でエラー内容を表示できます。

REST APIでは扱い方が変わることもある

画面フォームではBindingResultを使って入力画面に戻す実装が分かりやすいです。一方、REST APIでは、入力エラーを例外ハンドラでまとめてJSONレスポンスにする現場もあります。

@PostMapping("/api/users")
public ResponseEntity<Void> create(@RequestBody @Valid UserCreateRequest request) {
    userService.create(request);
    return ResponseEntity.ok().build();
}

APIでは画面へ戻すのではなく、400エラーとエラー内容を返す設計になります。プロジェクトのエラー処理方針に合わせましょう。

レビュー前のセルフチェック

確認リスト

  • BindingResult@Valid対象の直後にあるか
  • エラー時にServiceを呼ばず、入力画面へ戻しているか
  • 画面側でForm名とフィールド名が一致しているか
  • APIと画面フォームでエラー処理を混同していないか

この記事とあわせて読みたい

まとめ

BindingResultは、@Validの結果をControllerで受け取るためのものです。最も大事なのは、@Validを付けた引数の直後に置くことです。

新人のうちは、順番、エラー時にServiceへ進まないこと、画面に戻したときにエラーが表示されること。この3点を押さえれば十分実務で使えます。

-Spring
-, , , , ,