SpringでAPIリクエストをバリデーションしよう!初心者向け@Validの使い方解説
新人
「SpringでAPIを作っているんですけど、ユーザー入力が正しいかどうかってどうやって確認すればいいんですか?」
先輩
「それはバリデーションという仕組みを使って確認できるよ。特にSpringでは@Validというアノテーションを使うのが一般的なんだ。」
新人
「@Validって聞いたことありますけど、どうやって使うんですか?」
先輩
「よし、じゃあ今回はSpringでのリクエストのバリデーションの基本を一緒に学んでいこう!」
1. バリデーションとは?API開発に欠かせない入力チェック
API開発においてバリデーションは非常に重要です。バリデーションとは、リクエストで送られてくるデータが正しい形式かどうかをチェックする処理のことです。
例えば、ユーザー登録APIでは、次のようなチェックが必要になるでしょう。
- メールアドレスの形式が正しいか
- パスワードの文字数が足りているか
- 必須項目がすべて入力されているか
こうしたチェックをしないと、不正なデータがシステムに入り込んでしまい、バグやセキュリティ上のリスクにつながります。Springではこうしたリクエストのバリデーションを非常に簡単に行える仕組みが用意されています。
2. Springでリクエストバリデーションを実装する基本
Spring Frameworkでは、@Validアノテーションを使うことで、リクエストデータのバリデーションを簡単に実装できます。さらに、BindingResultを使えば、エラーの有無をコード内で判断できます。
ここでは、@Controller構成でのバリデーションの基本的な使い方を紹介します。
バリデーション対象のDTOクラスを作成
まずは、ユーザー登録用のデータを表すDTOクラスを用意し、バリデーションルールを付けます。
package com.example.demo.dto;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
public class UserRequest {
@NotBlank(message = "名前は必須です")
private String name;
@Email(message = "メールアドレスの形式が不正です")
private String email;
@Size(min = 8, message = "パスワードは8文字以上にしてください")
private String password;
// Getter / Setter
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
コントローラで@ValidとBindingResultを使う
続いて、@Controllerでバリデーションを実行します。フォーム送信時に@ValidとBindingResultをセットで使うことで、バリデーションエラーを確認できます。
package com.example.demo.controller;
import com.example.demo.dto.UserRequest;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
@Controller
public class UserController {
@PostMapping("/register")
public String registerUser(@ModelAttribute @Valid UserRequest userRequest,
BindingResult result,
Model model) {
if (result.hasErrors()) {
model.addAttribute("errors", result.getAllErrors());
return "registerForm";
}
// 登録処理(省略)
model.addAttribute("message", "ユーザー登録に成功しました!");
return "success";
}
}
エラーメッセージをHTMLで表示する
画面側(HTMLテンプレート)でエラーメッセージを表示するには、Thymeleafなどのテンプレートエンジンでerrorsを表示します。
<form method="post" action="/register">
<input type="text" name="name" placeholder="名前">
<input type="email" name="email" placeholder="メールアドレス">
<input type="password" name="password" placeholder="パスワード">
<button type="submit">登録</button>
</form>
<ul>
<th:block th:each="error : ${errors}">
<li th:text="${error.defaultMessage}"></li>
</th:block>
</ul>
このように@ValidとBindingResultを組み合わせることで、SpringのAPIバリデーションがとても簡単に行えます。
初心者でも取り組みやすく、セキュリティや品質の面でも安心できるため、必ず実装するようにしましょう。
3. フィールドレベルのバリデーション:@NotNull、@Size、@Emailの使い方
Springのバリデーションでは、フィールドに対して直接アノテーションを付けて制約を指定できます。これを「フィールドレベルのバリデーション」と呼びます。
例えば、次のようなアノテーションがよく使われます。
@NotNull:nullであってはいけない@NotBlank:空文字も不可@Size:文字列やリストのサイズを制限@Email:メール形式かどうかをチェック
これらのアノテーションを組み合わせることで、細かく入力制限ができます。以下の例は、@NotNullや@Sizeを使用したユーザーフォームの例です。
package com.example.demo.dto;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import jakarta.validation.constraints.Email;
public class ContactForm {
@NotNull(message = "件名は必須です")
@Size(min = 5, max = 100, message = "件名は5文字以上100文字以内で入力してください")
private String subject;
@NotNull(message = "メールアドレスは必須です")
@Email(message = "正しいメールアドレス形式で入力してください")
private String email;
@Size(max = 500, message = "本文は500文字以内で入力してください")
private String message;
// Getter / Setter(省略)
}
このように@NotNullや@Sizeなどのアノテーションを使えば、初心者でも簡単にAPIのリクエストチェックができます。
4. BindingResultを使ったエラー処理の流れ
BindingResultは、Springでリクエストのバリデーション結果を受け取るための仕組みです。@Validの直後の引数として宣言することで、入力チェックのエラー情報を取得できます。
エラーがあるかどうかは、hasErrors()メソッドで判断し、必要に応じて画面に戻したり、エラーメッセージを表示する処理を行います。
以下は、BindingResultを使ったエラー処理の基本パターンです。
@PostMapping("/contact")
public String submitContact(@ModelAttribute @Valid ContactForm contactForm,
BindingResult result,
Model model) {
if (result.hasErrors()) {
model.addAttribute("errors", result.getAllErrors());
return "contactForm";
}
// 正常処理(メール送信など)
model.addAttribute("message", "お問い合わせを受け付けました");
return "contactSuccess";
}
このようにBindingResultを使えば、Springのバリデーションで検出されたエラーをModelに渡して画面に反映させることができます。
初心者がバリデーションエラー処理を理解するうえで、まずこの「@Valid + BindingResult」の組み合わせをしっかりと押さえることが重要です。
5. カスタムエラーメッセージの設定方法
Springのバリデーションでは、アノテーションに直接message属性を指定することで、独自のエラーメッセージを定義できます。これにより、ユーザーにとって分かりやすく、親切なエラー表示が可能になります。
前の章で紹介したように、各フィールドにmessage属性を指定すれば、その内容がエラー時に表示されます。
例:
@NotNull(message = "ユーザー名は入力必須です")
@Size(min = 4, max = 20, message = "ユーザー名は4文字以上20文字以内で入力してください")
private String username;
メッセージプロパティファイルに分けて管理する
エラーメッセージが多くなる場合や多言語対応を考慮する場合は、プロパティファイル(messages.properties)に分離するのがおすすめです。
以下のように、アノテーションのmessage属性にキーを指定し、プロパティファイルにメッセージを記述します。
@NotNull(message = "{user.username.required}")
# src/main/resources/messages.properties
user.username.required=ユーザー名を入力してください
このようにプロパティファイルでバリデーションメッセージを管理すると、メッセージの一括管理ができ、Springプロジェクトの保守性も高まります。
プロパティファイルを有効にするには、Spring Bootであれば自動で読み込まれますが、通常のSpringアプリケーションではValidationMessages.propertiesというファイル名にしてresourcesフォルダに配置すると自動で使われるようになります。
6. レスポンスに対するバリデーションの考え方
これまでAPIのリクエストに対するバリデーションを中心に解説してきましたが、実はレスポンスに対しても整合性や安全性を意識する必要があります。Springではレスポンスそのものにバリデーションアノテーションを付けることは一般的ではありませんが、DTOを通して設計することで、誤った情報や機密データの漏洩を防ぐことができます。
例えば、ユーザーのパスワードや内部IDなどは、クライアントに返すべきではありません。レスポンス専用のDTO(Data Transfer Object)を使って必要な情報だけを返すようにしましょう。
package com.example.demo.dto;
public class UserResponse {
private String name;
private String email;
public UserResponse(String name, String email) {
this.name = name;
this.email = email;
}
public String getName() {
return name;
}
public String getEmail() {
return email;
}
}
このように、エンティティではなく専用のレスポンスDTOを定義し、コントローラで整形して返却することで、SpringアプリケーションのAPIレスポンスの安全性と明確さを保つことができます。
7. 実務でよくあるバリデーションの落とし穴とその対策
初心者がSpringのバリデーションを使う中で、つまずきやすいポイントはいくつかあります。ここでは、よくある落とし穴とその回避策を紹介します。
@Validの位置に注意
@Validを付ける位置が間違っていると、バリデーションが実行されません。特に@ModelAttributeや@RequestBodyの前に付ける必要があるため、順番には注意しましょう。
// 正しい例
public String create(@ModelAttribute @Valid UserRequest form, BindingResult result)
BindingResultを忘れるとエラーになる
バリデーションエラーを検出するためにはBindingResultが必須です。これを省略すると、エラー時に例外が発生してしまい、画面に戻す処理ができなくなります。
エラーメッセージの日本語化忘れ
デフォルトでは英語でエラーメッセージが表示されるため、日本語でわかりやすく表示したい場合はValidationMessages.propertiesの作成を忘れずに行いましょう。
重複チェックは別ロジックで
@NotBlankや@Sizeで形式のチェックはできますが、重複チェック(例えば「すでに使われているメールアドレスか」など)は、サービス層で別に処理する必要があります。
if (userRepository.existsByEmail(userRequest.getEmail())) {
result.rejectValue("email", "duplicate", "このメールアドレスは既に登録されています");
return "registerForm";
}
このように、形式チェックはアノテーションで、業務的なチェックは別途ロジックで分けて設計するのがSpringにおけるAPIバリデーションの正しい使い方です。
8. バリデーションを活用したSpring API設計のベストプラクティス
最後に、実務で使えるSpringバリデーションのベストプラクティスを紹介します。初心者向けにもわかりやすく、再利用性と保守性の高いAPIを作るために意識すべきポイントです。
DTOでリクエストとレスポンスを明確に分離
Springでは、バリデーションを適用するためにDTOを利用しますが、レスポンスにも専用のDTOを使うことで、漏洩リスクを防ぎつつ、データの整形もしやすくなります。
エラーハンドリングは共通化
バリデーションエラーの処理を毎回Controllerで行うのは冗長になりがちです。@ControllerAdviceを使えば、全体のエラーハンドリングを一元管理できます。
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public String handleValidationException(MethodArgumentNotValidException ex, Model model) {
BindingResult result = ex.getBindingResult();
model.addAttribute("errors", result.getAllErrors());
return "errorView";
}
}
独自アノテーションで業務ルールを表現
Springでは、標準のアノテーションだけでは表現しきれない業務ルールがある場合、独自のバリデーションアノテーションを作成することもできます。例えば「平日しか予約できない」といったルールも、独自制約で柔軟に対応できます。
入力制限はなるべくDTOに集約
コントローラにロジックを書きすぎるとテストしづらくなります。バリデーションや初期チェックはDTOに集中させ、コントローラはできるだけ処理の流れだけに専念させるとよいでしょう。
以上のようなベストプラクティスを意識することで、Springを使ったAPI設計の品質が大きく向上します。バリデーションは単なるエラー防止ではなく、システム全体の信頼性とユーザー体験を支える大切な要素なのです。