Skip to main content

入力値のバリデーション

この章の目的とゴール

この章では、Webアプリケーションにおいて 入力値のバリデーション(検証) を行う方法を学ぶ。 ユーザーが送信した値をそのまま受け入れると、システムの誤動作やセキュリティ事故につながるため、 入力値が正しいかをチェックすることは必須である。

この章では、Spring MVCで入力値のバリデーション(検証)を行う方法を学ぶ。 フォーム入力のチェックを自動化し、安全で信頼性の高いアプリケーションを作る基礎を身につける。

学習のゴール

  • バリデーションの目的と種類(単項目チェック / 相関項目チェック)を理解できる
  • フォームオブジェクトのフィールドにアノテーションを付けて自動検証を実装できる
  • @AssertTrue を使って複数項目の関係(相関チェック)を実装できる
  • コントローラで @ValidatedBindingResult を用いて検証結果を処理できる
  • th:object / th:field を使ってフォーム入力とエラーメッセージを連携できる
  • フロントエンドだけではなく、サーバサイドでもバリデーションが必要な理由を理解できる

バリデーションとは

バリデーションとは、入力された値が正しいかどうかを検証する仕組み である。

  • 数字が入るべきところに文字列が入力されていないか
  • 必須項目が空になっていないか
  • 2つの項目(パスワードと確認用パスワード)が一致しているか

などをチェックする。


単項目チェックと相関項目チェック

単項目チェック

1つの入力値が正しいかどうかを確認する。 例: 名前が空でないか、年齢が0以上か、メールアドレス形式か。

相関項目チェック

複数の入力値の関係が正しいかどうかを確認する。 例: パスワードと確認用パスワードが一致しているか、開始日が終了日より前か。


単項目チェックの実装

単項目チェックは、フォームオブジェクトのフィールドにアノテーションを付与することで実現できる。

UserForm.javajava
public class UserForm {
@NotNull(message = "名前は必須です")
private String name;
@Min(value = 0, message = "年齢は0以上で入力してください")
@Max(value = 120, message = "年齢は120以下で入力してください")
private Integer age;
@Email(message = "メールアドレスの形式が正しくありません")
private String email;
// getter / setter
}
  • @NotNull : 値が null でないことを保証する
  • @Min, @Max : 数値の範囲を指定する
  • @Email : メールアドレス形式かどうかを確認する
@NotNull と @NotBlank の違い
アノテーションチェック内容
@NotNullnull でなければOK(空文字は通過してしまう)
@NotEmptynull でも空文字でもなければOK(空白のみはNG)
@NotBlanknull でも空文字でも空白のみでもなければOK

文字列の入力必須チェックには @NotBlank を使うのが最も適切である。

👉 独自アノテーションを作ることも可能だが、本研修では扱わない。


相関項目チェックの実装

複数項目の関係をチェックするには @AssertTrue を用いる。

PasswordForm.javajava
public class PasswordForm {
private String password;
private String confirmPassword;
@AssertTrue(message = "パスワードが一致しません")
public boolean isPasswordConfirmed() {
return password != null && password.equals(confirmPassword);
}
// (省略) getter / setter
}
  • @AssertTrue が付いたメソッドは、trueを返すと検証成功、falseを返すと失敗
  • この例では、passwordconfirmPassword が一致しなければエラーとなる

👉 より柔軟に実装するには Validator インターフェースを実装する方法もあるが、本研修では扱わない。


コントローラでのバリデーション

フォームオブジェクトを受け取るコントローラのメソッドに @Validated を付与する。 忘れるとバリデーションが実行されないので注意。

バリデーション結果は BindingResult から取得できる。

RegisterController.javajava
@Controller
public class RegisterController {
@PostMapping("/register")
public String register(
@Validated UserForm form, // @Validated を付与するとバリデーションが実行される
BindingResult bindingResult, // バリデーション結果を受け取る
Model model) {
if (bindingResult.hasErrors()) {
return "registerForm"; // エラー時はフォーム画面に戻す
}
// 正常処理
model.addAttribute("msg", "登録完了");
return "result";
}
}
  • @Validated : このオブジェクトに対してバリデーションを行う
  • BindingResult : 検証結果(エラーの有無・内容)を保持する
@Validated を忘れるとバリデーションが実行されない

@Validated を付け忘れると、フォームオブジェクトにどんなアノテーションを書いても バリデーションは一切実行されず、常に通過してしまう。

また、BindingResult必ず @Validated を付けたフォームオブジェクトの直後 に書く必要がある。 順番が違うと正しく動作しない。

// 正しい順番
public String register(@Validated UserForm form, BindingResult bindingResult, Model model)

// 間違い(BindingResult が離れている)
public String register(@Validated UserForm form, Model model, BindingResult bindingResult)

フォーム定義とエラーメッセージ表示

Spring MVCでは、コントローラでフォームオブジェクトを受け取り、ビューに返す ことでバリデーションとエラーメッセージ表示を行う。 フォーム側では th:object / th:field を使うことで、フォームオブジェクトとHTMLを簡潔にバインドできる。

RegisterController.javajava
@Controller
public class RegisterController {
@GetMapping("/register")
public String showForm(UserForm form) {
// 空のフォームをビューに渡す
// model.addAttribute("userForm", form); // ← これは不要
return "registerForm";
}
@PostMapping("/register")
public String register(
@Validated UserForm form,
BindingResult bindingResult,
Model model) {
if (bindingResult.hasErrors()) {
// バリデーション失敗 → 入力画面に戻す
return "registerForm";
}
// 正常処理
model.addAttribute("msg", "登録完了");
return "result";
}
}
  • @GetMapping("/register") : 入力フォームを表示する
  • @PostMapping("/register") : 入力送信を受け取る
  • @Validated : バリデーションを有効化
  • BindingResult : バリデーション結果を保持。エラーがあればフォームに戻す

👉 ポイント

  • showForm(UserForm form) の引数は、明示的に model.addAttribute("userForm", form) と書かなくてもビューに渡される。
  • Spring MVCの仕様として、フォームオブジェクトはクラス名の先頭を小文字にした名前で自動的にModelへ追加される
    • UserFormuserForm
  • そのため、ビュー側で ${userForm} としてアクセスできる。
  • もし別名を使いたい場合は @ModelAttribute("form") のように指定する。

th:object と th:field を使う場合

<!-- registerForm.html -->
<form th:action="@{/register}" th:object="${userForm}" method="post">
<p>名前: <input type="text" th:field="*{name}" /></p>
<p th:if="${#fields.hasErrors('name')}" th:errors="*{name}"></p>

<p>年齢: <input type="number" th:field="*{age}" /></p>
<p th:if="${#fields.hasErrors('age')}" th:errors="*{age}"></p>

<p>メール: <input type="email" th:field="*{email}" /></p>
<p th:if="${#fields.hasErrors('email')}" th:errors="*{email}"></p>

<button type="submit">登録</button>
</form>
  • th:object="${userForm}" : このフォームが UserForm にバインドされることを示す
  • th:field="*{name}" : userForm.name に対応する。フォーム送信時に値が自動的にセットされる
  • th:errors="*{name}" : name フィールドにエラーがあればメッセージを表示する

th:object / th:field を使わない場合

<!-- registerForm.html -->
<form action="/register" method="post">
<p>名前: <input type="text" name="name" value="[[${userForm.name}]]" /></p>
<p th:if="${#fields.hasErrors('name')}" th:errors="*{name}"></p>

<p>年齢: <input type="number" name="age" value="[[${userForm.age}]]" /></p>
<p th:if="${#fields.hasErrors('age')}" th:errors="*{age}"></p>

<p>メール: <input type="email" name="email" value="[[${userForm.email}]]" /></p>
<p th:if="${#fields.hasErrors('email')}" th:errors="*{email}"></p>

<button type="submit">登録</button>
</form>
  • フィールド名 (name, age, email) を手動で合わせる必要がある
  • 入力値の保持(再表示)を ${userForm.xxx} と書かねばならず、記述が冗長になる
  • エラーメッセージ表示は th:errors を使う点は同じ

th:object / th:field を使うメリット

  • 入力フォームとフォームオブジェクトのフィールドが自動的にバインドされる
  • 入力エラーがあっても、入力値を保持して再表示できる(自動)
  • フィールド名のズレを防げる(Java側で name を変更すれば、自動的にフォームも連動する)

👉 実務では 必ず th:objectth:field を使うのが基本


なぜフロントエンドのバリデーションだけでは不十分なのか

  • フロントエンド(JavaScriptやHTML5)のバリデーションは、ブラウザ上で無効化することが可能
  • 攻撃者が意図的にリクエストを送れば、チェックを回避できてしまう
  • セキュリティやデータの整合性を守るため、必ずサーバサイドでもバリデーションを行う必要がある
フロントエンドのバリデーションだけでは不十分

HTML5の required 属性や type="email" などは、開発者ツールから簡単に削除・改ざんできる。 また、curlコマンドなどで直接HTTPリクエストを送れば、ブラウザのバリデーションは完全にスキップされる。

フロントエンドのバリデーションは「ユーザーに優しい補助機能」であり、最後の砦はサーバ側である。


よくある質問

Q. @NotNull@NotBlank はどちらを使えばよいですか?

A. 文字列フィールドの入力必須チェックには @NotBlank を使うのが適切である。 @NotNull は null チェックのみのため、空文字 "" や空白のみの文字列 " " は通過してしまう。 @NotBlank は null・空文字・空白のみすべてをエラーとする。


Q. バリデーションのエラーメッセージを日本語にしたい場合はどうすればよいですか?

A. アノテーションの message 属性に日本語を設定するか、ValidationMessages.properties ファイルを使って一元管理できる。

@NotBlank(message = "名前は必須です")
private String name;

Q. 入力エラーが発生してもフォームに前の入力値が残るのはなぜですか?

A. th:field="*{name}" を使っているためである。 th:field はフォームオブジェクトのフィールドの値を value 属性に自動セットする。 バリデーション失敗時にコントローラはフォームオブジェクトをそのままビューに返すため、 入力値が保持されて再表示される。


Q. 相関項目チェックで @AssertTrue を使うとき、メソッド名はなぜ is で始めるのですか?

A. Springのバリデーション機能はJavaBeans仕様に基づいており、@AssertTrue を付けたメソッドは 「boolean型のgetter」として扱われる必要がある。 Javaのgetterの命名規則として、boolean 型の場合は isXxx() の形式にする必要がある。


本章のまとめ

  • バリデーションは入力値の正しさを保証するために必要
  • 単項目チェック(@NotBlank, @Max, @Email など)と相関項目チェック(@AssertTrue)がある
  • コントローラでは @Validated を付与し、BindingResult で結果を受け取る
  • BindingResult@Validated を付けたフォームオブジェクトの 直後 に書く
  • フォームは th:objectth:field で定義し、#fields を使ってエラーメッセージを表示する
  • フロントエンドのバリデーションだけでは不十分。サーバサイドで必ず検証を行うこと
  • 次章では、SpringのDI(依存性注入)の仕組みを学ぶ

Webアプリケーションにおける「バリデーション」の説明として、最も適切なものを選べ。

正解

D. ユーザーが入力したデータが正しい形式・範囲・条件を満たしているか確認する処理のこと

解説

バリデーション(入力値検証)とは、ユーザーが入力したデータが期待する条件を満たしているか確認する処理である。

バリデーションで確認する主な項目:

  • 必須チェック: 入力が空でないか
  • 形式チェック: メールアドレスの形式、日付の形式など
  • 範囲チェック: 数値が指定の範囲内か
  • 長さチェック: 文字列の長さが制限内か

不正なデータをアプリケーションに取り込まないようにする重要な処理である。

クライアントサイドバリデーションとサーバーサイドバリデーションの違いとして、正しいものを選べ。

正解

A. クライアントサイドはブラウザ上でJavaScriptが即座に実行し、サーバーサイドはサーバー上でJavaが実行するバリデーションである

解説

クライアントサイドとサーバーサイドのバリデーションの違い:

クライアントサイドバリデーション(ブラウザ上):

  • JavaScriptやHTML5のバリデーション属性(requiredpatternなど)で実行
  • ページ遷移なしに即座にフィードバックできる(UXが良い)
  • しかしブラウザの開発ツールで迂回できるため、セキュリティ上は不十分

サーバーサイドバリデーション(サーバー上):

  • Springの @NotBlank@Email などのアノテーションで実行
  • 迂回することができないため、必ず実施する必要がある

両方を組み合わせることが理想的である。

SpringのバリデーションアノテーションとしてC, D, Eの役割に関する説明として、正しいものを選べ。

正解

C. `@NotBlank`は空文字・空白のみの文字列を禁止し、`@Min`/`@Max`は数値の範囲チェック、`@Email`はメール形式チェックに使う

解説

Java Bean Validation(Jakarta Validation)の主要なアノテーション:

アノテーション用途
@NotNullnull禁止
@NotBlanknull・空文字・空白のみを禁止(文字列用)
@NotEmptynull・空コレクションを禁止
@Size(min, max)文字列長・コレクションサイズの範囲チェック
@Min(value)数値の最小値チェック
@Max(value)数値の最大値チェック
@Emailメールアドレス形式チェック
@Pattern(regexp)正規表現による形式チェック

SpringMVCにおける BindingResult の役割として、最も適切なものを選べ。

正解

B. バリデーション結果(エラー情報)を保持し、エラーの有無やメッセージを確認できるオブジェクト

解説

BindingResult はバリデーションの結果(エラー情報)を保持するオブジェクトである。

使い方:

@PostMapping("/register")
public String register(@Validated UserForm form, BindingResult result) {
    if (result.hasErrors()) {
        // エラーあり → 入力フォームに戻る
        return "register/form";
    }
    // エラーなし → 登録処理
    return "redirect:/register/complete";
}

重要:BindingResult はバリデーション対象の引数(@Validated が付いた引数)の直後に記述する必要がある。

フォームオブジェクトの name フィールドに必須チェックを設定せよ。

private String name;

解答例
@NotBlank private String name;
解説

@NotBlank は文字列フィールドに対して、null・空文字("")・空白のみ(" ")を禁止するアノテーションである。

  • @NotNull: nullのみ禁止(空文字は許可)
  • @NotEmpty: nullと空文字を禁止(空白のみは許可)
  • @NotBlank: null・空文字・空白のみをすべて禁止(最も厳格)

フォームの必須入力チェックには @NotBlank を使うのが一般的である。

フォームオブジェクトの quantity フィールドに「1以上100以下」の範囲チェックを設定せよ。

private int quantity;

解答例
@Min(1) @Max(100) private int quantity;
解説

@Min(value) は数値の最小値チェック、@Max(value) は最大値チェックのアノテーションである。

複数のアノテーションを重ねることができる。

数値の範囲チェックには @Range(min, max) を使う方法もある(Hibernate Validatorの拡張)。

フォームオブジェクトの email フィールドにメールアドレス形式チェックを設定せよ。

private String email;

解答例
@Email private String email;
解説

@Email はメールアドレス形式(xxx@xxx.xxx)かどうかを検証するアノテーションである。

@Email だけでは空文字を許可してしまうため、必須チェックと組み合わせる場合は @NotBlank も付ける。

@NotBlank
@Email
private String email;

コントローラのフォーム受け取りメソッドでバリデーションを実行するコードを完成させよ。

@PostMapping("/register") public String register(
UserForm form, BindingResult result) { if (result.hasErrors()) { return "register/form"; } return "redirect:/register/complete"; }

解答例
@PostMapping("/register") public String register(@Validated UserForm form, BindingResult result) { if (result.hasErrors()) { return "register/form"; } return "redirect:/register/complete"; }
解説

@Validated(または @Valid)をコントローラのメソッドの引数に付けると、その引数に対するバリデーションが実行される。

  • @Validated: Spring独自のアノテーション(グループ検証も可能)
  • @Valid: Jakarta標準のアノテーション(シンプルな場合はどちらでも可)

バリデーション結果は BindingResult で受け取る(@Validated の引数の直後に記述する)。

バリデーションエラーを受け取り、エラーがあれば入力フォームに戻るコントローラを完成させよ。

@PostMapping("/register") public String register(@Validated UserForm form,
result) { if (
) { return "register/form"; } return "redirect:/register/complete"; }

解答例
public String register(@Validated UserForm form, BindingResult result) { if (result.hasErrors()) { return "register/form"; } }
解説

BindingResult はバリデーションエラーの情報を保持するオブジェクトである。

主なメソッド:

  • result.hasErrors(): エラーが1つでもある場合に true を返す
  • result.getFieldErrors(): フィールドごとのエラーリストを取得する
  • result.getAllErrors(): すべてのエラーリストを取得する

BindingResult は必ず @Validated が付いた引数の直後に配置する。

name フィールドのバリデーションエラーメッセージを表示するHTMLを完成させよ(th:object="${userForm}" が設定されているフォーム内)。

<span
>エラーメッセージ</span>

解答例
<span th:errors="*{name}">エラーメッセージ</span>
解説

th:errors="*{フィールド名}" を使うと、そのフィールドのバリデーションエラーメッセージを表示できる。

th:object と組み合わせて使うことが多い:

<form th:object="${userForm}">
  <input type="text" th:field="*{name}">
  <span th:errors="*{name}">名前のエラー</span>
</form>

エラーがない場合は th:errors の要素が表示されない。

@AssertTrue アノテーションを使った相関チェックの説明として、正しいものを選べ。

正解

D. `@AssertTrue` はHTMLフォームのボタンがクリックされたことを確認するアノテーションである

解説

@AssertTrue はboolean型のフィールドやメソッドが true であることを検証するアノテーションである。

フォームオブジェクト内にメソッドとして定義することで、複数フィールドを組み合わせた相関チェックができる。

public class PasswordForm {
    private String password;
    private String confirmPassword;

    @AssertTrue(message = "パスワードが一致しません")
    public boolean isPasswordMatch() {
        return password != null && password.equals(confirmPassword);
    }
}

相関チェックは @AssertTrue の他にも、カスタムバリデーターを作成する方法もある。

バリデーションエラー時に入力値を保持するフォーム入力フィールドを完成させよ(th:object="${userForm}" が設定されているフォーム内)。

<input type="text"
>

解答例
<input type="text" th:field="*{name}">
解説

Thymeleafの th:field="*{フィールド名}" を使うと、バリデーションエラー時に入力値が保持される。

th:field は以下を自動設定する:

  • id 属性
  • name 属性
  • value 属性(フォームオブジェクトの値)

コントローラ側では、バリデーションエラー時にフォームオブジェクトをモデルに渡す(Springが自動で行う)。