Good to know it's solved. I admit that your solution had me puzzled, until I read in the spec for replaceAll:
Note that backslashes (\) and dollar signs ($) in the replacement string may cause the results to be different than if it were being treated as a literal replacement string; see Matcher.replaceAll.
A bit of a trap, indeed. Using replace rather than replaceAll is probably a simpler solution, as then you just have literal target and replacement strings.