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
- Make PARAMETERS length exactly equal to the number of execute parameters.
- Order the PARAMETERS entries to match the execute parameter order — these become the variable names visible inside the script.
- 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
- Keep PARAMETERS and execute in lockstep — change both together.
- Do not trust the [2]/[1] literals in the message; verify actual lengths.
- Add a reflection unit test comparing array length to execute arity.
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
- Painless can only implement interfaces that have a single me
- no execute method found
- convertFromDef must take a single Object as an argument, not
- Painless needs a constant [String[] PARAMETERS] on all inter
- Error trying to read [{}#ARGUMENTS]
AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12).
Data as JSON: /api/errors/155a982d9cb60d54.
Report an issue: GitHub.