{"record":{"id":"a259219dfd8f6af6","repo":"oracle/graal","slug":"guest-handler-does-not-implement-expected-api","errorCode":null,"errorMessage":"guest handler does not implement expected API","messagePattern":"guest handler does not implement expected API","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/redefinition/plugins/impl/ExternalPluginHandler.java","lineNumber":55,"sourceCode":"    private static final String RERUN_CLINIT = \"shouldRerunClassInitializer\";\n    private static final String POST_HOTSWAP = \"postHotSwap\";\n\n    private final InteropLibrary interopLibrary;\n    private final StaticObject guestHandler;\n\n    private ExternalPluginHandler(StaticObject handler, InteropLibrary library) {\n        this.guestHandler = handler;\n        this.interopLibrary = library;\n    }\n\n    public static ExternalPluginHandler create(StaticObject guestHandler) throws IllegalArgumentException {\n        InteropLibrary library = InteropLibrary.getUncached(guestHandler);\n\n        boolean invocable = library.isMemberInvocable(guestHandler, RERUN_CLINIT) &&\n                        library.isMemberInvocable(guestHandler, POST_HOTSWAP);\n\n        if (!invocable) {\n            throw new IllegalArgumentException(\"guest handler does not implement expected API\");\n        }\n        return new ExternalPluginHandler(guestHandler, library);\n    }\n\n    public boolean shouldRerunClassInitializer(Klass klass, boolean changed) {\n        try {\n            return (boolean) interopLibrary.invokeMember(guestHandler, RERUN_CLINIT, klass.mirror(), changed);\n        } catch (UnsupportedMessageException | UnknownIdentifierException | UnsupportedTypeException | ArityException e) {\n            klass.getContext().getLogger().severe(() -> ExternalPluginHandler.class.getName() + \": shouldRerunClassInitializer: \" + e.getMessage());\n        }\n        return false;\n    }\n\n    public void postHotSwap(Klass[] changedKlasses) {\n        Meta meta = changedKlasses[0].getMeta();\n        try {\n            StaticObject[] guestClasses = new StaticObject[changedKlasses.length];\n            for (int i = 0; i < guestClasses.length; i++) {","sourceCodeStart":37,"sourceCodeEnd":73,"githubUrl":"https://github.com/oracle/graal/blob/a66e9ccd1d7bf2552883939aa0788dfd0e294aab/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/redefinition/plugins/impl/ExternalPluginHandler.java#L37-L73","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","Verify with InteropLibrary before registering: isMemberInvocable(handler, \"shouldRerunClassInitializer\") and the post-hotswap member.","Ensure you pass the handler instance itself, not a builder/factory/class object.","Recompile the plugin against the exact Espresso version in use to pick up any renamed members."],"exampleFix":"// before: guest handler missing the post-hotswap member\npublic class MyPlugin {\n    public boolean shouldRerunClassInitializer(Class<?> k, boolean c) { return c; }\n}\n// after: implement the full contract\npublic class MyPlugin {\n    public boolean shouldRerunClassInitializer(Class<?> k, boolean c) { return c; }\n    public void postHotSwap(Class<?> k) { /* notify listeners */ }\n}","handlingStrategy":"type-guard","validationCode":"// before registering, verify both members are invocable\nInteropLibrary lib = InteropLibrary.getUncached(handler);\nboolean ok;\ntry {\n    ok = lib.isMemberInvocable(handler, \"shouldRerunClassInitializer\") &&\n         lib.isMemberInvocable(handler, \"postHotSwap\");\n} catch (UnsupportedMessageException e) { ok = false; }\nif (!ok) throw new IllegalArgumentException(\"handler lacks plugin API members\");","typeGuard":"// Java host-side guard for a guest handler object\nstatic boolean isValidPluginHandler(Object handler) {\n    InteropLibrary lib = InteropLibrary.getUncached(handler);\n    try {\n        return lib.isMemberInvocable(handler, \"shouldRerunClassInitializer\") &&\n               lib.isMemberInvocable(handler, \"postHotSwap\");\n    } catch (UnsupportedMessageException e) {\n        return false;\n    }\n}","tryCatchPattern":"catch (IllegalArgumentException e) { // thrown by ExternalPluginHandler.create\n    // message \"guest handler does not implement expected API\": fix the guest class and re-register\n}","preventionTips":["Compile guest plugin handlers against the same Espresso version as the runtime.","Add a registration-time contract test that invokes both members with sample inputs.","Pass the handler instance, never its Class or a factory object."],"tags":["espresso","hotswap","plugins","interop","guest-language"],"backgroundTag":null,"analyzedSha":"a66e9ccd1d7bf2552883939aa0788dfd0e294aab","analyzedAt":"2026-08-14T13:58:47.161Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}