Fixing JavaTemplate Throwing ArrayIndexOutOfBounds

BitFlipperBitFlipper
2 min read

Are you getting a java.lang.ArrayIndexOutOfBoundsException from your JavaTemplate in your OpenRewrite recipe?

If you are using substitution indicators (#{}) in your template string, you could have a mismatch between the number of indicators provided and the number of varargs parameters given to the apply method.

Example

public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext executionContext) {
    method = super.visitMethodDeclaration(method, executionContext);
    ...
    return JavaTemplate.builder("this.#{} = #{}")
            .build()
            .apply(
                    getCursor(),
                    method.getBody().getCoordinates().lastStatement(),
                    "helloWorld" // variable to insert into template
            );
}

The above recipe is adding a new field assignment to a constructor declaration. helloWorld should be substituted into the template. Instead we see this exception being thrown:

Caused by: java.lang.ArrayIndexOutOfBoundsException: Index 1 out of bounds for length 1
    at org.openrewrite.java.internal.template.Substitutions.lambda$substitute$0(Substitutions.java:80)
    at org.openrewrite.internal.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:98)
    at org.openrewrite.internal.PropertyPlaceholderHelper.replacePlaceholders(PropertyPlaceholderHelper.java:72)
    at org.openrewrite.java.internal.template.Substitutions.substitute(Substitutions.java:52)
    at org.openrewrite.java.JavaTemplate.apply(JavaTemplate.java:58)

The JavaTemplate is able substitute the first marker with the first parameter. When trying to do the same for the second marker it finds a second element is not provided. The template does not know the recipe intends both markers to be replaced with the same value. The recipe needs to be explicit like so:

public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext executionContext) {
    method = super.visitMethodDeclaration(method, executionContext);
    ...
    return JavaTemplate.builder("this.#{} = #{}")
            .build()
            .apply(
                    getCursor(),
                    method.getBody().getCoordinates().lastStatement(),
                    "helloWorld",
                    "helloWorld" // Needs to be specified twice!
            );
}

References

Support our work

0
Subscribe to my newsletter

Read articles from BitFlipper directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

BitFlipper
BitFlipper