Back to Blog
Java

Java String replace: Literal Replacement Without Regex

java string replace: How Java's String.replace() works, how it differs from replaceAll() and replaceFirst(), and when to choose literal replacement over regex.

JavaStringreplaceregeximmutability
A Java String being transformed by the replace method, showing literal text substitution without regular expression pattern matching.

Java's String.replace() method replaces every occurrence of a literal character or text sequence in a string. It is the method developers reach for when they need straightforward text substitution without regular expression semantics. Understanding how String.replace() behaves — and where it differs from replaceAll() and replaceFirst() — prevents a common class of bugs in text processing code.

String original = "banana"; String result = original.replace('a', 'o'); System.out.println(result); // "bonono"

The Two Overloads of String.replace()

The first overload takes two char arguments:

public String replace(char oldChar, char newChar)

Every occurrence of oldChar is replaced with newChar. The operation is a direct character-for-character substitution with no pattern matching involved.

The second overload takes two CharSequence arguments:

public String replace(CharSequence target, CharSequence replacement)

Every occurrence of target is replaced with replacement. Both arguments are treated as literal text, never as regular expressions. This is the critical distinction that causes most confusion when developers move between replace() and replaceAll().

String text = "The quick brown fox"; String result = text.replace("quick", "slow"); System.out.println(result); // "The slow brown fox"

The CharSequence overload accepts String, StringBuilder, StringBuffer, and any other implementation of CharSequence. In practice, String arguments are the most common.

How replace() Differs from replaceAll() and replaceFirst()

replaceAll() and replaceFirst() both interpret their first argument as a regular expression. replace() does not.

String text = "price: 100, discount: 20"; String withRegex = text.replaceAll("\\d+", "0"); String withLiteral = text.replace("\\d+", "0");

The first call replaces every digit sequence with "0". The second call searches for the literal characters backslash, d, and plus, which do not appear in the string, so the original text is returned unchanged.

This distinction matters when the target text contains regex metacharacters. To replace a literal dot, dollar sign, or parenthesis, replace() is the safer choice because no escaping is required:

String path = "src.main.java"; String result = path.replace(".", "/");

The same intent with replaceAll() requires escaping the dot: path.replaceAll("\\.", "/"). Forgetting the escape produces a pattern that matches every character.

Why replace() Returns a New String

String objects in Java are immutable. Every method that appears to modify a string — replace(), toUpperCase(), trim(), substring() — actually returns a new String instance. The original object is never modified.

String value = "hello"; value.replace('l', 'r'); System.out.println(value); // "hello" — the result was discarded

The return value of replace() must be assigned to a variable or used directly. Failing to capture it is a common mistake, especially for developers coming from languages with mutable string types.

Performance: Literal Replacement vs Regex

The main practical advantage of replace() is semantic, not necessarily raw speed: it treats the target as literal text, so no regex metacharacter escaping is needed. How it performs depends on the JDK implementation. The char overload can be implemented as a simple scan, while the CharSequence overload may be implemented by compiling the target as a literal Pattern internally. That means replace() does not necessarily skip regex machinery; it only skips regex interpretation.

For a single call, any difference is negligible. In a loop, use the simplest correct method first:

for (String line : lines) { String cleaned = line.replace(",", ";"); // process cleaned }

If profiling shows replacement is a bottleneck and the replacement needs a real regex, compile the Pattern once and reuse it:

Pattern digitPattern = Pattern.compile("\\d+"); for (String line : lines) { String cleaned = digitPattern.matcher(line).replaceAll("0"); // process cleaned }

Unless you have measured a problem, prefer replace() for literal targets because it is easier to read and avoids regex escaping bugs, not because it has a guaranteed performance advantage.

Common Mistakes with replace() and replaceAll()

The most frequent mistake is using replaceAll() when the target is literal text and forgetting to escape regex metacharacters. This produces either no replacement or an unexpected result.

String text = "file.txt"; String wrong = text.replaceAll(".", "_"); // every character becomes "_" String right = text.replace(".", "_"); // "file_txt"

A second mistake is assuming replace() modifies the original string. Since String is immutable, the original is never touched, and the result must be captured.

A third mistake is passing null to the CharSequence overload. It throws NullPointerException if either target or replacement is null. The char overload cannot accept null because it takes primitive char values, so null checks are relevant mainly when the input is untrusted and you are using the CharSequence overload.

Edge Cases and Input Behavior

When the target sequence does not appear in the string, replace() returns a String equal to the original. The contract guarantees equality, not identity, so code should never rely on reference comparison after calling replace().

When target is an empty string, the replacement is inserted at the start, between every character, and at the end. This behavior follows directly from the replacement semantics and can be surprising:

String text = "abc"; String result = text.replace("", "-"); System.out.println(result); // "-a-b-c-"

This edge case is rarely needed in practice, but it is worth knowing because it follows from the documented contract rather than from any special-case handling.

Choosing the Right Replacement Method

MethodFirst argumentReplacement scope
replace()Literal textAll occurrences
replaceAll()Regular expressionAll occurrences
replaceFirst()Regular expressionFirst occurrence only

Use replace() when the target is a fixed character or literal sequence and all occurrences should be replaced. This covers most text-cleaning tasks in everyday code: normalizing separators, removing known substrings, or swapping fixed tokens.

Use replaceAll() when the target must be expressed as a regular expression — digit sequences, word boundaries, or character classes.

Use replaceFirst() when only the first occurrence of a pattern should be replaced, such as replacing a single leading prefix.

For literal replacement, replace() is the right default. It avoids regex escaping bugs, reads more clearly, and keeps the code focused on the literal text being matched. Reserve replaceAll() for cases where pattern matching genuinely adds value, and keep the pattern compiled and reused when it is evaluated frequently.

java string replace: Practical Usage and Code Examples | RYUSLOG DEV