SpEL fails to invoke function references whose function handle only consists of varargs parameters.

org.springframework.expression.spel.SpelEvaluationException: EL1023E: A problem occurred whilst attempting to invoke the function 'varArgsFunction': 'Cannot cast [Ljava.lang.String; to java.lang.String'

    at org.springframework.expression.spel.ast.FunctionReference.executeFunctionViaMethodHandle(FunctionReference.java:257)
    at org.springframework.expression.spel.ast.FunctionReference.getValueInternal(FunctionReference.java:97)
    at org.springframework.expression.spel.ast.SpelNodeImpl.getValue(SpelNodeImpl.java:114)
    at org.springframework.expression.spel.standard.SpelExpression.getValue(SpelExpression.java:273)
    at test.ReproduceVarArgsBugTest.testVarArgsFunction(ReproduceVarArgsBugTest.java:21)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
Caused by: java.lang.ClassCastException: Cannot cast [Ljava.lang.String; to java.lang.String
    at java.base/java.lang.Class.cast(Class.java:3889)
    at java.base/java.lang.invoke.MethodHandle.invokeWithArguments(MethodHandle.java:732)
    at java.base/java.lang.invoke.MethodHandleImpl$AsVarargsCollector.invokeWithArguments(MethodHandleImpl.java:535)
    at org.springframework.expression.spel.ast.FunctionReference.executeFunctionViaMethodHandle(FunctionReference.java:253)

This seems to be failing because in FunctionReference, methodHandle.invokeWithArguments(functionArgs) is invoked with functionArgs being set as Object[] {String[] {"a", "b", "c"}}. Instead of being wrapped in an Object[], functionArgs should have just been String[] {"a", "b", "c"}.

Reproduction

Spring Framework: 6.1.16

Here's a simple test class to reproduce it:

package test;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;

public class ReproduceVarArgsBugTest {

    // this test fails since varArgsFunction only consists of varargs parameters
    @Test
    void testVarArgsFunction() throws Exception {
        SpelExpressionParser parser = new SpelExpressionParser();
        StandardEvaluationContext context = new StandardEvaluationContext();
        MethodHandle methodHandle = MethodHandles.lookup().findStatic(ReproduceVarArgsBugTest.class,
                "varArgsFunction", MethodType.methodType(String.class, String[].class));
        context.registerFunction("varArgsFunction", methodHandle);
        Assertions.assertEquals("a,b,c", parser.parseExpression("#varArgsFunction('a', 'b', 'c')").getValue(context));
    }

    public static String varArgsFunction(String... input) {
        return String.join(",", input);
    }

    // this passes because varArgsWithOtherParamFunction has at least one parameter that isn't varargs (i.e. otherParam)
    @Test
    void testVarArgsWithOtherParamFunction() throws Exception {
        SpelExpressionParser parser = new SpelExpressionParser();
        StandardEvaluationContext context = new StandardEvaluationContext();
        MethodHandle methodHandle = MethodHandles.lookup().findStatic(ReproduceVarArgsBugTest.class,
                "varArgsWithOtherParamFunction", MethodType.methodType(String.class, String.class, String[].class));
        context.registerFunction("varArgsWithOtherParamFunction", methodHandle);
        Assertions.assertEquals("a,b,c", parser.parseExpression("#varArgsWithOtherParamFunction('a', 'b', 'c')").getValue(context));
    }

    public static String varArgsWithOtherParamFunction(String otherParam, String... input) {
        return otherParam + "," + String.join(",", input);
    }
}

Comment From: sbrannen

Thanks for reporting this, @Nephery.

This has been fixed in 6.2.x and main and backported to 6.1.x for inclusion in the upcoming 6.1.7 and 6.2.2 releases.

Comment From: Nephery

I confirmed that the fix works in the latest 6.1.17 snapshot. Thanks for the quick fix. 🙂

Comment From: sbrannen

I confirmed that the fix works in the latest 6.1.17 snapshot.

Great! Thanks for trying it out and confirming it works.

Thanks for the quick fix. 🙂

You're welcome.