baomidou/mybatis-plus · error · MybatisPlusException

Unsupported proxy type for Groovy lambda extraction: {}. Exp

Error message

Unsupported proxy type for Groovy lambda extraction: {}. Expected a Groovy ConvertedClosure handler exposing getDelegate().

What it means

GroovyLambdaMeta extracts the instantiated method/owner from a Groovy closure used as a lambda (e.g. in QueryWrapper lambdas under Groovy). It expects the closure to be a JDK Proxy whose InvocationHandler is a Groovy ConvertedClosure exposing getDelegate(). If handler.getClass().getMethod("getDelegate") throws NoSuchMethodException, mybatis-plus reports MybatisPlusException('Unsupported proxy type for Groovy lambda extraction: <handler class>').

Source

Thrown at mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/toolkit/support/GroovyLambdaMeta.java:50

 * @since 3.5.12
 */
public class GroovyLambdaMeta implements LambdaMeta {

    private final Class<?> clazz;
    private final String name;

    public GroovyLambdaMeta(Proxy func) {
        InvocationHandler handler = Proxy.getInvocationHandler(func);
        try {
            Method getDelegate = handler.getClass().getMethod("getDelegate");
            Object delegate = getDelegate.invoke(handler);
            Method getMethod = delegate.getClass().getMethod("getMethod");
            this.name = (String) getMethod.invoke(delegate);
            Method getOwner = delegate.getClass().getMethod("getOwner");
            Object owner = getOwner.invoke(delegate);
            this.clazz = (owner instanceof Class) ? (Class<?>) owner : owner.getClass();
        } catch (NoSuchMethodException e) {
            throw new MybatisPlusException("Unsupported proxy type for Groovy lambda extraction: "
                + handler.getClass().getName()
                + ". Expected a Groovy ConvertedClosure handler exposing getDelegate().", e);
        } catch (Exception e) {
            throw new MybatisPlusException("Failed to extract Groovy lambda meta from proxy handler: "
                + handler.getClass().getName(), e);
        }
    }

    @Override
    public String getImplMethodName() {
        return name;
    }

    @Override
    public Class<?> getInstantiatedClass() {
        return clazz;
    }

View on GitHub (pinned to bf67d90747)

Solutions

  1. Align Groovy versions with what the mybatis-plus release supports (check the release notes for Groovy support matrix).
  2. Avoid Groovy closure syntax in wrapper APIs: use the string/column-name wrappers (QueryWrapper.eq("col", val)) or explicit SFunction from Java.
  3. If you build custom proxies around closures, ensure the handler exposes getDelegate()/getMethod()/getOwner() like ConvertedClosure does.
  4. Upgrade mybatis-plus — Groovy lambda extraction robustness improved across 3.5.x patches.

Example fix

// before (Groovy): closure routed to GroovyLambdaMeta with unsupported handler
wrapper.eq({ it.name }, 'bob') // may explode on Groovy internals

// after: use column-name wrappers under Groovy
wrapper.eq('name', 'bob')
Defensive patterns

Strategy: validation

Validate before calling

Object handler = Proxy.getInvocationHandler(func);
boolean groovyConvertible = false;
try {
    handler.getClass().getMethod("getDelegate");
    groovyConvertible = true;
} catch (NoSuchMethodException ignored) { }
if (!groovyConvertible) { /* use column-name wrappers instead */ }

Type guard

static boolean isGroovyConvertedClosure(Object func) {
    if (!(func instanceof Proxy)) return false;
    try {
        Proxy.getInvocationHandler(func).getClass().getMethod("getDelegate");
        return true;
    } catch (NoSuchMethodException e) {
        return false;
    }
}

Try / catch

try {
    wrapper.eq(SFunctionCast.toSFunction(closure), value);
} catch (MybatisPlusException e) {
    // fall back to column-name condition when Groovy lambda extraction is unsupported
    wrapper.eq("column_name", value);
}

Prevention

When it happens

Trigger: Using lambda QueryWrapper/LambdaQueryWrapper syntax inside Groovy code where the 'lambda' the framework receives is a plain Groovy Closure wrapped by a proxy whose invocation handler is not ConvertedClosure (or a different Groovy version's internal layout). The reflective contract on Groovy internals no longer holds.

Common situations: Groovy version upgrades that change closure proxy internals (MethodClosure vs ConvertedClosure handling); Grape/Groovy 4 vs 3 differences; passing a method reference or custom proxy where a true ConvertedClosure is expected; non-Groovy proxies routed into the Groovy branch of lambda resolution.

Related errors


AI-assisted analysis of baomidou/mybatis-plus@bf67d90747 (2026-08-14). Data as JSON: /api/errors/3b6d876887e63219. Report an issue: GitHub.