Java String replaceAll Regex: Syntax and Pitfalls
java string replaceall regex: How Java's String.replaceAll() interprets regex patterns, handles escaping and group references, and when to reuse compiled Pattern insta...
Java's String.replaceAll() method is the standard way to apply a regex-based substitution to a string. The java string replaceall regex question usually comes down to one misunderstanding: the first argument is a regular expression, not a literal substring. This distinction drives everything else about the method, from escaping rules to replacement behavior.
How replaceAll Parses Its Arguments
String.replaceAll(String regex, String replacement) compiles regex as a regular expression and replaces every match with replacement. The key distinction from String.replace() is that both arguments are interpreted specially: the first as a regex pattern, the second as a replacement string where $ and \ have meaning.
String input = "order-2024-001"; String result = input.replaceAll("\\d+", "[num]"); // result: "order-[num]-[num]"
The \\d+ in Java source becomes the regex \d+, matching one or more digits. If you passed "\\d+" to replace() instead, it would look for the literal characters backslash-d-plus and find nothing.
This distinction matters in production code. A common bug is migrating a replace() call to replaceAll() without checking whether the search string contains regex metacharacters. Characters like ., *, +, ?, (, ), [, ], {, }, |, ^, and $ all change meaning when the first argument is treated as a pattern.
Escaping Regex Metacharacters
When the search text contains literal metacharacters, each one must be escaped with a backslash, and because Java string literals treat backslash as an escape character, the source code needs a double backslash.
String filename = "report.final.txt"; String result = filename.replaceAll("\\.", "_"); // result: "report_final_txt"
The string "\\." in Java source represents the two-character regex \., which matches a literal dot. Forgetting the escape produces ".", a regex that matches any single character, so "report.final.txt".replaceAll(".", "_") returns "________________" — every character replaced.
For input that is genuinely dynamic, such as user-supplied search terms, escaping every metacharacter manually is error-prone. Pattern.quote() wraps the input so it is treated as a literal:
String literal = Pattern.quote(userInput); String result = text.replaceAll(literal, replacement);
This is the safer path when the search text is not known at compile time.
Using Capturing Groups in the Replacement
The replacement string can reference capturing groups from the pattern using $1, $2, and so on. Group 0 refers to the entire match.
String isoDate = "2024-11-03"; String usDate = isoDate.replaceAll("(\\d{4})-(\\d{2})-(\\d{2})", "$2/$3/$1"); // result: "11/03/2024"
The replacement string "$2/$3/$1" reorders the captured groups. This is one of the most useful patterns for log normalization, data migration, and format conversion.
Named groups are also supported when the pattern uses (?<name>...) syntax:
String result = isoDate.replaceAll( "(?<year>\\d{4})-(?<month>\\d{2})-(?<day>\\d{2})", "${month}/${day}/${year}" );
Note the ${name} syntax in the replacement string. A plain $name is not valid.
Handling Backslashes and Dollar Signs in Replacement Text
The replacement string is not a plain literal. A backslash in the replacement escapes the next character, and a dollar sign introduces a group reference. To produce a literal backslash in the output, write \\ in the replacement; to produce a literal dollar sign, write \$.
String price = "Total: 100"; String result = price.replaceAll("100", "\\$100"); // result: "Total: $100"
The replacement string "\\$100" in Java source becomes the replacement \$100, which the matcher interprets as a literal $ followed by 100.
When the replacement text comes from user input or configuration, escaping every $ and \ manually is fragile. Matcher.quoteReplacement() handles it:
String replacement = Matcher.quoteReplacement(userProvidedReplacement); String result = text.replaceAll(pattern, replacement);
This is the recommended approach whenever the replacement is not a hard-coded constant.
Performance: Reusing Compiled Patterns
Every call to replaceAll() compiles the regex from scratch. For a one-off operation that cost is negligible, but in a loop that processes thousands of records, repeated compilation and allocation adds up.
// Compiles the pattern on every call for (String line : lines) { cleaned = line.replaceAll("\\s+", " "); } // Compiles once, reuses for every line Pattern whitespace = Pattern.compile("\\s+"); for (String line : lines) { cleaned = whitespace.matcher(line).replaceAll(" "); }
The second version parses the pattern once and reuses the compiled Pattern object. The Matcher instances created by matcher() are lightweight and can be discarded after each use. If the same pattern is evaluated frequently, this is the pattern to use.
For patterns that are constant, consider a static final field:
private static final Pattern TRAILING_WHITESPACE = Pattern.compile("\\s+$");
This also documents the pattern's purpose at the declaration site.
Building Complex Replacements with appendReplacement
replaceAll() applies one replacement string to every match. When the replacement must depend on the matched content itself, Matcher.appendReplacement() and appendTail() give finer control.
Pattern hex = Pattern.compile("#([0-9a-fA-F]{6})"); Matcher m = hex.matcher(css); StringBuffer out = new StringBuffer(); while (m.find()) { String hexValue = m.group(1); m.appendReplacement(out, Matcher.quoteReplacement("rgb(" + hexToRgb(hexValue) + ")")); } m.appendTail(out);
Each appendReplacement() call copies the text from the end of the previous match up to the current match, then appends the computed replacement. appendTail() copies the remaining text after the last match. This is the standard way to build a transformed string when the replacement logic is too complex for a single replacement expression.
Note that appendReplacement() still interprets $ and \ in its replacement argument, which is why the computed replacement is passed through quoteReplacement().
When replaceAll Throws Exceptions
Two exceptions are worth knowing about. A PatternSyntaxException is thrown when the regex is invalid:
text.replaceAll("[unclosed", "x"); // PatternSyntaxException
This is a subclass of IllegalArgumentException, so it is unchecked. The exception message includes the pattern, the index of the error, and a visual indicator showing where the parse failed, which helps during debugging.
A NullPointerException is thrown if either argument is null. This is often the result of an unvalidated configuration value or a missing field in a deserialized object. If the regex or replacement comes from external input, validate it before calling replaceAll().