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;
}

このように、フィールドごとに複数の制約を組み合わせることも可能です。

よく使われる制約アノテーション

バリデーションに使用できる制約アノテーションには、以下のようなものがあります。

アノテーション概要
@NotNullnullであってはならない
@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) など)
例外型MethodArgumentNotValidExceptionConstraintViolationException
クラスに付与不可(フィールド/引数のみ)可能(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つです。

  1. spring-boot-starter-validation が依存に入っていない(Spring Boot 2.3 以降は自動追加されないので注意)。
  2. ネストDTOのフィールドに @Valid を付け忘れている。
  3. Service層のメソッド検証を期待しているが、クラスに @Validated が付いていない(@Valid 単独ではメソッド検証は起動しない)。

MethodArgumentNotValidExceptionConstraintViolationException の違いは?

@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 を使うのが安全です。