oracle/graal · error · IllegalArgumentException

guest handler does not implement expected API

Error message

guest handler does not implement expected API

What it means

IllegalArgumentException thrown by ExternalPluginHandler.create when a guest-language object registered as a redefinition plugin handler does not expose the two required interop members (RERUN_CLINIT and POST_HOTSWAP, checked via InteropLibrary.isMemberInvocable). Espresso integrates external hotswap plugins by calling those members, so a guest handler missing either method cannot be used and construction fails.

Source

Thrown at espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/redefinition/plugins/impl/ExternalPluginHandler.java:55

    private static final String RERUN_CLINIT = "shouldRerunClassInitializer";
    private static final String POST_HOTSWAP = "postHotSwap";

    private final InteropLibrary interopLibrary;
    private final StaticObject guestHandler;

    private ExternalPluginHandler(StaticObject handler, InteropLibrary library) {
        this.guestHandler = handler;
        this.interopLibrary = library;
    }

    public static ExternalPluginHandler create(StaticObject guestHandler) throws IllegalArgumentException {
        InteropLibrary library = InteropLibrary.getUncached(guestHandler);

        boolean invocable = library.isMemberInvocable(guestHandler, RERUN_CLINIT) &&
                        library.isMemberInvocable(guestHandler, POST_HOTSWAP);

        if (!invocable) {
            throw new IllegalArgumentException("guest handler does not implement expected API");
        }
        return new ExternalPluginHandler(guestHandler, library);
    }

    public boolean shouldRerunClassInitializer(Klass klass, boolean changed) {
        try {
            return (boolean) interopLibrary.invokeMember(guestHandler, RERUN_CLINIT, klass.mirror(), changed);
        } catch (UnsupportedMessageException | UnknownIdentifierException | UnsupportedTypeException | ArityException e) {
            klass.getContext().getLogger().severe(() -> ExternalPluginHandler.class.getName() + ": shouldRerunClassInitializer: " + e.getMessage());
        }
        return false;
    }

    public void postHotSwap(Klass[] changedKlasses) {
        Meta meta = changedKlasses[0].getMeta();
        try {
            StaticObject[] guestClasses = new StaticObject[changedKlasses.length];
            for (int i = 0; i < guestClasses.length; i++) {

View on GitHub (pinned to a66e9ccd1d)

Solutions

  1. Make the guest handler class expose both required members exactly as the API defines (check RERUN_CLINIT/POST_HOTSWAP names in the Espresso plugin contract for your version) as public instance methods.
  2. Verify with InteropLibrary before registering: isMemberInvocable(handler, "shouldRerunClassInitializer") and the post-hotswap member.
  3. Ensure you pass the handler instance itself, not a builder/factory/class object.
  4. Recompile the plugin against the exact Espresso version in use to pick up any renamed members.

Example fix

// before: guest handler missing the post-hotswap member
public class MyPlugin {
    public boolean shouldRerunClassInitializer(Class<?> k, boolean c) { return c; }
}
// after: implement the full contract
public class MyPlugin {
    public boolean shouldRerunClassInitializer(Class<?> k, boolean c) { return c; }
    public void postHotSwap(Class<?> k) { /* notify listeners */ }
}
Defensive patterns

Strategy: type-guard

Validate before calling

// before registering, verify both members are invocable
InteropLibrary lib = InteropLibrary.getUncached(handler);
boolean ok;
try {
    ok = lib.isMemberInvocable(handler, "shouldRerunClassInitializer") &&
         lib.isMemberInvocable(handler, "postHotSwap");
} catch (UnsupportedMessageException e) { ok = false; }
if (!ok) throw new IllegalArgumentException("handler lacks plugin API members");

Type guard

// Java host-side guard for a guest handler object
static boolean isValidPluginHandler(Object handler) {
    InteropLibrary lib = InteropLibrary.getUncached(handler);
    try {
        return lib.isMemberInvocable(handler, "shouldRerunClassInitializer") &&
               lib.isMemberInvocable(handler, "postHotSwap");
    } catch (UnsupportedMessageException e) {
        return false;
    }
}

Try / catch

catch (IllegalArgumentException e) { // thrown by ExternalPluginHandler.create
    // message "guest handler does not implement expected API": fix the guest class and re-register
}

Prevention

When it happens

Trigger: Registering an external redefinition plugin handler object (e.g. a guest class instance passed to Espresso's plugin API) whose class does not declare public, invocable members named per the plugin contract (class initializer rerun callback and post-hotswap callback), or declares them with wrong visibility/static-ness so they are not interop-invocable.

Common situations: Implementing a custom hotswap plugin against an older/newer Espresso plugin API where the required method names changed; passing the wrong object (e.g. the plugin's factory or config object) instead of the handler instance; handler methods not public so the interop layer does not expose them.

Related errors


AI-assisted analysis of oracle/graal@a66e9ccd1d (2026-08-14). Data as JSON: /api/errors/a259219dfd8f6af6. Report an issue: GitHub.