elastic/elasticsearch · error · IllegalArgumentException

Class [{}] is not a functional interface

Error message

Class [{}] is not a functional interface

What it means

Def.lookupReference, after resolving interfaceType successfully, calls painlessLookup.lookupFunctionalInterfacePainlessMethod(interfaceType). If that returns null, the resolved type is not actually a functional interface (no single abstract method), and the code throws 'Class [...] is not a functional interface'. This catches cases where the type name is whitelisted but the type itself cannot serve as a lambda target.

Source

Thrown at modules/lang-painless/src/main/java/org/elasticsearch/painless/Def.java:495

     */
    static MethodHandle lookupReference(
        PainlessLookup painlessLookup,
        FunctionTable functions,
        Map<String, Object> constants,
        MethodHandles.Lookup methodHandlesLookup,
        String interfaceClass,
        Class<?> receiverClass,
        String name,
        boolean chargesAllocation
    ) throws Throwable {

        Class<?> interfaceType = painlessLookup.canonicalTypeNameToType(interfaceClass);
        if (interfaceType == null) {
            throw new IllegalArgumentException("type [" + interfaceClass + "] not found");
        }
        PainlessMethod interfaceMethod = painlessLookup.lookupFunctionalInterfacePainlessMethod(interfaceType);
        if (interfaceMethod == null) {
            throw new IllegalArgumentException("Class [" + interfaceClass + "] is not a functional interface");
        }
        int arity = interfaceMethod.typeParameters().size();
        PainlessMethod implMethod = painlessLookup.lookupRuntimePainlessMethod(receiverClass, name, arity);
        if (implMethod == null) {
            throw new IllegalArgumentException(
                "dynamic method [" + typeToCanonicalTypeName(receiverClass) + ", " + name + "/" + arity + "] not found"
            );
        }

        // Charging def-receiver ref: the script was over-captured (receiver type unknown, so no pre-filter). Charge only if
        // the resolved target has an estimator; either way lookupReferenceInternal appends the script and drops it.
        return lookupReferenceInternal(
            painlessLookup,
            functions,
            constants,
            methodHandlesLookup,
            interfaceType,
            PainlessLookupUtility.typeToCanonicalTypeName(implMethod.targetClass()),

View on GitHub (pinned to db6a809a66)

Solutions

  1. Target a true functional interface (exactly one abstract method) — typically from java.util.function.
  2. Check the resolved type's abstract method count; if more than one, choose the specific SAM type you need.
  3. Register a proper functional interface in the allowlist if a custom one is intended.
Defensive patterns

Strategy: type-guard

Type guard

// Verify a type is a functional interface (exactly one abstract method) before using as lambda target.
// In tooling: reflection MethodCount abstract == 1; or restrict to a known SAM set.

Try / catch

// On 'Class [...] is not a functional interface', instruct the author to choose a SAM type from java.util.function.

Prevention

When it happens

Trigger: A Painless lambda/method-reference targets a whitelisted type that is not a functional interface — e.g. an interface with zero or multiple abstract methods, a marker interface, or a class (non-interface) type that happened to be registered.

Common situations: Misidentifying a type with multiple methods as functional. Using a whitelisted non-functional type (e.g. an abstract class) as a lambda target. Allowlist changes that converted a functional interface into a richer one.

Related errors


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