HMCL-dev/HMCL · error · JsonParseException
Protected payload is not a
Error message
Protected payload is not a
What it means
Thrown by ProtectionMode.read(envelope, payloadType) after the payload is successfully revealed but is not an instance of the requested Gson element type. The envelope decrypted fine; the caller simply asked for the wrong JSON element type (e.g. JsonObject when the payload is actually a JsonPrimitive or JsonArray). Note the message uses payloadType.getSimpleName(), so it names the EXPECTED type, not the actual one.
Solutions
- Inspect the actual payload shape (e.g. reveal once with JsonElement.class) and request the matching Class: JsonObject, JsonArray, JsonPrimitive, or JsonNull.
- Update the reader to the current schema if the payload layout changed in a newer version of the writing code.
- If the shape is variable, call read(envelope, JsonElement.class) and switch on isJsonObject()/isJsonArray()/isJsonPrimitive() before consuming.
Example fix
// before
JsonObject obj = ProtectedPayload.read(envelope, JsonObject.class); // payload is actually an array
// after
JsonElement element = ProtectedPayload.read(envelope, JsonElement.class);
if (!element.isJsonObject()) {
throw new JsonParseException("Expected object payload, got: " + element);
}
JsonObject obj = element.getAsJsonObject(); Defensive patterns
Strategy: type-guard
Type guard
static JsonObject readObjectPayload(JsonObject envelope) {
JsonElement payload = ProtectedPayload.read(envelope, JsonElement.class);
if (!payload.isJsonObject()) {
throw new JsonParseException("Expected JsonObject payload, got: " + payload.getClass().getSimpleName());
}
return payload.getAsJsonObject();
} Try / catch
try {
return ProtectedPayload.read(envelope, JsonObject.class);
} catch (JsonParseException e) {
throw new SchemaMismatchException("Payload does not match expected shape", e);
} Prevention
- Read as JsonElement and narrow with isJsonObject()/isJsonArray() before requesting a concrete type.
- Keep writer and reader payloads in the same schema module/tests to catch shape drift early.
- Remember the message names the EXPECTED type — check what the writer actually serialized.
When it happens
Trigger: ProtectedPayload.read(envelope, JsonObject.class) on an envelope whose payload is a JsonArray, JsonPrimitive, or JsonNull; likewise read(..., JsonArray.class) on an object payload; calling with a concrete type when the writer serialized a different element shape.
Common situations: A schema change moved the payload from an object to an array (or wrapped it) between versions; requesting JsonObject by habit when the stored value is a string/number; reading an envelope written by a different feature that stores scalars.
Understand the failure class
Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.
Related errors
- json.toString()
- Expected JsonObject but got
- Config is not an object:
- Protected payload lane is not a string
- Missing protected payload member: nonce
AI-assisted analysis of HMCL-dev/HMCL@24702dc5a0 (2026-09-10).
Data as JSON: /api/errors/edb8652d560f17f5.
Report an issue: GitHub.
Appendix: source
Thrown at HMCL/src/main/java/org/jackhuang/hmcl/setting/ProtectedPayload.java:311
/// Reads the payload from the given envelope.
///
/// @param envelope the envelope object to read from
/// @return the revealed JSON payload
/// @throws JsonParseException if the envelope is malformed or cannot be revealed
protected abstract JsonElement readPayload(JsonObject envelope);
/// Reads the payload from the given envelope and checks its JSON element type.
///
/// @param envelope the envelope object to read from
/// @param payloadType the expected JSON element type
/// @return the revealed JSON payload
/// @param <T> the expected JSON element type
/// @throws JsonParseException if the envelope is malformed, cannot be revealed, or has the wrong payload type
public final <T extends JsonElement> T read(JsonObject envelope, Class<T> payloadType) {
Objects.requireNonNull(payloadType);
JsonElement payload = readPayload(envelope);
if (!payloadType.isInstance(payload)) {
throw new JsonParseException("Protected payload is not a " + payloadType.getSimpleName());
}
return payloadType.cast(payload);
}
/// Returns the write mode selected by a configuration value.
///
/// Unknown values intentionally fall back to obfuscation so opt-in plain storage cannot be enabled by typos.
///
/// @param id the configured protection marker
/// @return the selected write mode
static ProtectionMode fromConfiguredId(@Nullable String id) {
for (ProtectionMode mode : values()) {
if (mode.id.equals(id)) {
return mode;
}
}
return OBFUSCATED_V1;
}View on GitHub (pinned to 24702dc5a0)