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との違いと使い分け
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アノテーションを活用して、より堅牢なアプリケーションを作成してみてください!