JavaのreplaceとreplaceAllの違いは「探す文字列を正規表現として扱うかどうか」で、replaceは書いたとおりの文字列を、replaceAllは正規表現に一致する部分を、どちらもすべて置き換えます。 名前に All が付かない replace も全件置換です。最初の1件だけを置き換えたいときは replaceFirst を使います。

この記事では、3つのメソッドを 「正規表現か」「置換回数」「引数の型」 で比較し、. | ( $ \ といった特殊文字を replaceAll に渡したときの動きと例外を、Java 21.0.10 の実行結果で再現します。直し方として Pattern.quote と Matcher.quoteReplacement の使い方を示し、空白・改行・全角スペースの削除や、後方参照 $1 を使う置換のコピペ集も付けました。主なエラーメッセージは Java 8・11・17 でも同じになることを確かめています。

replace・replaceAll・replaceFirstの早見表

固定の文字列を置き換えるだけなら replace を選ぶのが基本です。特殊文字を気にせず書けて、処理も軽くなります。

メソッド 第1引数の扱い 置換回数 引数の型 第2引数の $ と \
replace(char, char) 文字そのまま すべて char, char そのまま
replace(CharSequence, CharSequence) 文字列そのまま すべて String や StringBuilder など そのまま
replaceAll(String, String) 正規表現 すべて String, String 特別な意味を持つ
replaceFirst(String, String) 正規表現 最初の1件 String, String 特別な意味を持つ

やりたいことから探すときは、次の表を使ってください。

やりたいこと 書き方 入力 → 結果
固定の文字列をすべて置換 s.replace("apple", "orange") apple,banana,apple → orange,banana,orange
最初の1件だけ置換 s.replaceFirst("apple", "orange") apple,banana,apple → orange,banana,apple
記号を含む文字列を置換 s.replace(".", "-") a.b.c → a-b-c
文字列を削除 s.replace("-", "") 2026-10-03 → 20261003
空白類をすべて削除 s.replaceAll("\\s", "") 半角スペース・タブ・改行を削除
全角スペースも含めて削除 s.replaceAll("[\\s ]", "") 全角スペースも削除
改行を削除 s.replaceAll("\\R", "") \n・\r\n・\r のどれも削除
数字以外を削除 s.replaceAll("[^0-9]", "") TEL: 03-1234-5678 → 0312345678
一致した部分を並べ替える s.replaceAll("(\\d{4})-(\\d{2})-(\\d{2})", "$3/$2/$1") 2026-10-03 → 03/10/2026
変数の文字列を正規表現の中で安全に使う s.replaceAll(Pattern.quote(target), Matcher.quoteReplacement(to)) 特殊文字をそのまま扱う

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Java文字列置換 replaceとreplaceAllの違いと特殊文字の注意点」

確認に使った環境

項目 内容
主な実行環境 OpenJDK 21.0.10(Linux)。java -Dstdout.encoding=UTF-8 ファイル名.java の単一ファイル実行
追加で実行した版 OpenJDK 1.8.0_504、11.0.32.1、17.0.20.1(ドット・縦棒・丸かっこ・ドル記号・バックスラッシュの動きと例外メッセージが同じになることを確認)
未実行 Java 22 以降

仕様については、Java SE 21 の API ドキュメント(String クラス)の replace、replaceAll、replaceFirst の項を開いて確認しました。replaceAll の説明には、置換文字列の中のバックスラッシュとドル記号は文字そのままとして扱った場合と異なる結果になることがあり、任意の文字列をそのまま置換に使いたいときは Matcher.quoteReplacement を使うよう書かれています。

3つのメソッドの違いを実行結果で確認する

同じ入力に3つのメソッドを使って、結果を比べます。

public class ReplaceBasic {
    public static void main(String[] args) {
        String fruits = "apple,banana,apple";
        System.out.println(fruits.replace("apple", "orange"));
        System.out.println(fruits.replaceAll("apple", "orange"));
        System.out.println(fruits.replaceFirst("apple", "orange"));

        String s = "a.b.c a.b.c";
        System.out.println(s.replace(".", "-"));
        System.out.println(s.replaceAll(".", "-"));
        System.out.println(s.replaceFirst(".", "-"));
    }
}
orange,banana,orange
orange,banana,orange
orange,banana,apple
a-b-c a-b-c
-----------
-.b.c a.b.c

前半の3行からわかるとおり、replace も replaceAll も、一致した箇所をすべて置き換えます。 記号を含まない文字列なら、2つの結果は同じです。

違いが出るのは後半です。. は正規表現で「任意の1文字」を表すため、replaceAll(".", "-") は 11文字すべて を - に変えました。replaceFirst(".", "-") は先頭の1文字 a を置き換えています。ドットそのものを置き換えたいときは、文字どおりに扱う replace(".", "-") が合います。

名前と動きの対応

名前から受ける印象 実際の動き
replace は1件だけ置換しそう すべて置換する(文字列そのまま)
replaceAll だけが全件置換しそう すべて置換する点は replace と同じ。違いは正規表現を使うこと
replaceFirst は replace の1件版に見える 正規表現で探して最初の1件を置換する

JavaScript の replace は文字列を渡すと最初の1件だけを置き換えるため、他の言語から来た方は特にこの違いを意識しておくと混乱しません。

引数の型と、元の文字列が変わらないこと

public class ReplaceTypes {
    public static void main(String[] args) {
        String s = "a.b.c";

        // char 版:1文字を1文字に置き換える
        System.out.println(s.replace('.', '-'));

        // CharSequence 版:StringBuilder も渡せる
        StringBuilder to = new StringBuilder("--");
        System.out.println(s.replace(".", to));

        // 元の文字列は変わらない(結果を変数で受け取る)
        s.replace(".", "-");
        System.out.println(s);

        // 一致しないときは同じインスタンスが返る
        System.out.println(s.replace("x", "y") == s);

        // 空文字を探すと、文字の間すべてに挿入される
        System.out.println("abc".replace("", "-"));

        // 重なる一致は左から順に消費される
        System.out.println("aaa".replace("aa", "b"));

        // 置換後の文字列に null は渡せない
        try {
            String replacement = null;
            System.out.println("abc".replace("a", replacement));
        } catch (NullPointerException e) {
            System.out.println("NullPointerException");
        }
    }
}
a-b-c
a--b--c
a.b.c
true
-a-b-c-
ba
NullPointerException
  • String は変更できない型なので、s.replace(...) を呼ぶだけでは s は変わりません。s = s.replace(...) のように受け取ります。
  • char 版は「1文字 → 1文字」専用です。1文字を削除したいときは、文字列版で s.replace("-", "") と書きます(空の char リテラル '' は書けないため)。
  • 置換後の文字列に null を渡すと NullPointerException になります。値がないときは空文字 "" に置き換えてから渡します。

replaceAllで特殊文字を使うときの注意点

正規表現には、特別な意味を持つ文字(メタ文字)があります。. | ( ) [ ] { } + * ? ^ $ \ がその代表です。これらを replaceAll や replaceFirst の第1引数にそのまま書くと、意図と違う結果になったり、例外になったりします。

import java.util.function.Supplier;

public class RegexTrap {
    static void test(String label, Supplier<String> action) {
        try {
            System.out.println(label + " -> " + action.get());
        } catch (RuntimeException e) {
            String firstLine = e.getMessage().split("\n")[0];
            System.out.println(label + " -> " + e.getClass().getName() + ": " + firstLine);
        }
    }

    public static void main(String[] args) {
        // ドット:任意の1文字
        test("a.b.c  replaceAll(\".\", \"-\")  ", () -> "a.b.c".replaceAll(".", "-"));
        // 縦棒:「または」。両側が空なので空文字に一致する
        test("a|b|c  replaceAll(\"|\", \",\")  ", () -> "a|b|c".replaceAll("|", ","));
        // 丸かっこ:グループの始まり
        test("f(x)   replaceAll(\"(\", \"[\")  ", () -> "f(x)".replaceAll("(", "["));
        test("f(x)   replaceAll(\")\", \"]\")  ", () -> "f(x)".replaceAll(")", "]"));
        // ドル記号:行末
        test("100$   replaceAll(\"$\", \"!\")  ", () -> "100$".replaceAll("$", "!"));
        // バックスラッシュ:エスケープの始まり
        test("C:\\dir replaceAll(\"\\\\\", \"/\") ", () -> "C:\\dir".replaceAll("\\", "/"));
        // そのほかの記号
        test("a[0]   replaceAll(\"[\", \"(\")  ", () -> "a[0]".replaceAll("[", "("));
        test("1+1    replaceAll(\"+\", \"-\")  ", () -> "1+1".replaceAll("+", "-"));
        test("2*3    replaceAll(\"*\", \"x\")  ", () -> "2*3".replaceAll("*", "x"));
        test("what?  replaceAll(\"?\", \"!\")  ", () -> "what?".replaceAll("?", "!"));
        test("a{1}   replaceAll(\"{\", \"<\")  ", () -> "a{1}".replaceAll("{", "<"));
        test("^a^    replaceAll(\"^\", \"-\")  ", () -> "^a^".replaceAll("^", "-"));
    }
}
a.b.c  replaceAll(".", "-")   -> -----
a|b|c  replaceAll("|", ",")   -> ,a,|,b,|,c,
f(x)   replaceAll("(", "[")   -> java.util.regex.PatternSyntaxException: Unclosed group near index 1
f(x)   replaceAll(")", "]")   -> java.util.regex.PatternSyntaxException: Unmatched closing ')'
100$   replaceAll("$", "!")   -> 100$!
C:\dir replaceAll("\\", "/")  -> java.util.regex.PatternSyntaxException: Unescaped trailing backslash near index 1
a[0]   replaceAll("[", "(")   -> java.util.regex.PatternSyntaxException: Unclosed character class near index 0
1+1    replaceAll("+", "-")   -> java.util.regex.PatternSyntaxException: Dangling meta character '+' near index 0
2*3    replaceAll("*", "x")   -> java.util.regex.PatternSyntaxException: Dangling meta character '*' near index 0
what?  replaceAll("?", "!")   -> java.util.regex.PatternSyntaxException: Dangling meta character '?' near index 0
a{1}   replaceAll("{", "<")   -> java.util.regex.PatternSyntaxException: Illegal repetition near index 1
^a^    replaceAll("^", "-")   -> -^a^

結果は大きく2種類に分かれます。

種類 文字 起きること
例外にならず、結果だけが変わる . | $ ^ エラーが出ないので気づきにくい。出力を確かめて見つける
PatternSyntaxException で止まる ( ) [ { + * ? \ 実行時に例外が出るので、メッセージから原因をたどれる

文字ごとの動きと直し方

文字 正規表現での意味 そのまま渡したときの結果 直し方(どちらでも可)
. 任意の1文字 すべての文字が置換される replace(".", "-") / replaceAll("\\.", "-")
| または 文字の間すべてに挿入され、縦棒は残る replace("|", ",") / replaceAll("\\|", ",")
( グループの始まり Unclosed group near index 1 replace("(", "[") / replaceAll("\\(", "[")
) グループの終わり Unmatched closing ')' replace(")", "]") / replaceAll("\\)", "]")
$ 行末 末尾に挿入され、ドル記号は残る replace("$", "!") / replaceAll("\\$", "!")
\ エスケープの始まり Unescaped trailing backslash near index 1 replace("\\", "/") / replaceAll("\\\\", "/")
[ 文字クラスの始まり Unclosed character class near index 0 replace("[", "(") / replaceAll("\\[", "(")
+ * ? 繰り返し Dangling meta character replace("+", "-") / replaceAll("\\+", "-")
^ 行頭 先頭に挿入され、^ は残る replace("^", "-") / replaceAll("\\^", "-")

縦棒の例(,a,|,b,|,c,)は特に気づきにくい動きです。"|" は「空文字 または 空文字」という正規表現になり、すべての文字の間にある空文字に一致するため、カンマが各所に挿入され、縦棒自体は残ります。区切り文字の変換のように固定の文字を置き換える処理は、replace で書くのが確実です。

バックスラッシュは「Java の文字列」と「正規表現」で2回エスケープする

バックスラッシュの数が多くなる理由は、エスケープが2段階あるためです。

置き換えたい文字 Java のソースに書く文字列 実際に正規表現エンジンへ渡る内容
. "\\." \.
$ "\\$" \$
\ 1つ "\\\\" \\

Java の文字列リテラルでは \\ と書いて初めてバックスラッシュ1つになります。正規表現でもバックスラッシュ1つを表すのに \\ が必要なので、ソース上では4つになります。replace なら "\\"(バックスラッシュ1つ)と書くだけで済みます。

public class BackslashReplace {
    public static void main(String[] args) {
        String path = "C:\\dir\\file.txt";
        System.out.println(path);
        System.out.println(path.replace("\\", "/"));
        System.out.println(path.replaceAll("\\\\", "/"));
    }
}
C:\dir\file.txt
C:/dir/file.txt
C:/dir/file.txt

## 置換後の文字列にも特別な文字がある $ と \

見落としやすいのが第2引数です。replaceAll と replaceFirst では、置換後の文字列の中の $ は「グループの参照」、\ は「次の文字をそのまま扱う」という意味を持ちます。

import java.util.function.Supplier;
import java.util.regex.Matcher;

public class ReplacementTrap {
    static void test(String label, Supplier<String> action) {
        try {
            System.out.println(label + " -> " + action.get());
        } catch (RuntimeException e) {
            System.out.println(label + " -> " + e.getClass().getName() + ": " + e.getMessage());
        }
    }

    public static void main(String[] args) {
        String s = "価格:X";

        test("replaceAll(\"X\", \"$100\")  ", () -> s.replaceAll("X", "$100"));
        test("replaceAll(\"X\", \"$\")     ", () -> s.replaceAll("X", "$"));
        test("replaceAll(\"X\", \"$a\")    ", () -> s.replaceAll("X", "$a"));
        test("replaceAll(\"X\", \"${a}\")  ", () -> s.replaceAll("X", "${a}"));
        test("replaceAll(\"X\", \"\\\\$100\")", () -> s.replaceAll("X", "\\$100"));
        test("quoteReplacement(\"$100\")  ", () -> s.replaceAll("X", Matcher.quoteReplacement("$100")));
        test("replace(\"X\", \"$100\")     ", () -> s.replace("X", "$100"));

        test("a/b replaceAll(\"/\", \"\\\\\")    ", () -> "a/b".replaceAll("/", "\\"));
        test("a/b replaceAll(\"/\", \"\\\\n\")   ", () -> "a/b".replaceAll("/", "\\n"));
        test("a/b replaceAll(\"/\", \"\\\\\\\\\")  ", () -> "a/b".replaceAll("/", "\\\\"));
        test("a/b quoteReplacement(\"\\\\\")  ", () -> "a/b".replaceAll("/", Matcher.quoteReplacement("\\")));
        test("a/b replace(\"/\", \"\\\\\")       ", () -> "a/b".replace("/", "\\"));
    }
}
replaceAll("X", "$100")   -> java.lang.IndexOutOfBoundsException: No group 1
replaceAll("X", "$")      -> java.lang.IllegalArgumentException: Illegal group reference: group index is missing
replaceAll("X", "$a")     -> java.lang.IllegalArgumentException: Illegal group reference
replaceAll("X", "${a}")   -> java.lang.IllegalArgumentException: No group with name {a}
replaceAll("X", "\\$100") -> 価格:$100
quoteReplacement("$100")   -> 価格:$100
replace("X", "$100")      -> 価格:$100
a/b replaceAll("/", "\\")     -> java.lang.IllegalArgumentException: character to be escaped is missing
a/b replaceAll("/", "\\n")    -> anb
a/b replaceAll("/", "\\\\")   -> a\b
a/b quoteReplacement("\\")   -> a\b
a/b replace("/", "\\")        -> a\b
置換後の文字列 起きること 理由
$100 IndexOutOfBoundsException: No group 1 $1 が「1番目のグループ」と解釈されるが、パターンにグループがない
$(末尾や単独) IllegalArgumentException: Illegal group reference: group index is missing $ の後ろに番号がない
$a IllegalArgumentException: Illegal group reference $ の後ろが数字でも { でもない
${a} IllegalArgumentException: No group with name {a} 名前付きグループ a がパターンにない
バックスラッシュ1つ IllegalArgumentException: character to be escaped is missing \ の後ろにエスケープする文字がない
バックスラッシュ + n anb(バックスラッシュが消えて n だけ残る) \n は「n をそのまま」の意味になる

金額の表記や Windows 形式のパスのように、置換後の文字列に $ や \ が入る可能性があるときは、次の3つのどれかで対応します。

  1. 正規表現がいらないなら replace を使う(第2引数もそのまま扱われる)。
  2. 固定の文字列なら "\\$100" のように $ の前にバックスラッシュを置く。
  3. 変数の値なら Matcher.quoteReplacement(値) で包む。

利用者の入力や設定ファイルから読んだ値を第2引数に渡す場合は、3 の方法にしておくと、どんな値が来ても同じ結果になります。

Pattern.quoteとMatcher.quoteReplacementで直す

Pattern.quote(文字列) は、文字列全体を「文字どおりに扱う正規表現」に変換します。Matcher.quoteReplacement(文字列) は、置換後の文字列の $ と \ を文字どおりに扱える形に変換します。 探す側には quote、置き換える側には quoteReplacement、と対で覚えます。

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class QuoteFix {
    public static void main(String[] args) {
        // 変換後の中身を見る
        System.out.println(Pattern.quote("a.b"));
        System.out.println(Pattern.quote("1+1=(2)"));
        System.out.println(Matcher.quoteReplacement("$1\\"));

        // replaceAll で文字どおりに置換する
        System.out.println("a.b.c".replaceAll(Pattern.quote("."), "-"));

        // replaceFirst で「最初の1件だけ」文字どおりに置換する
        System.out.println("f(x)(y)".replaceFirst(Pattern.quote("("), "["));
        System.out.println("a$b$c".replaceFirst(Pattern.quote("$"), Matcher.quoteReplacement("$$")));

        // 変数に入った文字列を、探す側・置き換える側の両方で安全に使う
        String target = "(税込)";
        String to = "$ 表記";
        String text = "100円(税込) / 200円(税込)";
        System.out.println(text.replaceAll(Pattern.quote(target), Matcher.quoteReplacement(to)));
        System.out.println(text.replace(target, to));
    }
}
\Qa.b\E
\Q1+1=(2)\E
\$1\\
a-b-c
f[x)(y)
a$$b$c
100円$ 表記 / 200円$ 表記
100円$ 表記 / 200円$ 表記

Pattern.quote は、文字列を \Q と \E で囲みます。この2つに挟まれた部分は、正規表現のメタ文字もすべて文字どおりに扱われます。Matcher.quoteReplacement は、$ と \ の前にバックスラッシュを付けています。

どれを使うかの判断表

場面 選ぶ書き方 理由
固定の文字列をすべて置換 replace(a, b) 特殊文字を気にせず書ける
固定の文字列を 最初の1件だけ 置換 replaceFirst(Pattern.quote(a), Matcher.quoteReplacement(b)) replace には1件だけの形がない
パターン(数字の並び、空白類など)で置換 replaceAll(正規表現, b) 正規表現が必要
正規表現の一部に変数の文字列を埋め込む "^" + Pattern.quote(prefix) + "\\d+" 変数の中の記号を文字どおりにできる
置換後の文字列が変数($ や \ を含むかもしれない) Matcher.quoteReplacement(b) 例外や意図しない参照を避けられる
一致した部分を使って組み立てる replaceAll("(...)", "$1...") 後方参照を使う

「固定の文字列を最初の1件だけ置き換える」 は、replace にも replaceAll にも当てはまらないため、検索されることの多い場面です。上の replaceFirst(Pattern.quote(...), Matcher.quoteReplacement(...)) を覚えておくと、記号を含む文字列でもそのまま使えます。

文字列を削除するコピペ集 空白・改行・全角スペース

削除は「空文字 "" に置き換える」 ことで行います。固定の文字なら replace、種類でまとめて消すなら replaceAll です。

public class DeleteSamples {
    static String show(String s) {
        return "[" + s.replace("\n", "\\n").replace("\r", "\\r").replace("\t", "\\t") + "]";
    }

    public static void main(String[] args) {
        // 入力:半角スペース、タブ、改行(\n と \r\n)、全角スペースを含む
        String s = " a b\tc\n d\r\n e f ";
        System.out.println("入力                : " + show(s));

        System.out.println("半角スペースだけ    : " + show("a b  c".replace(" ", "")));
        System.out.println("空白類(\\s)       : " + show(s.replaceAll("\\s", "")));
        System.out.println("全角スペースも      : " + show(s.replaceAll("[\\s ]", "")));
        System.out.println("(?U)\\s でも同じ    : " + show(s.replaceAll("(?U)\\s", "")));
        System.out.println("全角スペースだけ    : " + show("山田 太郎".replace(" ", "")));
        System.out.println("全角を半角スペースに: " + show("山田 太郎".replace(" ", " ")));
        System.out.println("連続する空白を1つに : " + show("a   b \t c".replaceAll("\\s+", " ")));

        System.out.println("改行(\\R)         : " + show("a\nb\r\nc\rd".replaceAll("\\R", "")));
        System.out.println("replace(\"\\n\") だけ : " + show("a\nb\r\nc".replace("\n", "")));
        System.out.println("改行を空白に        : " + show("a\nb\r\nc".replaceAll("\\R", " ")));
        System.out.println("空行を削除          : " + show("a\n\n\nb\n".replaceAll("(?m)^\\s*\\R", "")));

        System.out.println("先頭の空白          : " + show("   a b  ".replaceAll("^\\s+", "")));
        System.out.println("末尾の空白          : " + show("   a b  ".replaceAll("\\s+$", "")));
        System.out.println("前後(strip)       : " + show("  a b  ".strip()));
        System.out.println("前後(trim)        : " + show("  a b  ".trim()));

        System.out.println("ハイフンを削除      : " + "2026-10-03".replace("-", ""));
        System.out.println("数字以外を削除      : " + "TEL: 03-1234-5678".replaceAll("[^0-9]", ""));
        System.out.println("数字を削除          : " + "abc123def45".replaceAll("\\d", ""));
        System.out.println("末尾のカンマ        : " + "a,b,c,".replaceAll(",$", ""));
        System.out.println("先頭の0             : " + "000123".replaceFirst("^0+", ""));
        System.out.println("先頭の0(全部0なら1つ残す): " + "0000".replaceFirst("^0+(?!$)", ""));
        System.out.println("山かっこのタグ      : " + "<b>太字</b>と<i>斜体</i>".replaceAll("<[^>]+>", ""));
    }
}
入力                : [ a b\tc\n d\r\n e f ]
半角スペースだけ    : [abc]
空白類(\s)       : [abcde f]
全角スペースも      : [abcdef]
(?U)\s でも同じ    : [abcdef]
全角スペースだけ    : [山田太郎]
全角を半角スペースに: [山田 太郎]
連続する空白を1つに : [a b c]
改行(\R)         : [abcd]
replace("\n") だけ : [ab\rc]
改行を空白に        : [a b c]
空行を削除          : [a\nb\n]
先頭の空白          : [a b  ]
末尾の空白          : [   a b]
前後(strip)       : [a b]
前後(trim)        : [ a b ]
ハイフンを削除      : 20261003
数字以外を削除      : 0312345678
数字を削除          : abcdef
末尾のカンマ        : a,b,c
先頭の0             : 123
先頭の0(全部0なら1つ残す): 0
山かっこのタグ      : 太字と斜体

削除でつまずきやすい3つの点

つまずく点 実行結果 こうするとよい
\s で全角スペースが残る [abcde f] [\\s ] と全角スペースを足すか、先頭に (?U) を付ける
replace("\n", "") で \r が残る [ab\rc] \\R(改行全般)を replaceAll で使う
trim() で全角スペースが残る [ a b ] strip()(Java 11 以降)を使う
  • \s が対象にするのは、半角スペース・タブ・改行などの ASCII の空白です。(?U) は「Unicode の定義で判定する」フラグで、付けると全角スペースも \s に含まれます。
  • Windows で作られたテキストの改行は \r\n です。\n だけを消すと \r が残り、画面では見えないのに比較が一致しない、という状況になります。\R は \n・\r\n・\r をまとめて表します。
  • 前後だけを除くなら置換より strip() が簡潔です。途中の空白も消したいときに replaceAll を使います。

後方参照 $1 を使う置換のコピペ集

正規表現の中で丸かっこ ( ) で囲んだ部分は「グループ」になり、置換後の文字列から $1、$2 … で参照できます。$0 は一致した全体です。

import java.util.regex.Pattern;

public class BackrefSamples {
    public static void main(String[] args) {
        String date = "2026-10-03";
        String re = "(\\d{4})-(\\d{2})-(\\d{2})";

        System.out.println(date.replaceAll(re, "$1年$2月$3日"));
        System.out.println(date.replaceAll(re, "$3/$2/$1"));

        // 名前付きグループは ${名前} で参照する
        System.out.println(date.replaceAll("(?<y>\\d{4})-(?<m>\\d{2})-(?<d>\\d{2})", "${m}/${d}/${y}"));

        // 姓と名を入れ替える
        System.out.println("Yamada Taro".replaceAll("(\\w+) (\\w+)", "$2 $1"));

        // $0 は一致した全体
        System.out.println("id=42, n=7".replaceAll("\\d+", "[$0]"));

        // 3桁ごとにカンマを入れる
        System.out.println("1234567".replaceAll("(\\d)(?=(\\d{3})+$)", "$1,"));

        // 連続して重複した単語を1つにする(パターン内の参照は \1)
        System.out.println("the the cat".replaceAll("\\b(\\w+) \\1\\b", "$1"));

        // 末尾4文字以外を伏せる
        System.out.println("ABCD12345678".replaceAll(".(?=.{4})", "#"));

        // キャメルケースをスネークケースにする
        System.out.println("userNameId".replaceAll("([a-z0-9])([A-Z])", "$1_$2").toLowerCase());

        // 大文字と小文字を区別せずに置換する
        System.out.println("Java JAVA java".replaceAll("(?i)java", "Kotlin"));

        // 一致した部分を加工する(Java 9 以降:関数を渡す replaceAll)
        System.out.println(Pattern.compile("\\b[a-z]").matcher("hello java world")
                .replaceAll(m -> m.group().toUpperCase()));
    }
}
2026年10月03日
03/10/2026
10/03/2026
Taro Yamada
id=[42], n=[7]
1,234,567
the cat
########5678
user_name_id
Kotlin Kotlin Kotlin
Hello Java World
書き方 意味 使う場所
$1、$2 1番目、2番目のグループ 置換後の文字列
$0 一致した全体 置換後の文字列
${name} 名前付きグループ (?<name>...) 置換後の文字列
\\1 1番目のグループと同じ内容 正規表現(パターン)の中
(?i) 大文字と小文字を区別しない 正規表現の先頭
(?=...) 「この後ろに…が続く位置」(一致には含めない) 正規表現の中

$1 の直後に数字を続けたいとき

$1 のすぐ後ろに数字を書くと、どこまでが番号かが気になります。実行して確かめます。

import java.util.function.Supplier;

public class GroupNumber {
    static void test(String label, Supplier<String> action) {
        try {
            System.out.println(label + " -> " + action.get());
        } catch (RuntimeException e) {
            System.out.println(label + " -> " + e.getClass().getName() + ": " + e.getMessage());
        }
    }

    public static void main(String[] args) {
        test("$10   ", () -> "ab".replaceAll("(a)(b)", "$10"));
        test("$1\\\\0 ", () -> "ab".replaceAll("(a)(b)", "$1\\0"));
        test("$3    ", () -> "ab".replaceAll("(a)(b)", "$3"));
        test("${1}0 ", () -> "ab".replaceAll("(a)(b)", "${1}0"));
    }
}
$10    -> a0
$1\\0  -> a0
$3     -> java.lang.IndexOutOfBoundsException: No group 3
${1}0  -> java.lang.IllegalArgumentException: capturing group name {1} starts with digit character
  • グループが2つしかないとき、$10 は「$1 と文字 0」として扱われ、a0 になりました。
  • 数字をはっきり区切りたいときは $1\\0(数字の前にバックスラッシュ)と書くと、意図が明確になります。
  • ${1} という書き方は Java では使えず、例外になります。${...} は名前付きグループ専用です。
  • 存在しない番号($3)を参照すると No group 3 になります。

関数を渡す replaceAll でも $ は解釈される

Java 9 以降の Matcher.replaceAll(関数) は、一致ごとに関数の戻り値で置き換えます。この戻り値も、文字列版と同じく $ と \ が解釈されます。

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class FunctionReplace {
    public static void main(String[] args) {
        Pattern p = Pattern.compile("X");
        try {
            System.out.println(p.matcher("価格:X").replaceAll(m -> "$"));
        } catch (IllegalArgumentException e) {
            System.out.println(e.getMessage());
        }
        System.out.println(p.matcher("価格:X").replaceAll(m -> Matcher.quoteReplacement("$")));
    }
}
Illegal group reference: group index is missing
価格:$

関数の中で組み立てた文字列をそのまま返すときは、Matcher.quoteReplacement で包んでおくと安心です。

複数の置換をつなげるときの順番

replace は新しい文字列を返すので、続けて呼び出せます。順番によって結果が変わることがあるため、置換後の文字に次の置換が反応しない順に並べます。

public class ChainReplace {
    public static void main(String[] args) {
        String s = "a<b>&c";

        // & を最初に置換する
        System.out.println(s.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;"));

        // & を最後に置換すると、先に作った &lt; の & まで置換される
        System.out.println(s.replace("<", "&lt;").replace(">", "&gt;").replace("&", "&amp;"));
    }
}
a&lt;b&gt;&amp;c
a&amp;lt;b&amp;gt;&amp;c

2つ目は、先に作った &lt; の & がもう一度置換されて &amp;lt; になっています。置換後の文字列に含まれる文字(ここでは &)を、いちばん先に置換するのがコツです。

速度の目安 replace と replaceAll

12万文字の文字列("a.b,c " を2万回くり返したもの)に含まれるドット2万個を置き換える時間を、200回の平均で測りました。数値は環境や実行のたびに変わるので、傾向を見る目安として読んでください。

import java.util.regex.Pattern;

public class ReplaceTiming {
    public static void main(String[] args) {
        String big = "a.b,c ".repeat(20000);
        Pattern p = Pattern.compile("\\.");
        for (int round = 0; round < 3; round++) {
            long t0 = System.nanoTime();
            for (int i = 0; i < 200; i++) big.replace(".", "-");
            long t1 = System.nanoTime();
            for (int i = 0; i < 200; i++) big.replaceAll("\\.", "-");
            long t2 = System.nanoTime();
            for (int i = 0; i < 200; i++) p.matcher(big).replaceAll("-");
            long t3 = System.nanoTime();
            System.out.printf("round%d replace=%.2fms replaceAll=%.2fms precompiled=%.2fms%n",
                    round, (t1 - t0) / 200e6, (t2 - t1) / 200e6, (t3 - t2) / 200e6);
        }
    }
}

Java 21.0.10 で実行したときの出力です(3回くり返し、後ろの回ほど実行時の最適化が効いた値になります)。

回 replace(".", "-") replaceAll("\\.", "-") 事前に Pattern.compile
1回目 0.19 ms 1.09 ms 0.67 ms
2回目 0.06 ms 0.59 ms 0.57 ms
3回目 0.06 ms 0.66 ms 0.59 ms
  • 固定の文字列の置換は replace のほうが軽い結果でした(今回の計測では約10倍の差)。
  • replaceAll は呼び出すたびに正規表現を解釈します。同じパターンを何度も使う処理では、Pattern.compile した結果を定数として持っておく書き方が一般的です。今回のように文字列が長く呼び出し回数が少ない条件では、差は小さめでした。

うまく置換できないときのチェックリスト

症状 確認すること 直し方
置換されていない 戻り値を受け取っているか s = s.replace(...) と代入する
全部の文字が置換された replaceAll に . を渡していないか replace にするか \\. と書く
文字の間に余分な文字が入る replaceAll に | や空文字を渡していないか replace にするか \\| と書く
PatternSyntaxException 第1引数に ( ) [ { + * ? \ がないか replace にするか Pattern.quote で包む
Illegal group reference 第2引数に $ がないか replace にするか Matcher.quoteReplacement で包む
No group 1 第2引数に $1 のような並び($100 など)がないか \\$100 と書くか quoteReplacement を使う
character to be escaped is missing 第2引数の末尾が \ ではないか \\\\ と書くか quoteReplacement を使う
全角スペースが残る \s や trim() を使っていないか [\\s ]、(?U)\\s、strip() を使う
改行が消えきらない \r が残っていないか \\R を使う
1件だけ置換したいのに全部変わる replace を使っていないか replaceFirst を使う(固定文字列は Pattern.quote)

よくある質問

Q. replace と replaceAll のどちらを使えばよいですか? A. 置き換えたい対象が固定の文字列なら replace、パターン(数字の並び、空白類、行頭や行末など)なら replaceAll です。どちらも一致した箇所をすべて置き換えます。replace は記号をそのまま扱えるので、ドットや縦棒、ドル記号を含む文字列でも書いたとおりに動きます。

Q. 最初の1件だけを、正規表現を使わずに置き換える方法はありますか? A. String には固定文字列用の「1件だけ置換」メソッドがないため、s.replaceFirst(Pattern.quote(target), Matcher.quoteReplacement(replacement)) と書きます。target と replacement にどんな記号が入っていても、文字どおりに扱われます。

Q. replaceAll で $ を含む文字列に置き換えると例外になります。 A. 第2引数の $ はグループ参照として解釈されるためです。本文の実行結果のとおり、"$100" では No group 1、"$" では Illegal group reference: group index is missing になります。"\\$100" と書くか、Matcher.quoteReplacement("$100") で包むか、正規表現が不要なら replace に切り替えます。

Q. 全角スペースや改行をまとめて削除するには? A. s.replaceAll("[\\s ]", "") で、半角スペース・タブ・改行・全角スペースをまとめて削除できます。改行だけなら s.replaceAll("\\R", "") が、\n・\r\n・\r のどれにも対応します。前後の空白だけを除くなら、全角スペースにも対応する strip()(Java 11 以降)が簡潔です。

まとめ

  • replace も replaceAll も、一致した箇所をすべて置き換えます。 違いは、第1引数を文字どおりに扱うか、正規表現として扱うかです。
  • 固定の文字列の置換と削除は replace が向いています。特殊文字を気にせず書け、今回の計測では処理も軽い結果でした。
  • replaceAll の第1引数では . | ( ) [ { + * ? ^ $ \ が特別な意味を持ちます。ドットと縦棒は例外にならず結果だけが変わるため、出力を確かめます。
  • 第2引数でも $ と \ が特別な意味を持ち、No group 1 や Illegal group reference の原因になります。
  • 探す側は Pattern.quote、置き換える側は Matcher.quoteReplacement で包むと、変数の文字列を文字どおりに扱えます。
  • 全角スペースは [\\s ]、改行は \\R で削除できます。前後だけなら strip() が簡潔です。
  • 後方参照は $1、名前付きは ${name}、全体は $0 で、日付の並べ替えや桁区切りに使えます。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Java文字列置換 replaceとreplaceAllの違いと特殊文字の注意点」

株式会社RJCでは、Javaの文字列置換や正規表現の使い方を仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?