Java String Interpolation Performance Study

tl;dr In Java, String.replace should be used for string interpolation to reduce performance impact, and concatenating string with + is performant as well.

Generally, to perform string interpolation, one would provide a templated string, e.g. "[{1}] message received with id: {2} with size: {3}". In Java, the closest you get out of the box is String.format for this.

However, there is a common belief that String.format is slow and should not be used in performance critical applications. Some suggests to use String.replace instead in such situation.

Benchmarking

To test which method is better, I wrote code to interpolate string for integer, string and double in both ways and do benchmark with JMH on a Mac with 1.6 GHz Intel Core i5 with Zulu OpenJDK 64-Bit Server VM (build 11.0.10+9-LTS, mixed mode) based on throughput.

Interpolation with replace

@Benchmark
public void testStringReplace(){
  System.out.println(
    "{1} {2} {3}"
        .replace("{1}", String.valueOf(11))
        .replace("{2}", "Eleven")
        .replace("{3}", String.valueOf(1.1))
  );
}

Interpolation with format

@Benchmark
public void testStringFormat(){
  System.out.println(String.format("%d %s %f", 11, "Eleven", 1.1));
}

Result

Benchmark                                               Mode  Cnt      Score   Error  Units
StringInterpolationJavaBenchmark.testStringFormat      thrpt    2  25191.009          ops/s
StringInterpolationJavaBenchmark.testStringReplace     thrpt    2  46889.833          ops/s

As the result suggest, using replace is much faster than format

Why

Comparing their implementation tells most of the story. The following analysis is based on source code of OpenJDK 11

Implementation of String.replace

public String replace(CharSequence target, CharSequence replacement) {
    String tgtStr = target.toString();
    String replStr = replacement.toString();
    int j = indexOf(tgtStr);
    if (j < 0) {
        return this;
    }
    int tgtLen = tgtStr.length();
    int tgtLen1 = Math.max(tgtLen, 1);
    int thisLen = length();

    int newLenHint = thisLen - tgtLen + replStr.length();
    if (newLenHint < 0) {
        throw new OutOfMemoryError();
    }
    StringBuilder sb = new StringBuilder(newLenHint);
    int i = 0;
    do {
        sb.append(this, i, j).append(replStr);
        i = j + tgtLen;
    } while (j < thisLen && (j = indexOf(tgtStr, j + tgtLen1)) > 0);
    return sb.append(this, i, thisLen).toString();
}

It loop over the original string, append the either the original section of string, or the replaced string to a new string builder, and return the newly built string from the builder.

Implementation of String.format

public static String format(String format, Object... args) {
  return new Formatter().format(format, args).toString();
}

Every time it will create a new Formatter.

private List<FormatString> parse(String s) {
  ArrayList<FormatString> al = new ArrayList<>();
  Matcher m = fsPattern.matcher(s);

// other code...

The Formatter requires to parse the format with RegEx on each invocation. Here, RegEx parsing is expensive in terms of performance.

Thus, String.replace is much performant than String.format.

Can we go further?

replaceFirst

Some may think replaceFirst can reduce iterations by early exit in finding string.

@Benchmark
public void testStringReplaceFirst(){
  // NOTE: it is regex "\\{}", not simple curly bracket "{}"
  System.out.println(
    "{} {} {}"
        .replaceFirst("\\{}", String.valueOf(11))
        .replaceFirst("\\{}", "Eleven")
        .replaceFirst("\\{}", String.valueOf(1.1))
  );
}
Benchmark                                                  Mode  Cnt      Score   Error  Units
StringInterpolationJavaBenchmark.testStringReplaceFirst   thrpt    2  30718.865          ops/s

Here, the performance is worsen, because replaceFirst relies on RegEx.

Concatenate strings with +

@Benchmark
public void testStringConcat(){
  System.out.println(
    String.valueOf(11) + " " + "Eleven" + " " + String.valueOf(1.1)
  );
}
Benchmark                                            Mode  Cnt      Score   Error  Units
StringInterpolationJavaBenchmark.testStringConcat   thrpt    2  47188.903          ops/s

The performance is better than replace here, but it doesn't look like string interpolation and the code is not as elegant as the the replace version.

Closing

If your Java application is performance critical, String.replace and concatenate strings with + are more performant than String.format.

All above benchmark code is available in this repo.

If you like this article, remember to show your support by buy me a book.