{"record":{"id":"a4109f5ff35de4dc","repo":"elastic/elasticsearch","slug":"dynamic-method-not-found","errorCode":null,"errorMessage":"dynamic method [{}, {}/{}] not found","messagePattern":"dynamic method \\[(.+?), (.+?)/(.+?)\\] not found","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"modules/lang-painless/src/main/java/org/elasticsearch/painless/Def.java","lineNumber":300,"sourceCode":"        int numArguments = callSiteType.parameterCount();\n\n        // The compiler prefixes the recipe with 'S' at def call sites in cancellation-aware\n        // functions when the method name might resolve to a @script_aware augmentation;\n        // the call site pushed the script receiver as a synthetic slot after the receiver.\n        // Peel it here so the rest of the parsing sees the same recipe shape as before.\n        boolean scriptThisPushed = recipeString.isEmpty() == false && recipeString.charAt(0) == 'S';\n        if (scriptThisPushed) {\n            recipeString = recipeString.substring(1);\n            // The synthetic script-this slot doesn't count toward the user-visible arity.\n            numArguments--;\n        }\n\n        // simple case: no lambdas\n        if (recipeString.isEmpty()) {\n            PainlessMethod painlessMethod = painlessLookup.lookupRuntimePainlessMethod(receiverClass, name, numArguments - 1);\n\n            if (painlessMethod == null) {\n                throw new IllegalArgumentException(\n                    \"dynamic method \"\n                        + \"[\"\n                        + typeToCanonicalTypeName(receiverClass)\n                        + \", \"\n                        + name\n                        + \"/\"\n                        + (numArguments - 1)\n                        + \"] not found\"\n                );\n            }\n\n            MethodHandle handle = painlessMethod.methodHandle();\n            Object[] injections = PainlessLookupUtility.buildInjections(painlessMethod, constants);\n\n            if (injections.length > 0) {\n                // The method handle's leading parameter is the receiver, so injections start at position 1. For\n                // @script_aware augmentations the handle is (scriptThis, receiver, injections..., userArgs) so they\n                // start at position 2 instead — skipping past both the script slot and the receiver.","sourceCodeStart":282,"sourceCodeEnd":318,"githubUrl":"https://github.com/elastic/elasticsearch/blob/db6a809a667c081ca1dc7500389d26975573215f/modules/lang-painless/src/main/java/org/elasticsearch/painless/Def.java#L282-L318","documentation":"Def.invoke (the no-lambda fast path) resolves a dynamically-dispatched method call on a 'def'-typed receiver. It calls painlessLookup.lookupRuntimePainlessMethod(receiverClass, name, numArguments-1) — arity excludes the receiver. If the lookup returns null (no whitelisted method with that name and parameter count on the runtime receiver class), it throws naming the receiver type canonical name, the method name, and the expected arity. This is the classic 'method missing on def' error.","triggerScenarios":"A Painless script calls a method on a variable declared 'def' (or any dynamically-typed expression) where, at runtime, the actual receiver class has no whitelisted method matching the name and arity. Examples: def x = 5; x.foo(); or calling a method that exists in Java but is not whitelisted for Painless, or calling with the wrong argument count.","commonSituations":"Assuming a Java/JDK method is available when only a whitelisted subset is. Type inference producing 'def' unexpectedly. API changes removing or renaming a method. Wrong arity (off-by-one, forgetting 'this').","solutions":["Check the Painless API allowlist for the receiver class to confirm the method name and arity are permitted.","Replace 'def' with a concrete type so the call is resolved at compile time and gives a clearer error if missing.","Verify argument count: the reported arity excludes the receiver, so match your call's argument count to the lookup arity.","If the method legitimately should exist, ensure the relevant plugin/module registers it in its painless allowlist."],"exampleFix":"// before\ndef x = doc['v'].value;\nx.subString(0, 3); // typo + wrong name\n// after\ndef x = doc['v'].value;\nx.substring(0, 3); // correct whitelisted method","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"// Pre-flight: confirm the method exists on the expected type before relying on def dispatch.\n// Static: type the receiver so the compiler checks it.\n// int x = doc['v'].value; x.substring(0,3); // compile-time check vs def x = ...","tryCatchPattern":"// ScriptException from a def call is surfaced at the API boundary; surface the receiver class + method name from the message to guide the user to the allowlist.","preventionTips":["Prefer concrete types over 'def' where possible to move errors to compile time.","Cross-check method names against the Painless API allowlist for the receiver class.","Mind arity: the reported count excludes the receiver."],"tags":["painless","script","dynamic-dispatch","def","runtime"],"analyzedSha":"db6a809a667c081ca1dc7500389d26975573215f","analyzedAt":"2026-08-12T01:39:14.192Z","schemaVersion":2},"datasetVersion":"2026-08-12T06:17:24.410Z"}