The Java String method replaceAll offers an easy way to replace part of a String against another. However this can fail in unexpected ways when the search pattern contains special symbols. Here is how to fix the problem:
How to reproduce the PatternSyntaxException
First let us look at an example how to produce the error.
private static void useStringReplaceAll() { String someString = "This is a string with a {{pattern}} to be replaced"; String myPattern = "{{pattern}}"; String newValue = "replacement string"; String modifiedString = someString.replaceAll(myPattern, newValue); System.out.print(modifiedString); }
The error message then looks like this:
Exception in thread "main" java.util.regex.PatternSyntaxException: Illegal repetition near index 1 {{pattern}} ^ at java.base/java.util.regex.Pattern.error(Pattern.java:2028) at java.base/java.util.regex.Pattern.closure(Pattern.java:3321) at java.base/java.util.regex.Pattern.sequence(Pattern.java:2214) at java.base/java.util.regex.Pattern.expr(Pattern.java:2069) at java.base/java.util.regex.Pattern.compile(Pattern.java:1783) at java.base/java.util.regex.Pattern.<init>(Pattern.java:1430) at java.base/java.util.regex.Pattern.compile(Pattern.java:1069) at java.base/java.lang.String.replaceAll(String.java:2142) at de.evermann.testjava.PatternTest.useStringReplaceAll(PatternTest.java:24) at de.evermann.testjava.PatternTest.main(PatternTest.java:8)
I came across the error when I wanted to expand a macro “{{filename}}” in a string against the actual filename.
The PatternSyntaxException explained
My misunderstanding was that ReplaceAll simply exchanges all occurences of one substring with another. But this is not the case. The method signature is
String java.lang.String.replaceAll(String regex, String replacement)
The important point to remember here is that the search pattern is a regular expression (“regex”) and not a simple substring. This can be very powerful. You could e.g. replace everything from the beginning of the string up to and including the first whitespace character, if you wanted to do that.
The regular expression uses these special characters
\.[]{}()<>*+-=!?^$|
to define all sorts of weird stuff. But if you only want to replace one substring against another, and if the search pattern contains one of these special characters, then you are up for trouble.
If you get the PatternSyntaxException, then you at least see directly, that something is wrong. In this case the error is caused by the double brackets. But: If you use any of the special regex characters and the regex is valid, then you just do not get the replacement that you are looking for.
This could even be worse. Your code could produce errors that you are not aware of in case of unexpected and untested inputs. This is especially important if the search pattern comes from interactive user input.
How to fix PatternSyntaxException: the easy way
The easy way to get what you really want is to use another String replacement method: replace (without the ..All). It has this signature:
String java.lang.String.replace(CharSequence target, CharSequence replacement)
The docs state:
Replaces each substring of this string that matches the literal target sequence with the specified literal replacement sequence.
So let’s try it. Let’s see whether it works and whether it changes all occurences:
private static void useStringReplaceTwice() { String someString = "This is a string with a {{pattern}} to be replaced {{pattern}} twice"; String myPattern = "{{pattern}}"; String newValue = "replacement string"; String modifiedString = someString.replace(myPattern, newValue); System.out.print(modifiedString); }
The result is:
This is a string with a replacement string to be replaced replacement string twice
And this exactly what I wanted.
Unfortunatley it is easy to fall into this trap. When I start typing replace, then the autocompletion of my IDE offers two completions.
- replace and
- replaceAll
So I thought, better use replaceAll, the pattern can, after all, come twice and should be replaced in all places. But it turns out that a simple replace already did the trick.
How to fix PatternSyntaxException: the hard way
If you really want to make replaceAll work, there actually is a way. You need to tell the regex processor that the special characters are not meant to be treated as such:
private static void useStringReplaceAll2() { String someString = "This is a string with a {{pattern}} to be replaced {{pattern}} twice"; String myPattern = "{{pattern}}"; String newValue = "{{replacement string}}"; String quote1 = Pattern.quote(myPattern); System.out.println("quote1="+ quote1); String modifiedString = someString.replaceAll(quote1, newValue); System.out.print(modifiedString); }
There is a certain marker in a regex that does exactly that. It is a bit hard to remember. Fortunately there is a method in Pattern to add that marker: Pattern.quote. The result looks like this:
quote1=\Q{{pattern}}\E
This is a code that replaceAll can understand. After adding this markup, replaceAll behaves just like replace. But if you read the Java documentation carefully, you will find that the backslash and the $ sign have a special meaning in the replacement string, too. There is another way to add quoting there. Instead of Pattern.quote you need another quote method: Matcher.quoteReplacement.
This is the place where things become too complicated to use ReplaceAll in a safe way. So my definite recommendation is to use replace and to forget about ReplaceAll. Unless of course you really want to do advanced regex tricks.
More java tipps can be found here.