AIによる要約
@RequestParamへ@NotBlankや@Minを付けても効かないときは、Validation依存関係、Springのバージョン、例外の受け取り方を確認します。Spring Framework 6.1以降のSpring MVCにはControllerメソッドの検証機能があり、旧来のAOP方式で使うクラスレベルの@Validatedを外す方が適切な構成もあります。@Validだけでは制約にならず、BindingResultも単純なRequestParam検証の受け皿ではありません。


Spring Bootの検索APIや一覧画面では、@RequestParamでキーワード、ページ番号、期間などを受け取ります。必須チェックや範囲チェックを付けたのに動かない、エラーがBindingResultへ入らない、環境を更新したら例外型が変わった、といった問題が起こりやすい箇所です。
特にWeb上には、クラスへ@Validatedを付ける古い解説と、付けない新しい解説が混在しています。この記事では、若手が現場のSpringバージョンを確認しながら判断できるよう、現在のSpring MVCと旧来方式を分けて説明します。
この記事のポイント
@RequestParam(required = true)と@NotBlankは確認内容が違う@Valid単体は「検証を連鎖させる印」で、必須制約ではない- Spring Framework 6.1以降はMVC標準のメソッド検証を使える
BindingResultは単純なRequestParam制約違反を受けるためのものではない
@RequestParamへ制約を付ける基本形
検索キーワードの空白チェックと、ページ番号の最小値チェックを行う例です。
@RestController
@RequestMapping("/api/employees")
public class EmployeeSearchController {
@GetMapping
public List<EmployeeResponse> search(
@RequestParam @NotBlank @Size(max = 50) String keyword,
@RequestParam(defaultValue = "0") @Min(0) int page) {
return employeeService.search(keyword, page);
}
}
ここではjakarta.validation.constraints.NotBlankなどをimportします。古いSpring Boot 2系ではjavax.validationを使うため、別世代のimportを混ぜないようにします。
requiredと@NotBlankは役割が違う
| 指定 | 確認すること | 例 |
|---|---|---|
@RequestParam(required = true) | パラメータ自体が存在するか | ?keyword=は存在している |
@NotNull | nullではないか | 空文字は通る |
@NotEmpty | nullまたは長さ0でないか | 半角空白だけは通る |
@NotBlank | null、空文字、空白だけでないか | 検索語の必須チェック向き |
@RequestParamのrequiredは既定でtrueですが、?keyword=のようにパラメータが存在して値だけ空の場合は、存在チェックを通ることがあります。空白を拒否したいならBean Validationの制約が必要です。
「パラメータがない」と「値が空」は別のエラーとして切り分けます。
@Validだけ付けても必須チェックにはならない
// @Valid自体は「空文字禁止」という制約ではない
@GetMapping
public List<EmployeeResponse> search(
@RequestParam @Valid String keyword) {
return employeeService.search(keyword);
}
@Validは、FormやDTOの中にある制約を連鎖的に検証するときに使います。文字列そのものを空白禁止にしたいなら@NotBlank、長さなら@Sizeのように、確認したい条件を直接指定します。
Spring Framework 6.1以降ではMVCがメソッド検証を行う
Spring Framework 6.1以降のSpring MVCは、Controllerメソッド引数へ直接付けた@NotBlankや@Minなどを検証できます。Spring Bootでは、おおむね3.2以降でこの世代のSpring Frameworkが使われます。
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@GetMapping
public List<OrderResponse> find(
@RequestParam @NotBlank String customerCode,
@RequestParam @Min(1) int limit) {
return orderService.find(customerCode, limit);
}
}
MVC標準のメソッド検証を使う場合、旧来のAOP方式を有効にするクラスレベルの@Validatedは外すよう、Springの公式リファレンスで案内されています。Controllerをプロキシ経由で検証する方式と、MVC自身の方式を混ぜないためです。
既存Controllerから一括で@Validatedを削除してはいけません。Springのバージョン、Validationグループ、独自設定、テスト結果を確認し、プロジェクトの移行方針に従ってください。
古いSpringでは@Validatedが必要な構成がある
Spring Framework 6.1より前のControllerメソッド検証では、クラスに@Validatedを付け、Method Validationの仕組みを通す構成が広く使われていました。
@Validated
@RestController
@RequestMapping("/api/employees")
public class EmployeeSearchController {
@GetMapping
public List<EmployeeResponse> search(
@RequestParam @NotBlank String keyword) {
return employeeService.search(keyword);
}
}
この方式では、制約違反がConstraintViolationExceptionとして扱われる構成があります。Webの記事をそのまま貼り付ける前に、pom.xmlまたはbuild.gradleでSpring Bootのバージョンを確認し、同じ世代の公式ドキュメントを見ます。
Validationの依存関係を確認する
制約アノテーションを書けても、実行に必要なValidation実装が依存関係へ入っていなければ期待どおり動きません。Spring Bootでは通常、Validation Starterを追加します。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
親プロジェクトの依存関係管理で入っている場合もあるため、重複追加する前に依存ツリーと既存モジュールを確認します。
BindingResultへ入らないのはなぜか
BindingResultは、@Valid @ModelAttributeなどでバインドしたオブジェクトの検証エラーを、Controller内で受け取るときに使います。単純な@RequestParamへ付けた制約の違反を、次のように受け取るものではありません。
// RequestParamの制約違反をBindingResultで処理する考え方ではない
@GetMapping
public List<EmployeeResponse> search(
@RequestParam @NotBlank String keyword,
BindingResult bindingResult) {
// ...
}
Spring MVC 6.1以降のメソッド検証では、主にHandlerMethodValidationExceptionとして処理されます。一方、Formや@RequestBodyのオブジェクト検証ではMethodArgumentNotValidExceptionが使われる場面があります。入力方法によって例外処理を分けます。
例外ハンドラを用意する
REST APIで独自のエラー形式へ揃えるなら、@RestControllerAdviceでメソッド検証の例外を変換します。
@RestControllerAdvice
public class ApiValidationExceptionHandler {
@ExceptionHandler(HandlerMethodValidationException.class)
public ResponseEntity<ApiError> handleMethodValidation(
HandlerMethodValidationException ex) {
ApiError error = new ApiError(
"INVALID_PARAMETER",
"リクエストパラメータを確認してください");
return ResponseEntity.badRequest().body(error);
}
}
利用者向けメッセージだけでなく、どのパラメータが何の制約に違反したかをレスポンスへ含める場合は、プロジェクトの共通エラー形式へ合わせます。例外メッセージをそのまま外部へ返さないようにします。
必須不足と型変換エラーはValidationより前に起きる
パラメータ自体がない場合や、数値へ変換できない文字列が送られた場合は、Bean Validationの制約評価まで到達しないことがあります。
| リクエスト例 | 主な失敗 |
|---|---|
/api/employees | 必須RequestParamの不足 |
?page=abc | Stringからintへの型変換失敗 |
?page=-1 | @Min(0)の制約違反 |
?keyword= | @NotBlankの制約違反 |
すべてを「Validationエラー」と一括りにせず、パラメータ不足、型変換、制約違反のどこで失敗したかをログと例外型から確認します。
パラメータが増えたらFormクラスを検討する
検索条件が2、3個なら@RequestParamでも読みやすいですが、条件が増えるとアノテーションと引数が並び、検証も複雑になります。
public class EmployeeSearchForm {
@Size(max = 50)
private String keyword;
private String departmentCode;
@Min(0)
private int page = 0;
@Min(1)
@Max(100)
private int size = 20;
// getter / setter
}
@GetMapping
public List<EmployeeResponse> search(
@Valid @ModelAttribute EmployeeSearchForm form,
BindingResult bindingResult) {
// ...
}
FormへまとめるとBindingResultでエラーを受け取りやすくなります。ただし、単一のIDを受け取るだけなのに毎回クラスを作る必要はありません。条件数、再利用性、項目間チェックの有無で判断します。
MockMvcで実際のレスポンスを確認する
@Test
void keywordが空白なら400を返す() throws Exception {
mockMvc.perform(get("/api/employees")
.param("keyword", " ")
.param("page", "0"))
.andExpect(status().isBadRequest());
}
@Test
void pageが負数なら400を返す() throws Exception {
mockMvc.perform(get("/api/employees")
.param("keyword", "田中")
.param("page", "-1"))
.andExpect(status().isBadRequest());
}
「アノテーションが付いている」ことではなく、無効な入力を送ったときのHTTPステータスとエラー形式をテストします。バージョン更新時に例外処理が変わっても、外部仕様の変化を検知できます。
効かないときの確認順
- Validation Starterが依存関係へ入っているか確認する
jakarta.validationとjavax.validationの世代を確認する@Validではなく、必要な制約が付いているか確認する- Spring BootとSpring Frameworkのバージョンを確認する
- クラスレベルの
@Validated有無と既存方式を確認する - 発生した例外型を確認し、対応するハンドラを用意する
- MockMvcで実際の無効入力を送って再現する
現場レビューでよくある指摘
// レビューコメント例 @Validだけでは空文字チェックになりません。 要件に合わせて@NotBlankや@Sizeを指定してください。 // レビューコメント例 このプロジェクトはSpring MVC 6.1以降のメソッド検証を使っています。 クラスレベルの@Validatedを追加する前に、既存Controllerの方式へ揃えてください。 // レビューコメント例 RequestParamの制約違反をBindingResultで受けようとしています。 発生する例外型を確認し、共通例外ハンドラ側で処理してください。
提出前のセルフチェック
- 存在、空白、文字数、数値範囲を区別して制約を選んだか
- Validationの依存関係とimportの世代が合っているか
- 利用中のSpring Frameworkバージョンを確認したか
@Validatedを既存方針なしに追加していないか- 発生する例外を共通エラー形式へ変換しているか
- 無効な値を送るMockMvcテストがあるか
Springの検証処理を深める参考書
アノテーションの暗記ではなく、Spring MVCが引数を解決し、検証し、例外へ変換する流れを学ぶと、バージョン差にも対応しやすくなります。
Spring徹底入門 第2版
Spring Frameworkの主要機能を、実務レベルで整理する。
DI、MVC、データアクセス、テスト、セキュリティまで幅広く確認できます。ピンポイント検索から体系学習へ進みたい人向けです。
- Springの全体像を整理したい
- 現場で使う機能を体系的に学びたい
当サイトはAmazonアソシエイト・プログラムの参加者です。価格・在庫・配送条件はAmazonでご確認ください。
この記事とあわせて読みたい
まとめ
@RequestParamの検証が効かないときは、制約の種類、Validation依存関係、import、Springのバージョンを順番に確認します。@Validだけでは空白チェックにならず、BindingResultも単純なRequestParam制約違反の受け皿ではありません。
Spring Framework 6.1以降はMVC標準のメソッド検証が使えますが、古い案件ではクラスレベルの@Validatedを使う方式があります。検索で見つけた実装をそのまま混ぜず、プロジェクトのバージョンと既存Controllerを確認し、無効入力のテストで動作を確定させましょう。
