oracle/graal · error · IllegalArgumentException
Bad receiver: expected Object, got {}
Error message
Bad receiver: expected Object, got {} What it means
Thrown by EspressoExternalResolvedJavaMethod.invoke when an instance method's receiver JavaConstant is not an EspressoExternalObjectConstant. Like the field-write guard, the adapter needs the guest polyglot Value inside EspressoExternalObjectConstant to place at args[0] for the interop execute; constants from other JVMCI backends cannot be executed on. The message text prints arguments[0].getJavaKind(), which is misleading (a copy-paste from the argument loop) — the real problem is the receiver constant's provenance.
Source
Thrown at espresso-compiler-stub/src/com.oracle.truffle.espresso.vmaccess/src/com/oracle/truffle/espresso/vmaccess/EspressoExternalResolvedJavaMethod.java:307
throw new NullPointerException("For instance methods, the receiver argument must not be null");
} else if (receiver.isNull()) {
throw new IllegalArgumentException("For instance methods, the receiver argument must not represent a null constant");
}
AbstractEspressoSignature signature = getSignature();
int parameterCount = signature.getParameterCount(false);
if (parameterCount != arguments.length) {
throw new IllegalArgumentException("Expected " + parameterCount + " arguments, got " + arguments.length);
}
Object[] args = new Object[arguments.length + (isConstructor() || !isStatic() ? 1 : 0)];
int outputArgumentOffset = 0;
EspressoExternalVMAccess access = getAccess();
if (isConstructor()) {
EspressoExternalResolvedInstanceType type = (EspressoExternalResolvedInstanceType) getDeclaringClass();
args[0] = access.unsafeAllocateInstance(type).getValue();
outputArgumentOffset = 1;
} else if (!isStatic()) {
if (!(receiver instanceof EspressoExternalObjectConstant objectConstant)) {
throw new IllegalArgumentException("Bad receiver: expected Object, got " + arguments[0].getJavaKind());
}
args[0] = objectConstant.getValue();
outputArgumentOffset = 1;
}
for (int i = 0; i < parameterCount; i++) {
JavaConstant argument = arguments[i];
JavaKind argumentKind = argument.getJavaKind();
/*
* Perform widening primitive conversions (JLS 5.1.2) in order to implement strict
* method invocation conversions (JLS 5.3). Also promote to stack kind.
*/
args[i + outputArgumentOffset] = switch (signature.getParameterKind(i)) {
case Boolean -> switch (argumentKind) {
case Boolean -> argument.asBoolean() ? 1 : 0;
default ->
throw new IllegalArgumentException("Bad argument kind at index " + i + ": expected Boolean, got " + argumentKind);
};
case Byte -> switch (argumentKind) {View on GitHub (pinned to a66e9ccd1d)
Solutions
- Obtain the receiver via the same EspressoExternalVMAccess (constant reflection or snippet reflection) so it is an EspressoExternalObjectConstant
- Verify with instanceof before invoke and fail with a clear message naming the producer
- Add an assertion in test builds that every constant crossing into invoke comes from the Espresso access
Example fix
// before method.invoke(hostProvider.forObject(obj), args); // after JavaConstant receiver = espressoAccess.getConstantReflection().forObject(guestValue); assert !(receiver instanceof EspressoExternalObjectConstant eoc) || eoc.getValue().getMetaObject() != null; method.invoke(receiver, args);
Defensive patterns
Strategy: type-guard
Validate before calling
if (!method.isStatic() && !method.isConstructor() && !(receiver instanceof EspressoExternalObjectConstant)) {
throw new IllegalStateException("receiver from wrong JVMCI backend: " + receiver.getClass().getName());
} Prevention
- Note the message misreports the receiver problem as arguments[0].getJavaKind() — trust the guard, not the text
- Keep one constant-reflection provider per pipeline; never cache constants across backends
- Add provenance assertions on receivers in test builds to catch mixing early
When it happens
Trigger: Invoking an instance method with a receiver constant created by a host ConstantReflectionProvider, a mock JavaConstant, or another VM access instance. The bug is always at the call site that sourced the receiver.
Common situations: Shared compiler infrastructure running against both a host JVMCI and the Espresso external vmaccess, with receiver constants cached across backends; partial migrations to the espresso-compiler-stub where only some providers were swapped.
Related errors
- For static methods or constructors, the receiver argument mu
- For instance methods, the receiver argument must not represe
- Bad argument kind at index {}: expected Object, got {} wrapp
- Expected an espresso object receiver, got {}
- Expected an espresso object constant, got {}
AI-assisted analysis of oracle/graal@a66e9ccd1d (2026-08-14).
Data as JSON: /api/errors/992f7aad1de7343d.
Report an issue: GitHub.