oracle/graal · error · IllegalArgumentException

undecoded bytes

Error message

 undecoded bytes

What it means

IllegalArgumentException from OptionsEncoder.decode when the input byte array decodes its declared count of key/value pairs but bytes still remain in the stream (in.available() != 0). This means the payload is not a self-consistent OptionsEncoder payload: it was truncated/corrupted, produced by a different writer, or is raw data that merely looks like encoded options.

Source

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

        }
    }

    /**
     * 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))) {
            final int size = in.readInt();
            for (int i = 0; i < size; i++) {
                final String key = in.readUTF();
                final Object value = in.readTypedValue();
                res.put(key, value);
            }
            if (in.available() != 0) {
                throw new IllegalArgumentException(in.available() + " undecoded bytes");
            }
        } catch (IOException ioe) {
            throw new IllegalArgumentException(ioe);
        }
        return res;
    }
}

View on GitHub (pinned to a66e9ccd1d)

Solutions

  1. Verify provenance: re-encode the options on the same GraalVM build and compare lengths — a length mismatch confirms corruption or version skew.
  2. If the payload came from storage/network, re-obtain or re-transfer it (it is not recoverable client-side).
  3. Ensure encode and decode run on the same compiler version / same TypedDataInputStream tag set.
  4. Wrap decode at the trust boundary and reject malformed payloads explicitly rather than letting the exception propagate mid-initialization.

Example fix

// before
Map<String,Object> opts = OptionsEncoder.decode(readBytesFromFile(f));

// after: validate payload integrity before decoding
byte[] b = readBytesFromFile(f);
if (b.length < 4) throw new IllegalArgumentException("payload too short");
Map<String,Object> opts = OptionsEncoder.decode(b);
Defensive patterns

Strategy: validation

Validate before calling

// Cheap integrity check before decode: first 4 bytes declare entry count;
// verify payload length is at least a minimal encoded body
static boolean looksLikeEncodedOptions(byte[] b) {
    if (b == null || b.length < 6) return false;
    int count = ((b[0] & 0xFF) << 24) | ((b[1] & 0xFF) << 16) | ((b[2] & 0xFF) << 8) | (b[3] & 0xFF);
    return count >= 0 && count * 4 <= b.length; // each entry needs tag+len at minimum
}

Try / catch

try {
    return OptionsEncoder.decode(payload);
} catch (IllegalArgumentException e) {
    if (e.getMessage() != null && e.getMessage().endsWith("undecoded bytes")) {
        logCorruptPayload(payloadId); // re-fetch / discard artifact
        return Map.of();
    }
    throw e;
}

Prevention

When it happens

Trigger: Calling OptionsEncoder.decode(input) where input was truncated, padded, concatenated with other data, or produced by a mismatched encoder (different type-tag scheme or version) — readInt consumed the size, the loop consumed size entries, and trailing bytes are left over.

Common situations: Reading encoded options embedded in a compiled artifact after a partial write or file corruption; version skew where the encoder wrote extra fields the decoder loop does not consume; feeding an arbitrary byte[] (e.g. from a different format) into decode by mistake.

Related errors


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