読み込み中...

Java入力チェックの基本と実践12選!初心者向けプログラミング徹底ガイド

Javaでの入力チェックの基本と実践的な方法を学ぶイラスト Java
この記事は約40分で読めます。

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

本記事のサンプルコードを活用して機能追加、目的を達成できるように作ってありますので、是非ご活用ください。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を満たす現役のプログラマチームによって監修されています。

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

Javaの入力チェックは、フォームやAPIから受け取った値をそのまま処理しないための設計です。初心者がプログラミングを学ぶ段階でも、文字列、数値、日付、URL、認証情報を確認する流れを早く身につけると、後からセキュリティ対策を継ぎ足す負担を減らせます。

そのため、Javaで入力チェックを扱うときは、Stringの空判定、Patternによる正規表現、PreparedStatementによるSQLインジェクション対策、HTMLエスケープ、CSRFトークンの確認を分けて考えます。サンプルコードは小さく保ち、バリデーションの意図が読み取れる形にしているのが基本です。

動作確認環境
  • Java 21 LTS / OpenJDK 21
  • Jakarta Servlet 6.0 相当のServlet APIを想定
  • Hibernate Validator 8系を使う例ではJakarta Validation 3.0系を想定
📖 この記事で学べること
  • Javaの入力チェックで確認する値と処理場所の切り分け
  • 初心者がつまずきやすい文字列、数値、日付、URLのバリデーション
  • SQLインジェクション、XSS、CSRFを避けるセキュリティ設計
  • サンプルコードを読みながら実践ガイドとして使える判断基準
  • ライブラリや独自アノテーションで入力チェックを拡張する方法

Javaとは

Javaは、クラスベースのオブジェクト指向を中心にしたプログラミング言語です。javacでコンパイルされた.classファイルはJVM上で動作するため、サーバーサイド、業務システム、Android関連、バッチ処理など幅広い領域で利用されています。

一般に、Javaは静的型付けによりintStringbooleanなどの型をコンパイル時に確認できます。この性質は入力チェックとも相性がよく、文字列として受け取った値を数値や日付へ変換する前に、形式と範囲を検査しやすくなるのが目安です。

公式ドキュメントによれば、正規表現の処理にはjava.util.regex.PatternMatcherが使えます。詳細な仕様はOracle Java Pattern APIで確認できます。

基本的な特徴

これらの特徴により、Javaのコードは役割ごとにクラスへ分けやすく、入力チェックもValidatorServiceなどに整理できるのがポイントです。ガベージコレクションがメモリ解放を担う一方で、ConnectionResultSetのような外部リソースは明示的に閉じる設計が求められます。

そのため、Javaの初心者は文法だけでなく、値の入口で何を許可するかを決める考え方も同時に学ぶと理解が進みます。入力チェックは単なるエラー回避ではなく、データの意味を守るバリデーションとして扱うのが自然です。

プログラミング初心者が知っておくべきポイント

初心者がJavaプログラミングを始める場合、ifelsetrycatchreturnの流れを入力チェックと結びつけて覚えると扱いやすくなるのが一般的です。条件分岐は、値が正しいときの処理と、誤っているときの処理を分ける土台になります。

一方、入力チェックをすべて正規表現だけで片づけようとすると、長く読みにくいコードになりがちです。文字列の空判定にはisEmpty()isBlank()、長さの確認にはlength()、数値変換にはInteger.parseInt()を使い、目的に応じて手段を選びます。

対象確認内容主なAPI注意点関連
文字列空、空白、長さisBlank()nullを先に扱うList型の扱い
数値範囲、符号parseInt()例外処理を分けるうるう年判定
メール形式Pattern完全判定を狙いすぎないフォーム
URLスキーム、ホストURI正規表現だけに寄せないリンク
電話番号桁、区切りmatches()国や運用で差が出る会員情報
郵便番号3桁-4桁matches()ハイフン有無を決める住所
全角日本語文字p{}Unicode範囲を確認する氏名
半角ASCII範囲x00制御文字を除く場合があるID
パスワード長さ、文字種matches()保存時はハッシュ化する認証
日付存在する日付LocalDate厳格解析を使う予約
SQLクエリ分離PreparedStatement文字連結を避けるエスケープ処理
XSSHTML出力escapeHtml4()出力先ごとに変える画面表示
CSRFトークンHttpSessionPOSTで検証するフォーム
例外不正値の通知Exception内部情報を出さないログ
Bean Validation宣言的検証@Email依存関係を合わせるアノテーション
独自制約業務ルールConstraintValidator責務を絞るドメイン
空白前後の空白trim()全角空白を考慮する入力補正
ログ異常値の記録Logger個人情報を残しすぎない運用
APIJSON値DTO境界で検証するREST
画面即時フィードバックmessageサーバー側も省かないUX
ファイル名拡張子、長さPathパストラバーサルを避けるアップロード
サイズ上限Content-Length処理前に制限する負荷対策
ID存在確認Optional推測可能性を下げる権限
権限操作可否role入力値だけで判断しない認可
メッセージ利用者向け表現ResourceBundle詳細を出しすぎない多言語
テスト正常、異常、境界JUnit境界値を含める品質
共通化再利用static肥大化を避ける保守
正規化表記ゆれNormalizer保存前に方針を決める検索
暗号秘密値MessageDigestパスワード用途は専用方式認証
設定ルール値propertiesコード固定を避ける運用

入力チェックの重要性

入力チェックは、Javaアプリケーションの入口でデータの形式、範囲、意味をそろえる処理です。セキュリティ面ではSQLインジェクションやXSSを避け、ユーザー体験の面では入力ミスを早く伝える役割を持ちます。

そのため、初心者向けの実践ガイドでも、文字列の長さだけで終わらせず、保存先、表示先、外部連携先を考慮します。Javaの入力チェックは、プログラミングの基礎とセキュリティの考え方を同時に学べる題材です。

セキュリティの観点から見た入力チェック

これを怠ると、ユーザー名欄にSQL断片を入れられたり、コメント欄にスクリプトを混ぜられたりするのが現実的です。JavaではPreparedStatementsetString()executeQuery()などを使って、入力値と命令を分離する設計が一般的です。

公式ドキュメントによれば、JDBCのPreparedStatementは事前コンパイル済みSQL文を表すインターフェースです。仕様はOracle Java PreparedStatement APIで確認できます。

public class InputCheckExample {
    public static void main(String[] args) {
        String userInput = args.length > 0 ? args[0] : "guest";
        String unsafeQuery = "SELECT * FROM users WHERE username='" + userInput + "'";
        String safeQuery = "SELECT * FROM users WHERE username=?";
        System.out.println(unsafeQuery);
        System.out.println(safeQuery);
    }
}

結果: 期待される出力は、文字連結されたSQL文字列と、値を後から渡すための?付きSQL文字列です。実際の接続処理ではprepareStatement()setString()を組み合わせます。

ユーザー体験向上のための入力チェック

一方、入力チェックは攻撃対策だけではありません。メールアドレス欄に全角スペースが入った場合や、日付欄に存在しない日付が入った場合に、何を直せばよいかを伝えることで再入力の負担を減らせます。

このとき、エラーメッセージは内部例外名ではなく、利用者の行動に結びつく表現にすると整理できます。Java側ではIllegalArgumentExceptionや独自例外を使う場合でも、画面へ出す文言は別に管理するほうが扱いやすいです。

import java.util.regex.Pattern;

public class UserExperienceExample {
    public static void main(String[] args) {
        String emailInput = args.length > 0 ? args[0] : "sample@example.com";
        Pattern pattern = Pattern.compile("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$");
        if (pattern.matcher(emailInput).matches()) {
            System.out.println("有効なメールアドレスです");
        } else {
            System.out.println("メールアドレスの形式で入力してください");
        }
    }
}

結果: 期待される出力は、sample@example.comの場合に「有効なメールアドレスです」です。不正な形式を渡した場合は、入力形式を直すための文言が出力されます。

💡 Tips: クライアント側の即時チェックは入力支援として役立ちます。ただし、Javaのサーバー側バリデーションを省くと、APIや改変リクエストから不正値が届く可能性が残りますし、ここがポイントです。

Javaにおける入力チェックの基本

Javaの入力チェックでは、値を受け取る、前処理する、形式を確認する、範囲を確認する、エラーを返す、という順に整理すると見通しがよくなります。初心者はサンプルコードを写すだけでなく、どの段階でnullや空文字を扱っているかを見ると理解しやすくなります。

具体的には、文字列ならisBlank()length()、数値ならtry-catchと大小比較、日付ならDateTimeFormatterを使いると理解できます。Java 8以降ではjava.timeが標準的な日付時刻APIとして使われます。

文字列の入力チェック

文字列の入口では、空文字、空白だけの文字列、長すぎる文字列を分けて考えます。Java 11以降ではisBlank()が使えるため、半角スペースや改行だけの入力も空に近い値として扱えますが、これは押さえたい点です。

public class StringBlankCheck {
    public static void main(String[] args) {
        String input = "Hello World";
        if (input.isBlank()) {
            System.out.println("文字列は空白です");
        } else {
            System.out.println("文字列は入力されています");
        }
    }
}

結果: 期待される出力は「文字列は入力されています」です。input" "に変えると、空白として扱われます。

そのうえで、文字数の上限を決めます。データベースの列長や画面表示の都合がある場合、length()で早めに弾くと後続処理のエラーを減らせますし、これが一つの目安です。

public class StringLengthCheck {
    public static void main(String[] args) {
        String input = "Hello World";
        if (input.length() > 10) {
            System.out.println("文字列は10文字を超えています");
        } else {
            System.out.println("文字列は10文字以内です");
        }
    }
}

結果: 期待される出力は「文字列は10文字を超えています」です。Javaのlength()はUTF-16コードユニット数を返すため、絵文字などを厳密に数える要件では別途検討します。

数値の入力チェック

数値の入力チェックでは、文字列から数値へ変換できるか、変換後の値が許容範囲に入るかを分けます。JavaではInteger.parseInt()が失敗するとNumberFormatExceptionを投げるため、利用者向けのエラーに変換する処理が必要です。

public class PositiveNumberCheck {
    public static void main(String[] args) {
        int number = 5;
        if (number >= 0) {
            System.out.println("数値は0以上です");
        } else {
            System.out.println("数値は負数です");
        }
    }
}

結果: 期待される出力は「数値は0以上です」です。金額や数量の入力チェックでは、0を許可するかどうかも要件として決めます。

public class NumberRangeCheck {
    public static void main(String[] args) {
        int number = 5;
        if (number >= 1 && number <= 10) {
            System.out.println("数値は1から10の範囲内です");
        } else {
            System.out.println("数値は1から10の範囲外です");
        }
    }
}

結果: 期待される出力は「数値は1から10の範囲内です」です。範囲の下限と上限は、MIN_VALUEMAX_VALUEのような定数にすると再利用しやすくなります。

実践!Javaでの入力チェック方法12選

Javaでの入力チェックは、実際の項目ごとに失敗例が変わります。メール、パスワード、日付、URL、電話番号、郵便番号、文字種、SQL、XSS、CSRFを順に見ると、バリデーションが単なる正規表現ではないことが分かりますが、覚えておくと役立つでしょう。

この実践ガイドでは、初心者が読みやすいサンプルコードを中心に、セキュリティ上の注意点も合わせて整理します。Javaのサンプルコードは小さな例ですが、実務寄りのプログラミングではログ、テスト、認可、保存形式も合わせて設計します。

サンプルコード1:メールアドレス形式のチェック

メールアドレスは仕様が広いため、Javaの正規表現だけで完全に判定しようとすると複雑になると覚えるとよいでしょう。一般的な入力フォームでは、空でないこと、@を含むこと、極端に長くないことを確認し、最終的な到達性は確認メールで扱う設計が現実的です。

import java.util.regex.Pattern;

public class EmailValidation {
    public static void main(String[] args) {
        String email = "test@example.com";
        System.out.println(validateEmailAddress(email));
    }

    public static boolean validateEmailAddress(String email) {
        String emailPattern = "^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$";
        return Pattern.compile(emailPattern).matcher(email).matches();
    }
}

結果: 期待される出力はtrueです。validateEmailAddress()は形式だけを返すため、エラー文言は呼び出し側で管理できます。

サンプルコード2:パスワード強度のチェック

パスワードのバリデーションでは、長さ、文字種、よくある弱い文字列の拒否を分けます。ただし、過度に複雑な文字種ルールは利用者の負担を増やすことがあるため、現在は十分な長さと漏えい済みパスワードの回避も検討対象になると考えられます。

public class PasswordStrengthChecker {
    public static void main(String[] args) {
        String password = "Aa1@bcdef";
        boolean isLongEnough = password.length() >= 8;
        boolean hasUppercase = password.matches(".*[A-Z].*");
        boolean hasLowercase = password.matches(".*[a-z].*");
        boolean hasDigit = password.matches(".*\d.*");
        boolean hasSpecialChar = password.matches(".*[!@#\$%^&*].*");

        if (isLongEnough && hasUppercase && hasLowercase && hasDigit && hasSpecialChar) {
            System.out.println("パスワードは基準を満たしています");
        } else {
            System.out.println("パスワードが基準を満たしていません");
        }
    }
}

結果: 期待される出力は「パスワードは基準を満たしています」です。保存時には平文ではなく、パスワードハッシュ用の方式を採用します。

サンプルコード3:日付形式のチェック

日付は見た目の形式だけでなく、存在する日付かどうかも確認します。Javaの古いSimpleDateFormatでも処理できますが、現在のコードではLocalDateDateTimeFormatterを使うほうが扱いやすい場面が多いです。

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;

public class DateCheck {
    public static void main(String[] args) {
        String inputDate = "2023/09/12";
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("uuuu/MM/dd");
        try {
            LocalDate.parse(inputDate, formatter);
            System.out.println("日付の形式が正しいです");
        } catch (DateTimeParseException e) {
            System.out.println("日付の形式が不正です");
        }
    }
}

結果: 期待される出力は「日付の形式が正しいです」です。2023/09/32のような値では不正側のメッセージになります。

日付の形式が正しいです

結果: これは正常な日付文字列を渡した場合に期待される出力例です。出力例はテストケースの期待値として使えます。

日付の形式が不正です

結果: これは存在しない日付や形式違いを渡した場合に期待される出力例です。日付のJavaプログラミングでは境界値のテストが欠かせません。

サンプルコード4:URL形式のチェック

URLの入力チェックでは、正規表現だけでなくURIを使って構文を解析する方法もあると言えるでしょう。外部リンクとして使う場合は、httphttpsだけ許可するなど、用途に応じてスキームを制限します。

import java.net.URI;

public class URLValidator {
    public static void main(String[] args) {
        String url = "https://www.example.com";
        System.out.println("URLが有効かどうか: " + validateURL(url));
    }

    public static boolean validateURL(String url) {
        try {
            URI uri = URI.create(url);
            String scheme = uri.getScheme();
            return ("http".equals(scheme) || "https".equals(scheme)) && uri.getHost() != null;
        } catch (IllegalArgumentException e) {
            return false;
        }
    }
}

結果: 期待される出力は「URLが有効かどうか: true」です。javascript:のような値を許可しない設計にすると、リンク出力時のリスクを下げられます。

サンプルコード5:電話番号形式のチェック

電話番号は国や入力方針によって形式が変わります。日本国内向けの簡易フォームなら、ハイフンありの形式を受け付け、保存前に数字だけへ正規化する設計もあるのが基本です。

public class PhoneNumberValidation {
    public static void main(String[] args) {
        String phoneNumber = "090-1234-5678";
        if (validatePhoneNumber(phoneNumber)) {
            System.out.println("電話番号の形式が正しいです");
        } else {
            System.out.println("電話番号の形式が正しくありません");
        }
    }

    public static boolean validatePhoneNumber(String phoneNumber) {
        return phoneNumber.matches("^0\d{1,4}-\d{1,4}-\d{4}$");
    }
}

結果: 期待される出力は「電話番号の形式が正しいです」です。携帯、固定、フリーダイヤルを同じルールで扱うかは要件で決めます。

サンプルコード6:クレジットカード番号のチェック

クレジットカード番号は、桁や区切りの形式だけでは有効性を判断できません。Javaで入力チェックを書く場合でも、PCI DSSなどの要件に注意し、カード番号を不用意にログへ残さない設計が必要です。

public class CreditCardNumberValidator {
    public static void main(String[] args) {
        String cardNumber = "1234 5678 1234 5678";
        if (isValidCardNumber(cardNumber)) {
            System.out.println("クレジットカード番号は形式上有効です");
        } else {
            System.out.println("クレジットカード番号は形式上無効です");
        }
    }

    public static boolean isValidCardNumber(String cardNumber) {
        return cardNumber.matches("^[0-9]{4} [0-9]{4} [0-9]{4} [0-9]{4}$");
    }
}

結果: 期待される出力は「クレジットカード番号は形式上有効です」です。決済処理ではトークン化や決済代行の仕組みを使うのが一般的です。

サンプルコード7:郵便番号のチェック

郵便番号の入力チェックは、形式が比較的単純なので初心者の練習に向いています。ただし、ハイフンありだけ許可するのか、数字だけを許可して表示時に整えるのかを先に決めます。

public class PostalCodeValidator {
    public boolean validate(String postalCode) {
        return postalCode.matches("^[0-9]{3}-[0-9]{4}$");
    }

    public static void main(String[] args) {
        PostalCodeValidator validator = new PostalCodeValidator();
        System.out.println(validator.validate("123-4567"));
        System.out.println(validator.validate("123-45678"));
    }
}

結果: 期待される出力は1行目がtrue、2行目がfalseです。住所検索APIと連携する場合は、形式チェック後に存在確認を行います。

サンプルコード8:全角文字のチェック

全角文字の入力チェックは、氏名やフリガナなどで使われます。Javaの正規表現ではUnicodeブロックを使えますが、どの文字を許可するかは業務ルールと利用者の入力実態に合わせますし、ここを基本と考えるとよいでしょう。

public class ZenkakuCheck {
    public static void main(String[] args) {
        String input = "こんにちは";
        if (input.matches("^[\p{InCJKUnifiedIdeographs}\p{InHiragana}\p{InKatakana}\p{InHalfwidthAndFullwidthForms}]+$")) {
            System.out.println("全角文字のみで構成されています");
        } else {
            System.out.println("全角文字以外の文字が含まれています");
        }
    }
}

結果: 期待される出力は「全角文字のみで構成されています」です。記号や長音符を許可するかは、入力項目ごとに調整します。

import java.util.Scanner;

public class ZenkakuInputCheck {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.print("文字列を入力してください: ");
        String input = scanner.nextLine();
        if (input.matches("^[\p{InCJKUnifiedIdeographs}\p{InHiragana}\p{InKatakana}\p{InHalfwidthAndFullwidthForms}]+$")) {
            System.out.println("全角文字のみで構成されています");
        } else {
            System.out.println("全角文字以外の文字が含まれています");
        }
        scanner.close();
    }
}

結果: 期待される表示は、入力を促す文言の後に判定結果が出る流れです。Scannerは学習用に分かりやすい一方、Webアプリではリクエストパラメータから値を受け取ります。

サンプルコード9:半角文字のチェック

半角文字だけを許可する入力欄では、IDやコード値などが代表例です。JavaでASCII範囲を確認する場合、^[x20-x7E]+$のように表示可能な範囲へ絞る方法もあります。

public class HalfWidthCharacterChecker {
    public static void main(String[] args) {
        String input = "Hello12345";
        if (isHalfWidthCharacters(input)) {
            System.out.println("入力された文字列は半角文字のみです");
        } else {
            System.out.println("入力された文字列に全角文字が含まれています");
        }
    }

    public static boolean isHalfWidthCharacters(String input) {
        return input.matches("^[\x20-\x7E]+$");
    }
}

結果: 期待される出力は「入力された文字列は半角文字のみです」です。制御文字を含めたくない場合は、x00からではなくx20からにします。

サンプルコード10:SQLインジェクション対策

SQLインジェクション対策では、入力値の文字を削るより、SQL文と値を分離するほうが筋のよい設計です。JavaのJDBCではPreparedStatementを使い、?へ値をバインドします。

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class SQLInjectionPrevention {
    public static void main(String[] args) {
        String url = "jdbc:your-database-url";
        String user = "your-user";
        String password = "your-password";
        String userInput = "alice";
        String sql = "SELECT * FROM users WHERE username = ?";

        try (Connection conn = DriverManager.getConnection(url, user, password);
             PreparedStatement pstmt = conn.prepareStatement(sql)) {
            pstmt.setString(1, userInput);
            try (ResultSet rs = pstmt.executeQuery()) {
                while (rs.next()) {
                    System.out.println("ユーザー名: " + rs.getString("username"));
                    System.out.println("メール: " + rs.getString("email"));
                }
            }
        } catch (SQLException e) {
            System.out.println("データベース処理でエラーが発生しました");
        }
    }
}

結果: 期待される出力は、接続先データベースに該当ユーザーがある場合にユーザー名とメールが表示される形です。接続情報が例示値のままなら、データベース処理のエラーメッセージ側になります。

⚠️ 注意: ユーザー入力をSQL文字列へ連結する方法は避けます。エスケープだけに頼るより、PreparedStatementで命令と値を分離するほうが保守しやすいです。

サンプルコード11:XSS対策

XSS対策では、入力時に危険そうな文字を消すより、HTMLへ出力する直前に文脈に合ったエスケープを行う考え方が中心になるのが目安です。Javaのテンプレートエンジンやフレームワークを使う場合も、自動エスケープの有無を確認します。

public class HtmlEscapeSample {
    public static String escapeHtml(String input) {
        if (input == null) {
            return null;
        }
        return input.replace("&", "&amp;")
                    .replace("<", "&lt;")
                    .replace(">", "&gt;")
                    .replace(""", "&quot;")
                    .replace("'", "&#x27;");
    }
}

結果: 期待される処理は、HTML上で特別な意味を持つ文字がエンティティへ置き換わることです。出力先がJavaScript文字列やURL属性の場合は、別のエスケープが必要になります。

public class HtmlEscapeRun {
    public static void main(String[] args) {
        String userInput = "<script>alert('XSS Attack!');</script>";
        String escapedInput = HtmlEscapeSample.escapeHtml(userInput);
        System.out.println("Escaped Input: " + escapedInput);
    }
}

結果: 期待される出力は、<>がエンティティ化された文字列です。画面表示ではスクリプトとして解釈されにくい形になります。

Escaped Input: &lt;script&gt;alert(&#x27;XSS Attack!&#x27;);&lt;/script&gt;

結果: これはHTMLエスケープ後に期待される出力例です。Apache Commons Textなどのライブラリを使う場合は、利用中のバージョンと依存関係を確認します。

サンプルコード12:CSRF対策

CSRF対策では、ログイン済み利用者のブラウザから意図しないPOSTが送られる状況を想定するのがポイントです。Java Servletで扱う場合、フォーム表示時にトークンを生成し、送信時にHttpSession上の値と照合します。

@WebServlet("/csrftest")
public class CSRFProtectionServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String sessionToken = (String) request.getSession().getAttribute("CSRF_TOKEN");
        String requestToken = request.getParameter("CSRF_TOKEN");

        if (sessionToken != null && sessionToken.equals(requestToken)) {
            response.getWriter().write("request accepted");
        } else {
            response.sendError(HttpServletResponse.SC_FORBIDDEN, "CSRF protection token does not match");
        }
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String csrfToken = UUID.randomUUID().toString();
        request.getSession().setAttribute("CSRF_TOKEN", csrfToken);
        request.setAttribute("CSRF_TOKEN", csrfToken);
        request.getRequestDispatcher("/WEB-INF/jsp/csrftest.jsp").forward(request, response);
    }
}

結果: 期待される動作は、トークンが一致するPOSTだけ処理され、一致しないPOSTには403エラーが返ることです。実際のServletでは必要なimportとフレームワーク設定を追加します。

<form action="csrftest" method="post">
    <input type="hidden" name="CSRF_TOKEN" value="${CSRF_TOKEN}">
    <input type="submit" value="Submit">
</form>

結果: 期待される表示は、CSRFトークンを隠しフィールドに含むフォームです。Javaのサーバー側で生成した値と、送信された値を照合します。

入力チェックの注意点と対処法

入力チェックは、厳しくすればよい処理ではありません。利用者が正しく入力できる範囲を残しながら、アプリケーションの前提を壊す値を止める設計が必要です。

そのため、Javaのバリデーションでは「正規化」「検証」「エラーメッセージ」「ログ」「保存」の順序を意識するのが一般的です。初心者がプログラミングでつまずきやすいのは、入力値を整える処理と、不正値を拒否する処理を混ぜてしまう点です。

余分なスペースの削除

これに対して、前後の空白は保存前に取り除く方針がよく使われます。Javaのtrim()は前後の一部の空白を削除しますが、全角空白まで含めるならstrip()や正規化処理も検討します。

public class TrimExample {
    public static void main(String[] args) {
        String userInput = "  sample@email.com  ";
        String normalized = userInput.trim();
        System.out.println(normalized);
    }
}

結果: 期待される出力はsample@email.comです。メールアドレスやIDでは、前後の空白を削るだけで検索失敗を防げる場合があるのが現実的です。

特殊文字の取り扱い

ただし、特殊文字を一律で削除すると、利用者が入力した正当な文字まで失われる可能性があります。Javaでは保存時に必要な値を残し、表示時やSQL実行時に文脈へ合わせた処理を行うほうが自然です。

import org.apache.commons.text.StringEscapeUtils;

public class EscapeLibraryExample {
    public static void main(String[] args) {
        String userInput = "<script>alert('XSS');</script>";
        String safeInput = StringEscapeUtils.escapeHtml4(userInput);
        System.out.println(safeInput);
    }
}

結果: 期待される出力は、HTMLタグとして解釈されにくいエスケープ済み文字列です。org.apache.commons.lang3.StringEscapeUtilsは非推奨になっているため、org.apache.commons.text.StringEscapeUtilsを使います。

ℹ️ 補足: エスケープ処理は保存前の入力チェックと同じものではありません。入力値の妥当性確認、SQLへのバインド、HTMLへの出力エスケープは役割が異なります。

エラーメッセージの設計

エラーメッセージは、攻撃者に内部構造を教えず、利用者には修正方法が伝わる文言にすると整理できます。Javaの例外クラス名、SQL、スタックトレースを画面へ出すと、セキュリティ上の手がかりになる場合があります。

class InvalidInputException extends Exception {
    InvalidInputException(String message) {
        super(message);
    }
}

public class ErrorMessageExample {
    public static void main(String[] args) {
        try {
            throw new InvalidInputException("invalid email");
        } catch (InvalidInputException e) {
            System.out.println("入力内容を確認してください。メールアドレスの形式で入力してください。");
        }
    }
}

結果: 期待される出力は「入力内容を確認してください。メールアドレスの形式で入力してください。」です。内部ログには詳細、画面には修正しやすい文言という分担にします。

さらに進んで!カスタマイズ方法

Javaの入力チェックは、単体のif文だけでなく、ライブラリや独自制約で拡張できます。フォーム項目が増えるほど、バリデーションルールを宣言的に書ける仕組みが保守に効きますし、ここがポイントです。

一方、ライブラリを入れればすべてのセキュリティ問題が消えるわけではありません。Bean Validationは値の妥当性確認に向いていますが、SQLインジェクション対策、XSS対策、CSRF対策はそれぞれ別の層で扱います。

バリデーションライブラリの利用

Jakarta ValidationやHibernate Validatorを使うと、@NotBlank@Email@Sizeなどでルールを宣言できます。Javaのアノテーションに慣れている場合は、Javaアノテーションの解説と合わせて読むと理解しやすいです。

import jakarta.validation.Validation;
import jakarta.validation.Validator;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;

public class Sample {
    public static class User {
        @NotBlank(message = "メールアドレスを入力してください。")
        @Email(message = "有効なメールアドレスを入力してください。")
        private String email;

        public void setEmail(String email) {
            this.email = email;
        }
    }

    public static void main(String[] args) {
        Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
        User user = new User();
        user.setEmail("不正なメールアドレス");
        validator.validate(user).forEach(violation -> System.out.println(violation.getMessage()));
    }
}

結果: 期待される出力は「有効なメールアドレスを入力してください。」です。現在のJakarta系ではjavax.validationではなくjakarta.validationを使う構成が一般的です。

独自のバリデーションロジックの作成

独自ルールがある場合は、アノテーションとConstraintValidatorを組み合わせます。例えば会員コード、社内ID、予約番号のように、形式だけでなく業務上の意味を持つ値では独自バリデーションが向いています。

import jakarta.validation.Constraint;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import jakarta.validation.Payload;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Constraint(validatedBy = CustomValidator.MyConstraintValidator.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomValidator {
    String message() default "カスタムのバリデーションメッセージ";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};

    class MyConstraintValidator implements ConstraintValidator<CustomValidator, String> {
        @Override
        public boolean isValid(String value, ConstraintValidatorContext context) {
            return value != null && value.matches("^[A-Z]{3}-[0-9]{4}$");
        }
    }
}

結果: 期待される判定は、ABC-1234のような値が有効になり、それ以外が無効になる形です。独自制約は名前から意図が伝わるように分けると、Javaコードの読み手が追いやすくなります。

⚠️ 注意: バリデーションエラーをそのままログへ出すと、個人情報や秘密値が残ることがあります。パスワード、カード番号、トークンはマスクする方針を決めますが、これは押さえたい点です。

具体的には、入力チェックを共通化する場合でも、すべてを巨大なユーティリティクラスへ集めると変更の影響が読みにくくなります。メール用、日付用、認証用、表示用のように責務を分けると、サンプルコードから実務コードへ移しやすくなります。

同様に、Javaの入力チェックはテストとセットで考えますし、これが一つの目安です。正常値、空値、境界値、桁あふれ、形式違い、攻撃文字列を用意すると、プログラミングの学習段階でもバリデーションの抜けを見つけやすくなります。

まとめ

Javaの入力チェックは、データの形式を整えるだけでなく、セキュリティとユーザー体験を支える設計です。文字列、数値、日付、URL、電話番号、郵便番号、文字種、パスワードなど、値の性質ごとに確認方法を選ぶ必要があります。

そのため、初心者がプログラミングを学ぶときは、if文やPatternだけで完結させず、PreparedStatement、HTMLエスケープ、CSRFトークン、Bean Validationまで段階的に押さえるとよいです。サンプルコードを動作の期待値と一緒に読むことで、入力チェックの目的が明確になると理解できます。

一方、実務寄りのJava開発では、バリデーションの失敗理由を画面へどう返すか、ログへ何を残すか、保存前にどこまで正規化するかも設計対象になります。特に押さえたいのは、入力値を信頼しないこと、出力先に合わせてエスケープすること、SQLでは値をバインドすることです。

これらを分けて実装すると、Javaの入力チェックは読みやすく保守しやすいコードになります。実践ガイドとして各サンプルコードを見直し、バリデーションの目的、セキュリティ上の狙い、利用者へのメッセージをそろえることが次の改善につながりますが、覚えておくと役立つでしょう。

関連記事

著者: Japanシーモア編集部

Japanシーモアは、Web/IoT/APP/SYS 分野のプログラミング情報を体系的に提供するメディアです。本記事は編集部による執筆とAI支援を組み合わせて制作し、公開前に編集部が校正しています。誤りや改善案がございましたらお問い合わせよりご連絡ください。

※本記事は実在のエンジニア複数名で構成される Japanシーモア編集部が、AI支援を活用して作成・校正・公開しています。