elastic/elasticsearch · error · IllegalArgumentException

[{}#ARGUMENTS] has length [2] but [{}#execute] takes [1] arg

Error message

[{}#ARGUMENTS] has length [2] but [{}#execute] takes [1] argument.

What it means

After locating execute and reading the interface's PARAMETERS constant, ScriptClassInfo requires the PARAMETERS String[] length to equal the number of parameters on execute. A mismatch throws this IllegalArgumentException. NOTE: the message template hardcodes the literals [2] and [1] — these are NOT computed from the actual lengths, so the printed numbers are misleading regardless of the real mismatch.

Source

Thrown at modules/lang-painless/src/main/java/org/elasticsearch/painless/ScriptClassInfo.java:149

        MethodType methodType = MethodType.methodType(executeMethod.getReturnType(), executeMethod.getParameterTypes());
        this.executeMethod = new org.objectweb.asm.commons.Method(executeMethod.getName(), methodType.toMethodDescriptorString());
        executeMethodReturnType = definitionTypeForClass(
            painlessLookup,
            executeMethod.getReturnType(),
            componentType -> "Painless can only implement execute methods returning a whitelisted type but ["
                + baseClass.getName()
                + "#execute] returns ["
                + componentType.getName()
                + "] which isn't whitelisted."
        );

        // Look up the argument
        List<MethodArgument> arguments = new ArrayList<>();
        String[] argumentNamesConstant = readArgumentNamesConstant(baseClass);
        Class<?>[] types = executeMethod.getParameterTypes();
        if (argumentNamesConstant.length != types.length) {
            throw new IllegalArgumentException(
                "[" + baseClass.getName() + "#ARGUMENTS] has length [2] but [" + baseClass.getName() + "#execute] takes [1] argument."
            );
        }
        for (int arg = 0; arg < types.length; arg++) {
            arguments.add(methodArgument(painlessLookup, types[arg], argumentNamesConstant[arg]));
        }
        this.executeArguments = unmodifiableList(arguments);
        this.needsMethods = unmodifiableList(needsMethods);
        this.getMethods = unmodifiableList(getMethods);
        this.getReturns = unmodifiableList(getReturns);
        this.supportsCancellation = supportsCancellation(baseClass);
    }

    /**
     * Reflective check for whether a script base class opts into the persistent cancellation
     * mechanism by overriding {@code _getCancellationCheck()} with a non-default implementation
     * returning a {@code Runnable}.  Same semantics as {@link #supportsCancellation()} but
     * usable from places (e.g. {@link org.elasticsearch.painless.lookup.PainlessLookupBuilder})

View on GitHub (pinned to db6a809a66)

Solutions

  1. Make PARAMETERS length exactly equal to the number of execute parameters.
  2. Order the PARAMETERS entries to match the execute parameter order — these become the variable names visible inside the script.
  3. Do not rely on the [2]/[1] numbers in the message; compare the actual array length against execute's arity yourself.

Example fix

// before
public interface MyScript {
    String[] PARAMETERS = {"params"};
    double execute(Map<String,Object> params, Map<String,DocValue> doc); // arity 2 != 1 -> 1344
}
// after
public interface MyScript {
    String[] PARAMETERS = {"params", "doc"};
    double execute(Map<String,Object> params, Map<String,DocValue> doc);
}
Defensive patterns

Strategy: validation

Validate before calling

void assertParametersMatch(Class<?> iface) throws Exception {
    String[] params = (String[]) iface.getField("PARAMETERS").get(null);
    long arity = java.util.Arrays.stream(iface.getMethods())
        .filter(m -> !m.isDefault() && m.getName().equals("execute"))
        .mapToInt(m -> m.getParameterTypes().length).findFirst().orElseThrow();
    if (params.length != arity)
        throw new IllegalStateException("PARAMETERS length " + params.length + " != execute arity " + arity);
}

Try / catch

try {
    new ScriptClassInfo(lookup, MyScript.class);
} catch (IllegalArgumentException e) {
    if (e.getMessage().contains("#ARGUMENTS] has length")) {
        fail("Align PARAMETERS[] length with execute() parameter count (message hardcodes [2]/[1], check real values)");
    }
    throw e;
}

Prevention

When it happens

Trigger: A custom script-context interface where the PARAMETERS String[] constant and the execute method parameter count disagree (e.g. PARAMETERS = {"a","b"} but execute takes one arg, or vice-versa).

Common situations: Adding/removing an execute parameter and forgetting to update PARAMETERS; copy-pasting a context definition; renaming a parameter without adjusting the array.

Related errors


AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12). Data as JSON: /api/errors/155a982d9cb60d54. Report an issue: GitHub.