{"record":{"id":"8c8ded74bac251cd","repo":"oracle/graal","slug":"key-s-value-s-value-type-s","errorCode":null,"errorMessage":"Key: %s, Value: %s, Value type: %s","messagePattern":"Key: (.+?), Value: (.+?), Value type: (.+?)","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/util/OptionsEncoder.java","lineNumber":55,"sourceCode":"\n    private OptionsEncoder() {\n    }\n\n    /**\n     * Encodes {@code options} into a byte array.\n     *\n     * @throws IllegalArgumentException if any value in {@code options} is not supported\n     */\n    public static byte[] encode(final Map<String, Object> options) {\n        try (ByteArrayOutputStream baout = new ByteArrayOutputStream()) {\n            try (TypedDataOutputStream out = new TypedDataOutputStream(baout)) {\n                out.writeInt(options.size());\n                for (Map.Entry<String, Object> e : options.entrySet()) {\n                    out.writeUTF(e.getKey());\n                    try {\n                        out.writeTypedValue(e.getValue());\n                    } catch (IllegalArgumentException iae) {\n                        throw new IllegalArgumentException(String.format(\"Key: %s, Value: %s, Value type: %s\",\n                                        e.getKey(), e.getValue(), e.getValue().getClass()), iae);\n                    }\n                }\n            }\n            return baout.toByteArray();\n        } catch (IOException ioe) {\n            throw new IllegalArgumentException(ioe);\n        }\n    }\n\n    /**\n     * Decodes {@code input} into a name/value map.\n     *\n     * @throws IllegalArgumentException if {@code input} cannot be decoded\n     */\n    public static Map<String, Object> decode(byte[] input) {\n        Map<String, Object> res = new LinkedHashMap<>();\n        try (TypedDataInputStream in = new TypedDataInputStream(new ByteArrayInputStream(input))) {","sourceCodeStart":37,"sourceCodeEnd":73,"githubUrl":"https://github.com/oracle/graal/blob/a66e9ccd1d7bf2552883939aa0788dfd0e294aab/compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/util/OptionsEncoder.java#L37-L73","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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).","If the value is a custom object, serialize it to a String (e.g. toString or JSON) before putting it in the options map.","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.","Check for version skew: encode and decode must run on GraalVM builds with the same supported-type set."],"exampleFix":"// before\nMap<String,Object> opts = new HashMap<>();\nopts.put(\"myUri\", URI.create(\"http://x\")); // not encodable\nOptionsEncoder.encode(opts);\n\n// after\nopts.put(\"myUri\", URI.create(\"http://x\").toString()); // String is encodable\nOptionsEncoder.encode(opts);","handlingStrategy":"type-guard","validationCode":"static final Set<Class<?>> SUPPORTED = Set.of(\n    Boolean.class, Character.class, Integer.class, Long.class,\n    Float.class, Double.class, String.class, byte[].class);\n\nstatic boolean isEncodable(Object v) {\n    return v == null || SUPPORTED.contains(v.getClass()) || v.getClass().isEnum();\n}\n\n// before encode\noptions.forEach((k, v) -> { if (!isEncodable(v)) throw new IllegalArgumentException(k + \" -> \" + v.getClass()); });","typeGuard":"static boolean isEncodableOptionValue(Object v) {\n    if (v == null) return true;\n    Class<?> c = v.getClass();\n    return c == Boolean.class || c == Character.class || c == Integer.class\n        || c == Long.class || c == Float.class || c == Double.class\n        || c == String.class || c == byte[].class || c.isEnum();\n}","tryCatchPattern":"try {\n    byte[] enc = OptionsEncoder.encode(options);\n} catch (IllegalArgumentException e) {\n    // message already names Key/Value/type — surface it to config diagnostics\n    throw new ConfigException(\"non-encodable option: \" + e.getMessage(), e);\n}","preventionTips":["Restrict option maps to String + boxed primitives + enums by convention.","Add a round-trip unit test (encode->decode->equals) for every option set you persist.","Keep encoder and decoder on the same GraalVM build to avoid type-tag skew."],"tags":["serialization","options","encoding","illegal-argument"],"backgroundTag":null,"analyzedSha":"a66e9ccd1d7bf2552883939aa0788dfd0e294aab","analyzedAt":"2026-08-14T13:58:47.161Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}