読み込み中...

Javaエスケープ処理の10ステップマスターガイド 初心者向け

Javaエスケープ処理を学ぶ初心者向けのガイドブックの表紙 Java
この記事は約26分で読めます。

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

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

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

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

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

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

はじめに

Javaのエスケープ処理は、文字列リテラル、HTML出力、ファイル読み込み、Webフォームの表示で文字の意味を取り違えないための基礎知識になる。初心者がつまずきやすいのは、"のようなJava構文上のエスケープと、<&のようなHTML向けの変換を同じものとして扱ってしまう点だ。

その違いを整理すると、Javaソース内で文字列を成立させる処理と、ブラウザに安全な文字として渡す処理は目的が異なる。そのため、サンプルコードを写すだけでなく、入力元、出力先、変換対象の文字を合わせて判断する姿勢が必要になる。

動作確認環境
  • Java SE 21 LTS / OpenJDK 21
  • Apache Commons Text 1.12.0 / Jakarta Servlet 6.1
  • HTML Living Standard / Google Chrome 126相当のHTML解釈を前提
📖 この記事で学べること
  • Java文字列リテラルで使う基本的なエスケープ処理
  • HTML表示用に<&を変換する考え方
  • StringBuilderreplaceを使ったサンプルコードの読み方
  • Webアプリケーションで入力値を安全に表示する実装パターン
  • 初心者が混同しやすいJava構文とHTMLエンティティの違い

公式情報はJava SE 21 String APIApache Commons Text StringEscapeUtilsを参照できる。

Javaとは

public class EscapeExample {
    public static void main(String[] args) {
        String text = "彼は"Javaの基本を学ぶ"と言った。";
        System.out.println(text);
    }
}

結果: 期待される出力は彼は"Javaの基本を学ぶ"と言った。です。JavaのStringでは、文字列を囲む"と本文中の引用符を区別するため、本文側の引用符に"を使います。

Javaはクラスを中心にコードを構成するオブジェクト指向言語で、javacでコンパイルした.classJVM上で実行する仕組みを持つ。その仕組みにより、OSごとの差をJVMが吸収しやすく、Webアプリケーション、業務システム、Android関連の学習などで長く使われてきた。

基本的にJavaのソースコードはclasspublicstaticvoidmainなどの構文要素で組み立てる。文字列を扱う場面ではString、繰り返し結合ではStringBuilder、配列引数ではString[]がよく出てくるため、初心者はこれらの役割を先に押さえると読み進めやすい。

Javaの周辺知識は、Java List型完全ガイドJavaアノテーションの12選Javaでうるう年を判定JavaでマスターするオーバーライドJavaのオブジェクト指向で補える。

基本的な特徴

Javaの特徴は、プラットフォーム独立性、オブジェクト指向、ガベージコレクション、標準ライブラリの厚さに整理できる。ただし、どの特徴も単独で成り立つわけではなく、JDKJREJVM、標準APIが組み合わさって開発体験を支えている。

分類主な対象Javaでの例エスケープ処理の観点初心者の注意
文字列引用符"文字列の終端と本文を分ける閉じ引用符と混同しない
文字列バックスラッシュ\パスや正規表現で必要1個だけでは構文が崩れる
文字列改行n表示上の行を分けるOSの改行差と分ける
文字列タブtログやTSVで使う空白とは幅が異なる
文字列シングルクォート'charで必要になるStringとの違いを見る
HTML小なり&lt;タグ開始として解釈させないJava構文の<ではない
HTML大なり&gt;タグ終了として扱わせない入力値表示で漏れやすい
HTMLアンパサンド&amp;エンティティの始まりを保護最初に変換する
HTMLダブルクォート&quot;属性値を壊さない属性内では特に確認する
HTMLアポストロフィ&#39;引用符の区切りを避ける&apos;との差を見る
API置換replace単純な文字列変換順序で結果が変わる
API連結append変換後文字列を構築大量結合では+を避ける
API空判定isEmpty空文字を早期返却nullとは別
API配列化toCharArray1文字ずつ確認サロゲートペアに注意
API長さlengthループ条件に使う文字数とコード単位は違う
API文字取得charAt対象文字を判定範囲外参照を避ける
制御分岐switch文字ごとの変換に向くbreak漏れを見る
制御繰り返しfor全文字を走査終了条件を確認する
制御条件if入力値の分岐nullを先に扱う
入出力読み込みBufferedReader行単位で変換文字コードを意識する
入出力ファイルFileReaderテキスト入力元既定文字セットに注意
入出力例外IOException失敗時の処理握りつぶさない
WebリクエストHttpServletRequest入力値を受け取る信頼済みと見なさない
WebレスポンスHttpServletResponseHTMLへ出力する出力直前に変換する
WebパラメータgetParameterフォーム値を読むnullの可能性を見る
WebContent-TypesetContentTypeHTMLとして返す文字セットも検討する
ライブラリHTML4escapeHtml4定番のHTMLエスケープCommons Text側を使う
ライブラリクラスStringEscapeUtils既存実装を利用Commons Lang版は非推奨
設計出力先text/htmlHTML文脈で変換JSONやSQLと混ぜない
設計戻り値return変換済み文字列を返す二重エスケープを避ける

エスケープ処理とは

エスケープ処理とは、特別な意味を持つ文字を、意図した文脈で普通の文字として扱える形に変える処理です。Javaではソースコード上のnt"\のような表記があり、HTMLでは&lt;&gt;のようなエンティティが使われる。

ただし、JavaのエスケープとHTMLのエスケープは同じ目的ではありません。Javaコンパイラに文字列の範囲を誤解させないための処理と、ブラウザにタグとして解釈させないための処理を分けて考えると、サンプルコードの意図が読み取りやすくなる。

💡 Tips: String.translateEscapes()はJava文字列内のエスケープシーケンスを変換するAPIです。HTMLエスケープ用ではないため、<script>の無害化にはescapeHtml4など別の処理を選びます。

エスケープ処理の詳細な作り方

Javaでエスケープ処理を作るときは、変換対象を決めてから実装する。文字列リテラル内の引用符を扱うのか、HTML出力に備えて入力値を変換するのか、ファイルから読んだテキストを画面表示用に整えるのかで、使うAPIと変換ルールが変わる。

詳細な使い方

具体的には、Javaソースの中で"を文字として入れたいなら"、バックスラッシュ自体を入れたいなら\を書く。一方、HTMLへ出す文字列では<&lt;&&amp;へ変えるため、文脈に合わせた変換が必要だ。

サンプルコード1:文字列のエスケープ

その基本になるのが、引用符を文字列本文として扱う書き方です。初心者向けの最小サンプルコードとして、JavaのStringに引用符を含める例を確認します。

public class QuoteEscapeExample {
    public static void main(String[] args) {
        String escapedString = "これは"エスケープ処理"の例です。";
        System.out.println(escapedString);
    }
}

結果: 期待される出力はこれは"エスケープ処理"の例です。です。Javaコンパイラは"を文字列終端ではなく本文中の引用符として扱います。

サンプルコード2:特殊文字のエスケープ

HTMLに文字列を埋め込む場面では、<div>のような入力がタグとして解釈される可能性がある。そのため、Java側でHTMLエンティティに変換してから出力すると、ブラウザはタグではなくテキストとして扱いやすくなる。

public class SpecialCharacterEscape {
    public static void main(String[] args) {
        String originalText = "<div>Javaエスケープ処理</div>";
        String escapedText = originalText
                .replace("&", "&amp;")
                .replace("<", "&lt;")
                .replace(">", "&gt;")
                .replace(""", "&quot;")
                .replace("'", "&#39;");
        System.out.println("エスケープ後のテキスト: " + escapedText);
    }
}

結果: 期待される出力はエスケープ後のテキスト: &lt;div&gt;Javaエスケープ処理&lt;/div&gt;です。replaceを連ねる場合、&を先に変換することで後続のエンティティをさらに変換してしまう事故を避けます。

詳細な対処法

初心者がエスケープ処理で間違えやすいのは、入力された時点で変換するか、出力する直前に変換するかの判断です。一般的には、保存データは元の意味を保ち、HTMLとして表示する直前にHTML向けへ変換する設計のほうが扱いやすい。

サンプルコード3:エスケープのミスを避ける

これを避ける最小の対処法として、入力値を受け取り、HTML表示用の変数を別に作る形がある。元データと表示用データの名前を分けると、Javaコード内で二重エスケープを発見しやすい。

public class EscapeProcess {
    public static void main(String[] args) {
        String original = "Hello <World>";
        String escaped = original.replace("&", "&amp;")
                                 .replace("<", "&lt;")
                                 .replace(">", "&gt;");
        System.out.println("元の文字列: " + original);
        System.out.println("エスケープ処理後の文字列: " + escaped);
    }
}

結果: 期待される出力は元の文字列: Hello <World>エスケープ処理後の文字列: Hello &lt;World&gt;の2行です。Java変数originalescapedを分けることで、処理前後の意味が読み取りやすくなります。

⚠️ 注意: replace("<", "&lt;")を実行したあとでreplace("&", "&amp;")を行うと、生成済みの&lt;まで変換対象になります。順序は仕様として固定しておきますし、ここがポイントです。

サンプルコード4:セキュリティ対策

セキュリティ対策では、Apache Commons Textのorg.apache.commons.text.StringEscapeUtilsを使う。Commons Lang版は非推奨だ。

import org.apache.commons.text.StringEscapeUtils;

public class SecurityEscapeExample {
    public static void main(String[] args) {
        String userInput = "<script>alert('XSS');</script>";
        String safeOutput = StringEscapeUtils.escapeHtml4(userInput);
        System.out.println("安全な出力: " + safeOutput);
    }
}

結果: 期待される出力は安全な出力: &lt;script&gt;alert(&#39;XSS&#39;);&lt;/script&gt;に相当します。厳密なアポストロフィの表現はライブラリのバージョンやHTML仕様の扱いで差が出る場合があります。

詳細な注意点

エスケープ処理の注意点は、変換漏れ、二重変換、文脈違い、過剰な文字列生成に分けて考えられる。JavaのStringは不変なので、短い処理ならreplaceの連鎖でも読みやすいが、大量データを1文字ずつ処理するならStringBuilderが合う場面もある。

サンプルコード5:処理時間の確認

処理時間を確認するサンプルコードは、測定結果そのものより、同じ変換を大量に繰り返したときの構造を読む目的で使う。実行環境により数値は変わるため、期待される出力には固定値を書かない。

public class EscapePerformance {
    public static void main(String[] args) {
        long startTime = System.currentTimeMillis();

        for (int i = 0; i < 100_000; i++) {
            String escapedString = escapeHtml("Javaエスケープ処理テスト<" + i + ">");
        }

        long endTime = System.currentTimeMillis();
        System.out.println("処理時間: " + (endTime - startTime) + "ミリ秒");
    }

    public static String escapeHtml(String input) {
        return input.replace("&", "&amp;")
                    .replace("<", "&lt;")
                    .replace(">", "&gt;")
                    .replace(""", "&quot;")
                    .replace("'", "&#39;");
    }
}

結果: 期待される出力は処理時間: 123ミリ秒のような形式です。数値はCPU、JVMオプション、実行回数、ウォームアップ状況で変わるため、固定の処理時間として扱いません。

サンプルコード6:メモリ効率を意識したエスケープ処理

同様に、1文字ずつ判定する処理ではStringBuilderに結果を積み上げる方法が使える。これにより、変換対象の文字が多い場合でも処理の流れを追いやすく、ルール追加もswitchに集約しやすい。

public class EscapeProcessing {
    public static String escapeHtml(String str) {
        if (str == null) {
            return null;
        }
        StringBuilder escapedStr = new StringBuilder(str.length());
        for (int i = 0; i < str.length(); i++) {
            char c = str.charAt(i);
            switch (c) {
                case '&': escapedStr.append("&amp;"); break;
                case '<': escapedStr.append("&lt;"); break;
                case '>': escapedStr.append("&gt;"); break;
                case '"': escapedStr.append("&quot;"); break;
                case ''': escapedStr.append("&#39;"); break;
                default: escapedStr.append(c);
            }
        }
        return escapedStr.toString();
    }

    public static void main(String[] args) {
        String input = "Hello & Welcome to the <Java> World!";
        String output = escapeHtml(input);
        System.out.println("Original String: " + input);
        System.out.println("Escaped String: " + output);
    }
}

結果: 期待される出力はOriginal String: Hello & Welcome to the <Java> World!Escaped String: Hello &amp; Welcome to the &lt;Java&gt; World!です。StringBuilderは変換後の文字列を段階的に組み立てます。

詳細なカスタマイズ

カスタマイズでは、どの文字をどの出力先向けに変えるのかをルールとして固定する。Javaのメソッド名にescapeHtmlescapeAttributeescapeLogTextのような文脈を含めると、呼び出し側の誤用を減らしやすい。

サンプルコード7:カスタムエスケープ処理

具体的には、<>だけを変換する限定的なメソッドを作れる。対象を絞ったサンプルコードは学習には読みやすいが、実運用のHTML出力では&や引用符も考慮する必要がある。

public class CustomEscape {
    public static String customEscape(String input) {
        if (input == null || input.isEmpty()) {
            return input;
        }

        StringBuilder result = new StringBuilder(input.length());
        for (char c : input.toCharArray()) {
            if (c == '<') {
                result.append("&lt;");
            } else if (c == '>') {
                result.append("&gt;");
            } else {
                result.append(c);
            }
        }
        return result.toString();
    }

    public static void main(String[] args) {
        String text = "<div>Hello Java</div>";
        System.out.println(customEscape(text));
    }
}

結果: 期待される出力は&lt;div&gt;Hello Java&lt;/div&gt;です。customEscape<>だけを置き換えるため、限定用途の例として読みます。

サンプルコード8:エスケープ処理の拡張

その拡張として、HTMLでよく扱う基本文字をまとめて変換するメソッドを作る。Javaのswitch式ではなく従来のswitch文で書くと、古い学習環境でも読みやすい。

public class EscapeProcessingExtension {
    public static String customEscape(String input) {
        if (input == null) {
            return null;
        }

        StringBuilder escapedString = new StringBuilder(input.length());
        for (char c : input.toCharArray()) {
            switch (c) {
                case '<': escapedString.append("&lt;"); break;
                case '>': escapedString.append("&gt;"); break;
                case '&': escapedString.append("&amp;"); break;
                case '"': escapedString.append("&quot;"); break;
                case ''': escapedString.append("&#39;"); break;
                default: escapedString.append(c);
            }
        }
        return escapedString.toString();
    }

    public static void main(String[] args) {
        String originalString = "Hello <World> & 'Java'";
        String escapedString = customEscape(originalString);
        System.out.println("元の文字列: " + originalString);
        System.out.println("エスケープ後の文字列: " + escapedString);
    }
}

結果: 期待される出力は元の文字列: Hello <World> & 'Java'エスケープ後の文字列: Hello &lt;World&gt; &amp; &#39;Java&#39;です。&も変換対象に入るため、HTML本文へ渡す文字列として扱いやすくなります。

エスケープ処理の応用例とサンプルコード

応用例では、ファイル、Webフォーム、ログ、JSON風テキストなど、入力元と出力先が変わる。Javaのエスケープ処理は単なる文字置換ではなく、どの文脈で危険な文字になるかを判断する設計上の作業でもある。

そのため、サンプルコードを読むときは、変換対象の文字、変換のタイミング、出力先の仕様をセットで見るとよい。Servletの入力処理についてはJakarta Servlet HttpServletRequestも一次情報として確認できる。

サンプルコード9:ファイルの読み込みとエスケープ処理

ファイルから読み込んだ文字列をHTMLへ出すなら、行単位で読み取り、表示直前に変換する構成が分かりやすい。try-with-resourcesを使うとBufferedReaderのクローズも自然に扱える。

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class FileEscapeExample {
    public static void main(String[] args) {
        try (BufferedReader br = new BufferedReader(new FileReader("example.txt"))) {
            String line;
            while ((line = br.readLine()) != null) {
                String escapedLine = line.replace("&", "&amp;")
                                         .replace("<", "&lt;")
                                         .replace(">", "&gt;")
                                         .replace(""", "&quot;")
                                         .replace("'", "&#39;");
                System.out.println(escapedLine);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

結果: example.txt<p>Java & HTML</p>という行がある場合、期待される出力は&lt;p&gt;Java &amp; HTML&lt;/p&gt;です。ファイルが存在しない場合はIOExceptionに応じたスタックトレースが出ます。

サンプルコード10:Webアプリケーションでの利用

Webアプリケーションでは、フォーム入力をHttpServletRequestから受け取り、HttpServletResponseへHTMLとして返す流れがある。このとき、入力値をそのまま連結して出力すると、HTMLタグやスクリプト断片がブラウザで解釈される可能性がある。

import java.io.IOException;
import java.io.PrintWriter;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.text.StringEscapeUtils;

@WebServlet("/FormHandler")
public class FormHandler extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String userInput = request.getParameter("userInput");
        String escapedInput = StringEscapeUtils.escapeHtml4(userInput);

        response.setContentType("text/html; charset=UTF-8");
        PrintWriter out = response.getWriter();
        out.println("<html><body>");
        out.println("入力されたデータ: " + escapedInput);
        out.println("</body></html>");
    }
}

結果: フォームに<script>alert('XSS')</script>が入力された場合、期待される表示はスクリプトの実行ではなく、エスケープ済みの文字列表示です。escapeHtml4はHTML本文へ出す直前の変換として使います。

ℹ️ 補足: XSS対策はHTMLエスケープだけで完結しません。属性値、URL、JavaScript内、CSS内では文脈ごとのエンコードが必要になり、入力検証、CSP、テンプレートエンジンの自動エスケープも合わせて設計します。
public static String customEscape(String input) {
    if (input == null) {
        return "";
    }
    return input.replace("&", "&amp;")
                .replace("<", "&lt;")
                .replace(">", "&gt;")
                .replace(""", "&quot;")
                .replace("'", "&#39;");
}

結果: nullが渡された場合は空文字を返し、通常の文字列ではHTMLの基本文字をエスケープした文字列を返す想定です。戻り値の方針をnull維持にするか空文字にするかは、呼び出し側の設計に合わせます。

ログとJSON風テキストでの扱い

ログではHTMLエスケープより、改行やタブを見える文字へ寄せる処理が役立つ場合がある。攻撃対策というより、ログの行構造を壊さないための整形であり、Javaのreplacerntを明示表記に変える考え方だ。

public class LogEscapeExample {
    public static void main(String[] args) {
        String input = "JavanEscapetGuide";
        String safeLog = input.replace("r", "\r")
                              .replace("n", "\n")
                              .replace("t", "\t");
        System.out.println(safeLog);
    }
}

結果: 期待される出力はJavanEscapetGuideです。実際の改行やタブを見える文字列に変えるため、ログを1行単位で読みやすくできます。

まとめ

Javaのエスケープ処理は、ソースコード内の文字列を成立させる処理と、HTMLなどの出力先で安全に表示する処理に分けて理解する。初心者は"\nのようなJava構文から入り、次に&lt;&gt;&amp;のようなHTMLエンティティへ進むと整理しやすい。

そのうえで、HTML本文に出す値にはStringEscapeUtils.escapeHtml4のような既存ライブラリを使い、限定的な学習や要件だけを手書きのサンプルコードで確認する。Javaのコードレビューでは、変換順序、二重エスケープ、文脈違い、非推奨APIの混入を重点的に確認するとよい。

関連記事

著者: Japanシーモア編集部

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

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