{"record":{"id":"155a982d9cb60d54","repo":"elastic/elasticsearch","slug":"arguments-has-length-2-but-execute-tak","errorCode":null,"errorMessage":"[{}#ARGUMENTS] has length [2] but [{}#execute] takes [1] argument.","messagePattern":"\\[(.+?)#ARGUMENTS\\] has length \\[2\\] but \\[(.+?)#execute\\] takes \\[1\\] argument\\.","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"modules/lang-painless/src/main/java/org/elasticsearch/painless/ScriptClassInfo.java","lineNumber":149,"sourceCode":"\n        MethodType methodType = MethodType.methodType(executeMethod.getReturnType(), executeMethod.getParameterTypes());\n        this.executeMethod = new org.objectweb.asm.commons.Method(executeMethod.getName(), methodType.toMethodDescriptorString());\n        executeMethodReturnType = definitionTypeForClass(\n            painlessLookup,\n            executeMethod.getReturnType(),\n            componentType -> \"Painless can only implement execute methods returning a whitelisted type but [\"\n                + baseClass.getName()\n                + \"#execute] returns [\"\n                + componentType.getName()\n                + \"] which isn't whitelisted.\"\n        );\n\n        // Look up the argument\n        List<MethodArgument> arguments = new ArrayList<>();\n        String[] argumentNamesConstant = readArgumentNamesConstant(baseClass);\n        Class<?>[] types = executeMethod.getParameterTypes();\n        if (argumentNamesConstant.length != types.length) {\n            throw new IllegalArgumentException(\n                \"[\" + baseClass.getName() + \"#ARGUMENTS] has length [2] but [\" + baseClass.getName() + \"#execute] takes [1] argument.\"\n            );\n        }\n        for (int arg = 0; arg < types.length; arg++) {\n            arguments.add(methodArgument(painlessLookup, types[arg], argumentNamesConstant[arg]));\n        }\n        this.executeArguments = unmodifiableList(arguments);\n        this.needsMethods = unmodifiableList(needsMethods);\n        this.getMethods = unmodifiableList(getMethods);\n        this.getReturns = unmodifiableList(getReturns);\n        this.supportsCancellation = supportsCancellation(baseClass);\n    }\n\n    /**\n     * Reflective check for whether a script base class opts into the persistent cancellation\n     * mechanism by overriding {@code _getCancellationCheck()} with a non-default implementation\n     * returning a {@code Runnable}.  Same semantics as {@link #supportsCancellation()} but\n     * usable from places (e.g. {@link org.elasticsearch.painless.lookup.PainlessLookupBuilder})","sourceCodeStart":131,"sourceCodeEnd":167,"githubUrl":"https://github.com/elastic/elasticsearch/blob/db6a809a667c081ca1dc7500389d26975573215f/modules/lang-painless/src/main/java/org/elasticsearch/painless/ScriptClassInfo.java#L131-L167","documentation":"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.","triggerScenarios":"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).","commonSituations":"Adding/removing an execute parameter and forgetting to update PARAMETERS; copy-pasting a context definition; renaming a parameter without adjusting the array.","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."],"exampleFix":"// before\npublic interface MyScript {\n    String[] PARAMETERS = {\"params\"};\n    double execute(Map<String,Object> params, Map<String,DocValue> doc); // arity 2 != 1 -> 1344\n}\n// after\npublic interface MyScript {\n    String[] PARAMETERS = {\"params\", \"doc\"};\n    double execute(Map<String,Object> params, Map<String,DocValue> doc);\n}","handlingStrategy":"validation","validationCode":"void assertParametersMatch(Class<?> iface) throws Exception {\n    String[] params = (String[]) iface.getField(\"PARAMETERS\").get(null);\n    long arity = java.util.Arrays.stream(iface.getMethods())\n        .filter(m -> !m.isDefault() && m.getName().equals(\"execute\"))\n        .mapToInt(m -> m.getParameterTypes().length).findFirst().orElseThrow();\n    if (params.length != arity)\n        throw new IllegalStateException(\"PARAMETERS length \" + params.length + \" != execute arity \" + arity);\n}","typeGuard":null,"tryCatchPattern":"try {\n    new ScriptClassInfo(lookup, MyScript.class);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().contains(\"#ARGUMENTS] has length\")) {\n        fail(\"Align PARAMETERS[] length with execute() parameter count (message hardcodes [2]/[1], check real values)\");\n    }\n    throw e;\n}","preventionTips":["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."],"tags":["painless","scripting","script-context","reflection","elasticsearch"],"backgroundTag":null,"analyzedSha":"db6a809a667c081ca1dc7500389d26975573215f","analyzedAt":"2026-08-12T01:39:14.192Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}