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
- 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.
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
- 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.
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.