{"record":{"id":"d9aab488ecc16d22","repo":"elastic/elasticsearch","slug":"painless-can-only-implement-interfaces-that-have-a","errorCode":null,"errorMessage":"Painless can only implement interfaces that have a single method named [execute] but [{}] has more than one.","messagePattern":"Painless can only implement interfaces that have a single method named \\[execute\\] but \\[(.+?)\\] has more than one\\.","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"modules/lang-painless/src/main/java/org/elasticsearch/painless/ScriptClassInfo.java","lineNumber":63,"sourceCode":"        this.baseClass = baseClass;\n\n        // Find the main method and the uses$argName methods\n        java.lang.reflect.Method executeMethod = null;\n        List<org.objectweb.asm.commons.Method> needsMethods = new ArrayList<>();\n        List<org.objectweb.asm.commons.Method> getMethods = new ArrayList<>();\n        List<Class<?>> getReturns = new ArrayList<>();\n\n        Class<?> returnType = null;\n        for (java.lang.reflect.Method m : baseClass.getMethods()) {\n            if (m.isDefault()) {\n                continue;\n            }\n            if (m.getName().equals(\"execute\")) {\n                if (executeMethod == null) {\n                    executeMethod = m;\n                    returnType = m.getReturnType();\n                } else {\n                    throw new IllegalArgumentException(\n                        \"Painless can only implement interfaces that have a single method named [execute] but [\"\n                            + baseClass.getName()\n                            + \"] has more than one.\"\n                    );\n                }\n            } else if (m.getName().startsWith(\"needs\") && m.getReturnType() == boolean.class && m.getParameterTypes().length == 0) {\n                needsMethods.add(new org.objectweb.asm.commons.Method(m.getName(), NEEDS_PARAMETER_METHOD_TYPE.toMethodDescriptorString()));\n            } else if (m.getName().startsWith(\"get\")\n                && m.getName().equals(\"getClass\") == false\n                && Modifier.isStatic(m.getModifiers()) == false) {\n                    getReturns.add(\n                        definitionTypeForClass(\n                            painlessLookup,\n                            m.getReturnType(),\n                            componentType -> \"[\"\n                                + m.getName()\n                                + \"] has unknown return \"\n                                + \"type [\"","sourceCodeStart":45,"sourceCodeEnd":81,"githubUrl":"https://github.com/elastic/elasticsearch/blob/db6a809a667c081ca1dc7500389d26975573215f/modules/lang-painless/src/main/java/org/elasticsearch/painless/ScriptClassInfo.java#L45-L81","documentation":"ScriptClassInfo scans a script-context interface via reflection and requires exactly one non-default method named 'execute'. If two or more methods named execute are visible on the interface (overloads, or inherited from multiple super-interfaces), the second one triggers this IllegalArgumentException during PainlessLookup/context initialization. The interface name is interpolated into the message.","triggerScenarios":"Defining a custom ScriptContext whose interface declares two overloaded execute(...) methods, or whose interface inherits execute from more than one super-interface, and then registering that context with the Painless engine so ScriptClassInfo is constructed for it.","commonSituations":"Authoring a custom script context (e.g. for a plugin) and accidentally overloading execute; merging two interfaces that both define execute; refactor that added a convenience execute overload.","solutions":["Ensure the context interface exposes exactly one non-default method named execute.","Remove or rename the duplicate execute method; if a variant is needed, make it a default method (default methods are skipped by the loop).","Rebuild and redeploy the plugin/module that registers the context."],"exampleFix":"// before\npublic interface MyScript {\n    double execute(Map<String,Object> params);\n    double execute(); // duplicate -> triggers 1341\n}\n// after\npublic interface MyScript {\n    double execute(Map<String,Object> params);\n}","handlingStrategy":"validation","validationCode":"void assertSingleExecute(Class<?> iface) {\n    long count = java.util.Arrays.stream(iface.getMethods())\n        .filter(m -> !m.isDefault() && m.getName().equals(\"execute\"))\n        .count();\n    if (count != 1) throw new IllegalStateException(\"interface must have exactly one execute, found \" + count);\n}","typeGuard":null,"tryCatchPattern":"try {\n    new ScriptClassInfo(lookup, MyScript.class);\n} catch (IllegalArgumentException e) {\n    // message names the offending interface\n    fail(\"Invalid script context interface: \" + e.getMessage());\n}","preventionTips":["When authoring a custom context, run a reflection unit test asserting exactly one non-default execute.","Mark convenience overloads as default methods so they are skipped.","Avoid multiple inheritance of interfaces that both declare execute."],"tags":["painless","scripting","script-context","reflection","elasticsearch"],"backgroundTag":null,"analyzedSha":"db6a809a667c081ca1dc7500389d26975573215f","analyzedAt":"2026-08-12T01:39:14.192Z","schemaVersion":2},"datasetVersion":"2026-08-12T12:31:55.035Z"}