Why is this an issue?

Looking for a given substring starting from a specified offset can be achieved by such code: str.substring(beginIndex).indexOf(char1). This works well, but it creates a new String for each call to the substring method. When this is done in a loop, a lot of Strings are created for nothing, which can lead to performance problems if str is large.

To avoid performance problems, String.substring(beginIndex) should not be chained with the following methods:

For each of these methods, another method with an additional parameter is available to specify an offset.

Using these methods will avoid the creation of additional String instances. For indexOf methods, adjust the returned value by subtracting the substring index parameter to obtain the same result.

Noncompliant code example

str.substring(beginIndex).indexOf(char1); // Noncompliant; a new String is going to be created by "substring"

Compliant solution

str.indexOf(char1, beginIndex) - beginIndex; // index for char1 not found is (-1-beginIndex)

Resources

Benchmarks

Method stringSize Runtime Average time Error margin

indexOfOnly

10

Temurin 21

1.55 ns/op

±0.12 ns/op

indexOfOnly

100

Temurin 21

1.78 ns/op

±0.05 ns/op

indexOfOnly

1000

Temurin 21

1.82 ns/op

±0.18 ns/op

indexOfOnly

10000

Temurin 21

1.77 ns/op

±0.08 ns/op

substringThenIndexOf

10

Temurin 21

4.85 ns/op

±0.41 ns/op

substringThenIndexOf

100

Temurin 21

6.22 ns/op

±0.40 ns/op

substringThenIndexOf

1000

Temurin 21

14.22 ns/op

±1.66 ns/op

substringThenIndexOf

10000

Temurin 21

275.00 ns/op

±20.49 ns/op

Benchmarking code

The results were generated by running the following snippet with JMH.

@BenchmarkMode({Mode.AverageTime})
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Benchmark)
public class S4635 {
  @Param({"10", "100", "1000", "10000"})
  int stringSize;

  String input;

  @Setup
  public void setup() {
    StringBuilder builder = new StringBuilder();
    for (int i = 0; i < stringSize; i++) {
      builder.append('a');
    }
    input = builder.toString();
  }

  @Benchmark
  public int substringThenIndexOf() {
    return stringSize / 2 + input.substring(stringSize / 2).indexOf('a');
  }

  @Benchmark
  public int indexOfOnly() {
    return input.indexOf('a', stringSize / 2);
  }
}