elastic/elasticsearch · error · IllegalArgumentException

Painless can only implement interfaces that have a single me

Error message

Painless can only implement interfaces that have a single method named [execute] but [{}] has more than one.

What it means

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.

Source

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

        this.baseClass = baseClass;

        // Find the main method and the uses$argName methods
        java.lang.reflect.Method executeMethod = null;
        List<org.objectweb.asm.commons.Method> needsMethods = new ArrayList<>();
        List<org.objectweb.asm.commons.Method> getMethods = new ArrayList<>();
        List<Class<?>> getReturns = new ArrayList<>();

        Class<?> returnType = null;
        for (java.lang.reflect.Method m : baseClass.getMethods()) {
            if (m.isDefault()) {
                continue;
            }
            if (m.getName().equals("execute")) {
                if (executeMethod == null) {
                    executeMethod = m;
                    returnType = m.getReturnType();
                } else {
                    throw new IllegalArgumentException(
                        "Painless can only implement interfaces that have a single method named [execute] but ["
                            + baseClass.getName()
                            + "] has more than one."
                    );
                }
            } else if (m.getName().startsWith("needs") && m.getReturnType() == boolean.class && m.getParameterTypes().length == 0) {
                needsMethods.add(new org.objectweb.asm.commons.Method(m.getName(), NEEDS_PARAMETER_METHOD_TYPE.toMethodDescriptorString()));
            } else if (m.getName().startsWith("get")
                && m.getName().equals("getClass") == false
                && Modifier.isStatic(m.getModifiers()) == false) {
                    getReturns.add(
                        definitionTypeForClass(
                            painlessLookup,
                            m.getReturnType(),
                            componentType -> "["
                                + m.getName()
                                + "] has unknown return "
                                + "type ["

View on GitHub (pinned to db6a809a66)

Solutions

  1. Ensure the context interface exposes exactly one non-default method named execute.
  2. Remove or rename the duplicate execute method; if a variant is needed, make it a default method (default methods are skipped by the loop).
  3. Rebuild and redeploy the plugin/module that registers the context.

Example fix

// before
public interface MyScript {
    double execute(Map<String,Object> params);
    double execute(); // duplicate -> triggers 1341
}
// after
public interface MyScript {
    double execute(Map<String,Object> params);
}
Defensive patterns

Strategy: validation

Validate before calling

void assertSingleExecute(Class<?> iface) {
    long count = java.util.Arrays.stream(iface.getMethods())
        .filter(m -> !m.isDefault() && m.getName().equals("execute"))
        .count();
    if (count != 1) throw new IllegalStateException("interface must have exactly one execute, found " + count);
}

Try / catch

try {
    new ScriptClassInfo(lookup, MyScript.class);
} catch (IllegalArgumentException e) {
    // message names the offending interface
    fail("Invalid script context interface: " + e.getMessage());
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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