Difference Between replace() vs replaceAll()
Both replace() and replaceAll() replace all matching occurrences within a string with a replacement sequence.
🟢 Junior Level
Both replace() and replaceAll() replace all matching occurrences within a string with a replacement sequence.
The fundamental distinction lies in how the search target is evaluated:
replace()performs a literal (exact character) replacement. It searches for exact character or substring matches. Metacharacters (such as.,*,+,?,[,],(,)) are treated as ordinary text.replaceAll()performs replacement based on a Regular Expression (Regex). The target string is compiled into a regex pattern, and characters like$and\in the replacement string have special group-referencing semantics.
In a Nutshell (30 Seconds)
The most common interview misconception is assuming that replace() replaces only the first occurrence while replaceAll() replaces all of them. Both methods replace ALL occurrences! If you only need to replace the first matching occurrence, use replaceFirst().
String text = "a.b.c.d";
// 1. Literal replacement: dot '.' is treated as an exact character
System.out.println(text.replace(".", "/"));
// Output: "a/b/c/d"
// 2. Regex replacement: dot '.' matches ANY character in Regex!
System.out.println(text.replaceAll(".", "/"));
// Output: "///////" (every single character replaced by '/')
// 3. Replace only the first occurrence:
System.out.println(text.replaceFirst("\\.", "/"));
// Output: "a/b.c.d"
🟡 Middle Level
Method Signatures and Performance Characteristics
The java.lang.String class provides four replacement methods:
| Method | Target Parameter | Replacement Parameter | Scope | Performance & Mechanism |
|---|---|---|---|---|
replace(char, char) |
Primitive char |
Primitive char |
All occurrences | ⚡ Fastest: Single-pass primitive byte array scan |
replace(CharSequence, CharSequence) |
Literal text | Literal text | All occurrences | 🚀 Very fast: Direct index scan, no regex engine in Java 9+ |
replaceAll(String, String) |
Regular Expression | Replacement string ($ groups) |
All occurrences | 🐢 Slower: Compiles Pattern and traverses Matcher |
replaceFirst(String, String) |
Regular Expression | Replacement string ($ groups) |
First occurrence | 🐢 Slower: Compiles Pattern, stops after first match |
Traps When Using replaceAll()
-
Unescaped Regex Metacharacters in Target: Calling
str.replaceAll("100$", "200$")will not find the literal string"100$", because in regex syntax$signifies the end of a line. For literal matching withreplaceAll, the target must be escaped usingPattern.quote("100$"). -
Crashes Caused by
$in the Replacement String:String price = "Cost: PRICE"; // 💥 RUNTIME CRASH: Throws IllegalArgumentException: No group with name 100! String result = price.replaceAll("PRICE", "$100");In
replaceAll, the$symbol designates back-references to captured regex groups ($1,$2, etc.). When followed by digits, the regex engine expects corresponding capturing parentheses in the search pattern. Solution: When inserting arbitrary text that may contain$or\, sanitize the replacement string viaMatcher.quoteReplacement("$100").
🔴 Senior Level
Implementation Evolution: Java 8 vs Java 9+
Prior to Java 9, replace(CharSequence, CharSequence) carried hidden regex overhead:
- Internally, it delegated to:
Pattern.compile(target.toString(), Pattern.LITERAL).matcher(this).replaceAll(Matcher.quoteReplacement(replacement.toString()))Even though matching was literal (Pattern.LITERAL), the JVM still createdPatternandMatcherinstances and evaluated matches through a finite state automaton.
Starting with Java 9 (including Java 17 and 21):
replace(CharSequence, CharSequence) was completely rewritten without using java.util.regex:
- Fast Failure Check: Checks for the first occurrence using
indexOf(target). - Zero-Allocation Short-Circuit: If the target substring is not found, the method immediately returns
thiswithout allocating a single byte on the heap! - Exact Allocation Sizing: If matches exist, it calculates the exact capacity needed for the resulting array: \(\text{newLength} = \text{oldLength} + \text{matchCount} \times (\text{replacementLength} - \text{targetLength})\)
- Allocates a single
byte[]array of the exact required length (accounting for Latin-1 or UTF-16 coder) and copies segments directly usingSystem.arraycopy. As a result,replace()in modern Java is 5 to 10 times faster thanreplaceAll(), generating substantially less GC pressure.
// Simplified OpenJDK String.java implementation
public String replace(CharSequence target, CharSequence replacement) {
String trgStr = target.toString();
String replStr = replacement.toString();
int i = indexOf(trgStr);
if (i < 0) {
return this; // Zero allocations when target is absent!
}
// Direct byte-level replacement without Pattern or Matcher
return isLatin1()
? StringLatin1.replace(value, trgStr.value, replStr.value)
: StringUTF16.replace(value, trgStr.value, replStr.value);
}
Security Implications: ReDoS (Regular Expression Denial of Service)
Passing untrusted user input directly into replaceAll() introduces a severe ReDoS vulnerability:
- If a malicious user supplies a regex pattern with catastrophic backtracking (e.g.,
(a+)+$), the worker thread will hang at 100% CPU utilization for minutes or hours, leading to server thread pool exhaustion. - The
replace()method is completely immune to ReDoS attacks because it executes a deterministic linear string scan running in $O(N)$ time.
4 Tricky Questions
1. Why does str.replaceAll("PRICE", "$100") throw IllegalArgumentException and how do you fix it?
Answer:
In replaceAll(), the second argument (replacement) is parsed for group capture references: the $ symbol indicates a captured group.
When the regex engine encounters $100, it attempts to look up group number 100. Because the search pattern "PRICE" contains no capturing groups, the JVM throws java.lang.IllegalArgumentException: No group with name 100 (or IndexOutOfBoundsException).
Solutions:
- Use
str.replace("PRICE", "$100")if regex capabilities are not required (safe, literal substitution). - If regex matching is required, wrap the replacement string in
Matcher.quoteReplacement("$100").
2. Does str.replace("target", "replacement") allocate a new object on the heap if "target" is not present in the string?
Answer:
In modern Java (Java 9, 11, 17, 21), no, it does not allocate any new object.
The method first checks indexOf(trgStr). If no match is found (i < 0), it returns the original instance reference this. Evaluating str == str.replace("missing", "new") yields true, incurring zero heap allocation and zero GC overhead.
Contrast: str.replaceAll("missing", "new") always compiles a Pattern and instantiates a Matcher, allocating garbage on the heap even when zero matches occur.
3. How do you replace ONLY THE FIRST occurrence of a literal substring without treating it as a regular expression?
Answer:
The String class does not provide a replaceFirst(CharSequence, CharSequence) overload — replaceFirst(String regex, String replacement) always parses its first argument as a regex.
Two recommended approaches:
- Quoting both arguments:
String result = str.replaceFirst( Pattern.quote(target), Matcher.quoteReplacement(replacement) ); - Manual
indexOfslicing (Fastest, zero regex overhead):public static String replaceFirstLiteral(String source, String target, String replacement) { int index = source.indexOf(target); if (index == -1) return source; return source.substring(0, index) + replacement + source.substring(index + target.length()); }
4. What happens when executing text.replaceAll("(?i)apple", "orange")?
Answer:
It performs a case-insensitive replacement of every variant of the word apple ("Apple", "APPLE", "aPple") with "orange".
The embedded flag expression (?i) enables Pattern.CASE_INSENSITIVE for the remainder of the regex. The replace() method cannot achieve case-insensitive replacements natively because it performs strict byte-for-byte literal matching.
🎯 Interview Cheat Sheet
30-Second Summary
“Both
replace()andreplaceAll()replace all occurrences in a string. The difference is thatreplace()treats inputs as literal strings, whereasreplaceAll()compiles the first parameter into a regular expression (Pattern). InreplaceAll(), the replacement string treats$and\as group references, requiringMatcher.quoteReplacement()for raw strings. Since Java 9,replace()has been rewritten without regex, returningthiswhen no matches are found and running 5–10x faster thanreplaceAll().”
Key Takeaways for Senior Interviews
- Both Replace All: Neither method stops at the first occurrence (use
replaceFirst()for single substitutions). - Java 9+ Engine Redesign:
replace(CharSequence, CharSequence)no longer compilesPattern.LITERAL; it uses a direct byte scan with exact buffer pre-allocation. - Fast Return:
replace()returnsthisif no match is found (zero heap allocation). - Replacement Traps:
replaceAll()crashes on unquoted$in replacements (IllegalArgumentException); useMatcher.quoteReplacement(). - Security:
replaceAll()with untrusted input is vulnerable to ReDoS;replace()runs in deterministic $O(N)$ time. - Fastest Option:
replace(char, char)performs a single-pass primitive array copy and should always be preferred for single-character swaps.
Red Flags to Avoid
- ❌ Saying that “
replace()only replaces the first occurrence.” (The classic junior mistake). - ❌ Using
replaceAll()to substitute simple static characters (e.g., replacing dots with slashes). - ❌ Passing raw, untrusted user inputs into
replaceAll()withoutPattern.quote(). - ❌ Believing
replaceAll()is faster thanreplace().
Related Questions
- How split() Method Works — Regex vs literal splitting
- What Happens When Concatenating Strings with + Operator — Concatenation internals
- Why String is Immutable — Immutability and security
- What are Compact Strings in Java 9+ — Compact string encoding