Spring BootでWebアプリケーションやREST APIを開発していると、リクエストデータのバリデーション(検証)は避けて通れない処理です。例えば、ユーザー登録時に名前やメールアドレスが空でないか、形式が正しいかといったチェックが必要になります。
そんなときに役立つのが、@Validアノテーションです。本記事では、@Validの基本的な役割から、実際のバリデーションルールの定義方法まで、実践的に解説していきます。
@Validとは何か?
@Validは、Javaの Jakarta Bean Validation(旧 JSR 303/380)仕様に準拠したバリデーション処理をトリガーするアノテーション です。Spring Bootでは、spring-boot-starter-validationを導入することで、コントローラーの引数やJavaオブジェクトに対して簡単にバリデーションが行えるようになります。
ただし、@Valid自体は「このオブジェクトを検証対象にする」という トリガー的な役割 しか持ちません。何をどう検証するかは、フィールドに付ける制約アノテーションで定義します。
なぜAPI開発でよく使われるのか?
Spring BootのREST APIでは、クライアントからJSONなどでリクエストデータを受け取ることが一般的です。このとき、受け取ったデータをPOJO(Plain Old Java Object)にマッピングし、@Validを付けることで、Springが自動的にバリデーションを実行してくれます。
@PostMapping("/users")
public ResponseEntity<String> createUser(@RequestBody @Valid UserRequest userRequest) {
return ResponseEntity.ok("User created");
}
これにより、リクエストが不正な場合は、SpringがMethodArgumentNotValidExceptionをスローし、適切なエラーレスポンスを返すような設計が可能です。
バリデーションの内容はアノテーションで定義する
@Validでバリデーション処理が有効になったオブジェクトには、フィールドごとに制約アノテーションを付けることで、検証内容を細かく制御 できます。
public class UserRequest {
@NotBlank(message = "名前は必須です")
@Size(min = 2, max = 20, message = "名前は2〜20文字で入力してください")
private String name;
@Email(message = "メールアドレスの形式が正しくありません")
private String email;
}
このように、フィールドごとに複数の制約を組み合わせることも可能です。
よく使われる制約アノテーション
バリデーションに使用できる制約アノテーションには、以下のようなものがあります。
| アノテーション | 概要 |
|---|---|
@NotNull | nullであってはならない |
@NotBlank | 空文字や空白のみの文字列は禁止 |
@NotEmpty | 空のコレクションや配列、文字列は禁止 |
@Size(min=, max=) | 長さや要素数の制約 |
@Email | メール形式かどうか |
@Pattern(regexp=) | 正規表現に一致するか |
@Min, @Max | 数値の範囲制約 |
@Positive, @Negative | 正数・負数かどうか |
@Past, @Future | 日付が過去か未来か |
これらのアノテーションにmessage属性を指定することで、独自のエラーメッセージを定義することもできます。
ネストされたオブジェクトの検証
@Validは、ネストされたオブジェクトにも再帰的にバリデーションを適用できます。
public class OrderRequest {
@Valid
private Address address;
}
この場合、Addressクラス内に定義されたバリデーションルールも適用されます。
サービス層での手動バリデーション
コントローラー以外のクラス(たとえばService層)でも、Validatorを使えばバリデーションを明示的に実行できます。
@Service
public class UserService {
private final Validator validator;
public UserService(Validator validator) {
this.validator = validator;
}
public void register(UserRequest request) {
Set<ConstraintViolation<UserRequest>> violations = validator.validate(request);
if (!violations.isEmpty()) {
throw new IllegalArgumentException("Validation failed: " + violations);
}
// 登録処理
}
}
@Validatedとの違いと使い分け
@Valid と @Validated の比較表
両者は似ていますが、使える場所と機能に違いがあります。実務で迷ったときの早見表として整理します。
| 観点 | @Valid (Jakarta) | @Validated (Spring) |
|---|---|---|
| 由来 | Jakarta Bean Validation 仕様 | Spring Framework 独自 |
| 主な用途 | コントローラー引数・ネストDTOの再帰検証 | グループバリデーション、メソッド引数検証 |
| グループ指定 | 不可 | 可能(@Validated(OnCreate.class) など) |
| 例外型 | MethodArgumentNotValidException | ConstraintViolationException |
| クラスに付与 | 不可(フィールド/引数のみ) | 可能(Bean全体の検証を有効化) |
| ネスト検証 | 子DTOフィールドへの再帰適用が直感的 | ネストでは @Valid を併用するのが一般的 |
端的にまとめると、コントローラーの@RequestBodyに対する標準的な検証は@Valid、Service層のメソッド引数検証や登録/更新でルールを切り替えたい場合は@Validated という使い分けが実務上もっとも事故が少ないです。
Spring独自の@Validatedアノテーションも、@Validと似た用途で使えます。主な違いは、グループバリデーション や メソッド単位のバリデーション など、より柔軟な制御が可能な点です。
@Component
@Validated
public class UserValidator {
public void validate(@Valid UserRequest request) {
// 自動的にバリデーションされる
}
}
実務でのDTO設計ルール
運用を考えると、DTOのバリデーションは次のルールに揃えると保守しやすくなります。
- API入力専用DTOとDBエンティティを分離する
- フィールドごとに責務を限定し、過剰なバリデーションを避ける
- メッセージはユーザー向けとログ向けを意識して設計する
@NotNullと@NotBlankを用途で使い分ける(文字列には@NotBlank優先)
「とりあえず全部に制約を付ける」より、入力契約として必要なものだけ定義する方が変更に強くなります。
エラーレスポンスを標準化する
@Validを使うなら、MethodArgumentNotValidExceptionの返却形式を最初に標準化しておくのが重要です。
ここが毎回バラバラだと、フロントエンド側の実装コストが増えます。
@RestControllerAdvice
public class ApiExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, Object>> handleValidation(MethodArgumentNotValidException ex) {
var errors = ex.getBindingResult().getFieldErrors().stream()
.map(error -> Map.of(
"field", error.getField(),
"message", error.getDefaultMessage()
))
.toList();
return ResponseEntity.badRequest().body(Map.of(
"code", "VALIDATION_ERROR",
"errors", errors
));
}
}
このようにレスポンスのキー(code, errors, field, message)を固定すると、利用側の扱いが安定します。
よくある落とし穴
バリデーションは通るのに業務要件を満たさない
アノテーション制約は「形式チェック」に強い一方で、「業務ルール」の検証は別です。
例えば「同じメールアドレスが既に存在するか」はDB照会が必要なので、Service層で追加チェックします。
ネストDTOに@Validを付け忘れる
親DTOだけに@Validを付けても、子DTOフィールドに@Validが無いと再帰検証されません。
入れ子構造を使う場合は、親子両方の注釈を確認してください。
バリデーションメッセージをハードコードしすぎる
将来的に多言語対応する可能性がある場合、messages.propertiesへ寄せる設計が有効です。
@NotBlank(message = "{validation.name.required}")
private String name;
まとめ
@Validアノテーションを使うことで、Spring Bootにおける入力値のバリデーションを簡潔かつ柔軟に実装できます。実際のバリデーションルールは、対象フィールドに制約アノテーションを付けることで定義されます。これにより、APIの堅牢性を高めると同時に、ユーザーに対して分かりやすいエラーメッセージを提供することができます。
APIに限らず、任意のコンポーネント内でもValidatorを使ってバリデーションを行えるため、業務ロジックに応じた使い分けも可能です。
ぜひ@Validアノテーションを活用して、より堅牢なアプリケーションを作成してみてください!
よくある質問 (FAQ)
@Valid と @RequestBody はどちらを先に書くべき?
どちらを先に書いても動作は同じです。Spring MVCはアノテーション順序に依存しません。可読性のため、チームでどちらかに統一しておくと良いでしょう。
@Valid を付けても検証が走らないのはなぜ?
よくある原因は次の3つです。
spring-boot-starter-validationが依存に入っていない(Spring Boot 2.3 以降は自動追加されないので注意)。- ネストDTOのフィールドに
@Validを付け忘れている。 - Service層のメソッド検証を期待しているが、クラスに
@Validatedが付いていない(@Valid単独ではメソッド検証は起動しない)。
MethodArgumentNotValidException と ConstraintViolationException の違いは?
@RequestBody @Valid で検証が失敗すると前者、@Validated を付けたBeanのメソッド引数(パスパラメータやクエリパラメータ)で失敗すると後者が投げられます。@RestControllerAdvice では両方を別々に補足するハンドラを用意しておくと、エラーレスポンス形式を統一できます。
バリデーションメッセージを多言語化したい
messages.properties / messages_en.properties などに validation.name.required=名前は必須です の形式でキーを定義し、制約アノテーションでは @NotBlank(message = "{validation.name.required}") のように波括弧で参照します。Spring Bootは LocaleResolver で解決された言語に応じて自動的に切り替えます。
@NotNull と @NotBlank と @NotEmpty の使い分けは?
@NotNull: nullでなければOK(空文字""は通る)。@NotEmpty: null・空(長さ0)でなければOK(空白文字" "は通る)。@NotBlank: null・空・空白のみのいずれも禁止。文字列フィールドには基本的に@NotBlankを使うのが安全です。