oracle/graal · error · IllegalArgumentException

Key: %s, Value: %s, Value type: %s

Error message

Key: %s, Value: %s, Value type: %s

What it means

IllegalArgumentException from OptionsEncoder.encode when one of the values in the options map cannot be serialized: the underlying TypedDataOutputStream.writeTypedValue rejects its type and encode rethrows with the offending key, value, and value class attached. OptionsEncoder is used to persist/transfer compiler option maps (e.g. encoded Truffle compiler options attached to compiled artifacts), so this means an option value of a non-encodable type entered the map.

Source

Thrown at compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/util/OptionsEncoder.java:55

    private OptionsEncoder() {
    }

    /**
     * Encodes {@code options} into a byte array.
     *
     * @throws IllegalArgumentException if any value in {@code options} is not supported
     */
    public static byte[] encode(final Map<String, Object> options) {
        try (ByteArrayOutputStream baout = new ByteArrayOutputStream()) {
            try (TypedDataOutputStream out = new TypedDataOutputStream(baout)) {
                out.writeInt(options.size());
                for (Map.Entry<String, Object> e : options.entrySet()) {
                    out.writeUTF(e.getKey());
                    try {
                        out.writeTypedValue(e.getValue());
                    } catch (IllegalArgumentException iae) {
                        throw new IllegalArgumentException(String.format("Key: %s, Value: %s, Value type: %s",
                                        e.getKey(), e.getValue(), e.getValue().getClass()), iae);
                    }
                }
            }
            return baout.toByteArray();
        } catch (IOException ioe) {
            throw new IllegalArgumentException(ioe);
        }
    }

    /**
     * Decodes {@code input} into a name/value map.
     *
     * @throws IllegalArgumentException if {@code input} cannot be decoded
     */
    public static Map<String, Object> decode(byte[] input) {
        Map<String, Object> res = new LinkedHashMap<>();
        try (TypedDataInputStream in = new TypedDataInputStream(new ByteArrayInputStream(input))) {

View on GitHub (pinned to a66e9ccd1d)

Solutions

  1. Read the message: it names the exact Key, Value, and Value type — remove that entry or convert it to a supported type (String, Integer, Long, Float, Double, Boolean, Character, byte[], enum).
  2. If the value is a custom object, serialize it to a String (e.g. toString or JSON) before putting it in the options map.
  3. If it is a legitimately new option type, extend TypedDataOutputStream.writeTypedValue (and the matching reader) with a new type tag instead of working around the exception.
  4. Check for version skew: encode and decode must run on GraalVM builds with the same supported-type set.

Example fix

// before
Map<String,Object> opts = new HashMap<>();
opts.put("myUri", URI.create("http://x")); // not encodable
OptionsEncoder.encode(opts);

// after
opts.put("myUri", URI.create("http://x").toString()); // String is encodable
OptionsEncoder.encode(opts);
Defensive patterns

Strategy: type-guard

Validate before calling

static final Set<Class<?>> SUPPORTED = Set.of(
    Boolean.class, Character.class, Integer.class, Long.class,
    Float.class, Double.class, String.class, byte[].class);

static boolean isEncodable(Object v) {
    return v == null || SUPPORTED.contains(v.getClass()) || v.getClass().isEnum();
}

// before encode
options.forEach((k, v) -> { if (!isEncodable(v)) throw new IllegalArgumentException(k + " -> " + v.getClass()); });

Type guard

static boolean isEncodableOptionValue(Object v) {
    if (v == null) return true;
    Class<?> c = v.getClass();
    return c == Boolean.class || c == Character.class || c == Integer.class
        || c == Long.class || c == Float.class || c == Double.class
        || c == String.class || c == byte[].class || c.isEnum();
}

Try / catch

try {
    byte[] enc = OptionsEncoder.encode(options);
} catch (IllegalArgumentException e) {
    // message already names Key/Value/type — surface it to config diagnostics
    throw new ConfigException("non-encodable option: " + e.getMessage(), e);
}

Prevention

When it happens

Trigger: Calling OptionsEncoder.encode(map) where map contains a value whose class is not one of the supported primitive/String/Enum/byte[] types handled by TypedDataOutputStream.writeTypedValue — for example a custom object, java.net.URI, or a boxed type added by a new option.

Common situations: Adding a new compiler option whose value type is not in the typed-stream whitelist and then encoding options for storage or IPC; mixing option maps across GraalVM versions where a value type changed; passing a decoded-then-mutated map back in with an extra debugging entry.

Related errors


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