oracle/graal · error · IllegalArgumentException

Unexpected value: ${instance}

Error message

Unexpected value: ${instance}

What it means

The primitive-constant serializer accepts only Integer, Byte, Short, Long, Float, and Double and encodes them as hex strings. Serialize() throws IllegalArgumentException('Unexpected value: ...') when it receives any other wrapper type — typically Boolean, Character, or a boxed value from a different serializer version.

Source

Thrown at compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/hotspot/replaycomp/JsonReplayCodec.java:456

            return Number.class;
        }

        @Override
        public String tag() {
            return "number";
        }

        @Override
        public void serialize(Object instance, JsonBuilder.ObjectBuilder objectBuilder, RecursiveSerializer serializer) throws IOException {
            objectBuilder.append("class", instance.getClass().getSimpleName());
            String value = switch (instance) {
                case Integer n -> Integer.toHexString(n);
                case Byte n -> Integer.toHexString(n);
                case Short n -> Integer.toHexString(n);
                case Long n -> Long.toHexString(n);
                case Float n -> floatToHex(n);
                case Double n -> doubleToHex(n);
                default -> throw new IllegalArgumentException("Unexpected value: " + instance);
            };
            objectBuilder.append("value", value);
        }

        @Override
        public Object deserialize(EconomicMap<String, Object> json, RecursiveDeserializer deserializer, CompilationProxies.ProxyFactory proxyFactory) {
            String className = (String) json.get("class");
            String value = (String) json.get("value");
            switch (className) {
                case "Integer" -> {
                    return Integer.parseUnsignedInt(value, HEX_RADIX);
                }
                case "Byte" -> {
                    return (byte) Integer.parseUnsignedInt(value, HEX_RADIX);
                }
                case "Short" -> {
                    return (short) Integer.parseUnsignedInt(value, HEX_RADIX);
                }

View on GitHub (pinned to a66e9ccd1d)

Solutions

  1. Add Boolean/Character cases to the switch in JsonReplayCodec.java:456 (serialize 'true'/'false' or the char) and matching deserialize cases
  2. Check the chained value in the message to confirm exactly which type leaked through, then trace which serializer should have claimed it
  3. Update both halves of the codec (serialize AND deserialize) so the file stays round-trippable

Example fix

// before
String value = switch (instance) {
    case Integer n -> Integer.toHexString(n);
    // ...
    default -> throw new IllegalArgumentException("Unexpected value: " + instance);
};
// after: add
    case Boolean b -> Boolean.toString(b);
    case Character c -> Integer.toHexString(c);
// and the mirror cases in deserialize(...)
Defensive patterns

Strategy: type-guard

Type guard

static boolean isSupportedPrimitiveConstant(Object v) {
    return v instanceof Integer || v instanceof Byte || v instanceof Short
        || v instanceof Long || v instanceof Float || v instanceof Double;
}

Prevention

When it happens

Trigger: Calling the JSON replay serializer on a recorded constant that is a Boolean or Character (or Void/other boxed primitive) — the pattern match in the switch falls through to default.

Common situations: A recorded compiler-interface method takes a boolean or char parameter and no dedicated serializer covers it; GraalVM version drift that starts recording a previously unsupported parameter type; custom compiler-interface additions.

Related errors


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