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
- Target a true functional interface (exactly one abstract method) — typically from java.util.function.
- Check the resolved type's abstract method count; if more than one, choose the specific SAM type you need.
- 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
- Target only single-abstract-method interfaces for lambdas.
- Validate the chosen type has exactly one abstract method in script authoring tooling.
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
- type [{}] not found
- dynamic method [{}, {}/{}] not found
- Illegal list shortcut value [{}].
- dynamic getter [{}, {}] not found
- Illegal list shortcut value [name].
AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12).
Data as JSON: /api/errors/ba82f97b50c6bd26.
Report an issue: GitHub.