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

  1. Inspect the actual payload shape (e.g. reveal once with JsonElement.class) and request the matching Class: JsonObject, JsonArray, JsonPrimitive, or JsonNull.
  2. Update the reader to the current schema if the payload layout changed in a newer version of the writing code.
  3. 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

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


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)