1. test 1
  2. test 2
  3. test 3
  4. test 4
  5. test 5
  6. test 6
コース一覧
Spring Boot入門
検証エラーをまとめて返す

Spring Boot入門

Java で Web API サーバーを作るための Spring Boot の入門コースです。DI コンテナ、REST コントローラ、リクエスト検証、レイヤ分割、Spring Data JPA、例外ハンドリング、テストまでを扱います。毎レッスン、Spring が裏側でやっている判断そのものを pure Java の関数として自分で書き、フレームワークが何を肩代わりしてくれているのかを手触りで理解できる構成にしました。約 12 時間 (1 日 30 分 × 24 日) で 48 レッスンを修了できます。

1
Spring Bootとは何か
01. Spring Bootで何が作れるのか10分
02. サーブレットからフレームワークへ15分
03. ルーティング表という発想15分
04. プロジェクトの構成10分
05. 起動のしくみと自動設定15分
06. パスのマッチング15分
2
DIコンテナ
01. newで書くと何が困るのか15分
02. コンストラクタインジェクション15分
03. インターフェースへの依存15分
04. @Componentと@Bean15分
05. 自作のDIコンテナ20分
06. Beanのスコープとライフサイクル15分
3
RESTコントローラ
01. RESTという設計方針15分
02. @RestControllerの役割15分
03. @GetMappingとパス変数15分
04. クエリパラメータの受け取り15分
05. @PostMappingとリクエストボディ15分
06. ステータスコードの選び方15分
07. ResponseEntityで組み立てる15分
4
リクエストとレスポンスの変換
01. JSONとオブジェクトの往復15分
02. DTOとエンティティを分ける15分
03. recordでDTOを書く15分
04. 入力検証の基本15分
05. 検証エラーをまとめて返す15分
06. アノテーションによる検証15分
5
レイヤ構成とビジネスロジック
01. 3層アーキテクチャ10分
02. @Serviceに置くもの15分
03. 状態遷移をルールにする15分
04. 料金計算のような業務ロジック15分
05. @Transactionalとは何か15分
06. トランザクションの巻き戻し15分
6
データアクセス
01. @Entityとテーブルの対応15分
02. リポジトリという抽象15分
03. メソッド名からクエリを作る20分
04. 検索条件の組み立て15分
05. ページングと並び替え15分
06. N+1問題15分
7
例外処理と横断的関心事
01. 例外をHTTPに変換する15分
02. @ExceptionHandlerと@ControllerAdvice15分
03. エラーレスポンスの形を決める15分
04. フィルタとインターセプタ15分
05. ログとリクエストの追跡15分
06. 設定値の外部化15分
8
テストと仕上げ
01. テストの種類と使い分け15分
02. Serviceの単体テスト15分
03. モックで依存を差し替える15分
04. MockMvcでエンドポイントを試す15分
05. 本番へ出すための次の一歩10分

検証エラーをまとめて返す

このレッスンでは、検証エラーを1件で打ち切らずにまとめて返す設計と、その受け皿である BindingResult を理解します。

1件ずつ返す API は使いにくいものです

入力フォームに5つの項目があり、そのうち3つが誤っているとします。サーバーが最初の1件だけを返すと、利用者は直して送信し、また次のエラーを見せられ、また直す、という往復を3回することになります。まとめて返せば1回で済みます。エラーを集約するかどうかは、単なる実装の都合ではなく利用者の体験そのものです。

Spring はすべての制約を評価します

Bean Validation の Validator は、最初の違反で止まりません。対象オブジェクトのすべての制約を評価し、違反の集合を返します。@Valid を付けた引数の直後に BindingResult 型の引数を置くと、その集合を自分で受け取れます。

Java

@PostMapping("/users") public ResponseEntity<?> create(@Valid @RequestBody UserRequest req, BindingResult result) { if (result.hasErrors()) { List<String> messages = result.getFieldErrors().stream() .map(e -> e.getField() + "=" + e.getDefaultMessage()) .toList(); return ResponseEntity.badRequest().body(messages); } // 正常系 }

ここで重要なのは、BindingResult を引数に置いた瞬間、Spring は例外を投げなくなるという点です。検証に失敗しても制御はメソッドの中へ入り、続きは自分で書くことになります。逆に BindingResult を置かなければ MethodArgumentNotValidException が飛び、既定のエラー応答になります。どちらを選ぶかで責任の所在が変わります。

書き方違反時の振る舞い
@Valid だけ例外が投げられ、既定の 400 応答になる
@Valid と BindingResultメソッドに入り、自分で応答を組み立てる

どのフィールドが悪かったのかを返します

エラーの本文で最も価値のある情報は「どの項目が」「なぜ」駄目だったのかです。FieldError は getField() でフィールド名、getDefaultMessage() でメッセージ、getRejectedValue() で拒否された値を返します。これを使うと、次のような応答が組めます。

JSON

{"errors":[{"field":"name","reason":"required"},{"field":"age","reason":"range"}]}

クライアントはこの field を見て、対応する入力欄の横にメッセージを出せます。逆に文章を1本返すだけの設計では、どの欄が悪いのかを画面側で推測することになり、うまくいきません。

順序を決めておきます

Validator が返す違反の集合には、決まった順序がありません。応答の順が呼び出しごとに変わるとテストが安定しないので、フィールドの定義順やアルファベット順など、自分で並びを決めて整えるのが実務的です。

演習で書くもの

演習では、複数の検証を全部評価して結果を集め、フィールド名の付いた形で連結して返します。区切りにはイコールとカンマを使い、name=required,age=range のような文字列を組み立てます。エラーが1件も無ければ ok を返します。Spring では BindingResult から取り出して整形する部分にあたる処理を、ここでは検証そのものを含めて自分で書きます。前のレッスンとの違いは、最初の1件で return せず、最後まで集める点です。

要件

  1. 最初の違反で打ち切らず、すべての違反を集めてから連結すること
  2. 並びは name、age、email の順に固定し、区切りはカンマとすること
  3. name の必須と長さは排他とし、email の必須と形式も排他とすること。違反が無ければ ok を返すこと

入出力例

validateAll("taro", 20, "taro@example.com") → "ok" validateAll("", 200, "bad") → "name=required,age=range,email=format" validateAll("taro", 200, "taro@example.com") → "age=range" validateAll("abcdefghijklmnopqrstu", 20, "") → "name=length,email=required" validateAll("", -5, "") → "name=required,age=range,email=required" validateAll("taro", 0, "a@b@c") → "email=format"

ヒント

List<String> にエラー文字列を追加していき、最後に String.join(",", errors) でつなぐと並びが安定します

必須と長さを排他にするには if と else if でつなぎます。別々の if にすると null のときに length() で落ちます

email の形式は indexOf('@') と lastIndexOf('@') が一致し、位置が 0 でも末尾でもないことで判定できます

生田 陸人
監修生田 陸人
ゆめさくエンジニア / 現役ソフトウェアエンジニア監修者プロフィールを見る →
編集 ゆめさく編集部·公開 2026/08/09

関連レッスン

  • アノテーションによる検証

    @NotNull と @NotBlank の違いなどを確認します。

  • 3層アーキテクチャ

    層を分ける理由と、それぞれの責務を説明できるようになります。

  • @Entityとテーブルの対応

    エンティティのマッピングと主キーの指定を確認します。

  • 例外をHTTPに変換する

    例外の種類からステータスコードを決める処理を実装します。

このレッスンに出てくる用語

意味があいまいなまま進んだ語は、ここから読み直せます。

  • 設計何をどう作るかを決める前工程
  • エンドポイント「この URL を叩くとこの機能が動く」入口の住所
  • APIアプリ間でデータをやり取りする窓口
  • フォームinput(text, email, password, checkbox, radio)、select、textarea、button。
  • サーバークライアント(ブラウザなど)がリクエストを送り、サーバーがレスポンスを返す。
  • 集約複数の値を 1 つの結果にまとめる操作
  • 評価出力品質を数値化して継続改善する活動
  • オブジェクトキーと値のペアで構造を作るデータ
main.java
学習モード
エディタを読み込んでいます

メモ

検証エラーをまとめて返す

⌘S で保存