nautechsystems/nautilus_trader · error

CustomData envelope type '{envelope_type_name}' does not mat

Error message

CustomData envelope type '{envelope_type_name}' does not match message type '{custom_type_name}'

What it means

CustomData payloads are decoded by looking up a registered custom data type from the envelope's 'type' field, then verifying the envelope type name equals the registered message type name. If the JSON envelope's 'type' does not match the type the registry resolved for the payload, decoding aborts because the envelope and body would disagree about what data is being carried.

Source

Thrown at crates/common/src/msgbus/external/mod.rs:585

    }
}

fn decode_custom_data_value(
    custom_type_name: Ustr,
    value: &serde_json::Value,
) -> anyhow::Result<Option<CustomData>> {
    let Some(data) = deserialize_custom_from_json(custom_type_name.as_str(), value)? else {
        log::warn!(
            "External custom payload type '{custom_type_name}' is not registered for inbound republishing"
        );
        return Ok(None);
    };

    let envelope_type_name = value
        .get("type")
        .and_then(serde_json::Value::as_str)
        .context("CustomData JSON missing 'type' field")?;
    anyhow::ensure!(
        envelope_type_name == custom_type_name.as_str(),
        "CustomData envelope type '{envelope_type_name}' does not match message type '{custom_type_name}'"
    );

    let Data::Custom(custom) = data else {
        anyhow::bail!("CustomData registry returned non-custom data");
    };

    Ok(Some(custom))
}

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Ensure the JSON 'type' field exactly equals the custom data class name (to_string of the registered type)
  2. Re-serialize/re-stamp old persisted messages after renaming a custom data type
  3. Check for case or namespace differences between the envelope 'type' and the registered type name
  4. Verify the CustomData registration (registry mapping) points at the same type used to publish

Example fix

// before
let msg = json!({"type": "Quotes", "data": body});
// after (custom type registered as "QuoteTick")
let msg = json!({"type": "QuoteTick", "data": body});
Defensive patterns

Strategy: validation

Validate before calling

def assert_envelope_matches(msg: dict, registered_type: str) -> None:
    t = msg.get("type")
    if not isinstance(t, str) or not t:
        raise ValueError("CustomData JSON missing 'type' field")
    if t != registered_type:
        raise ValueError(f"envelope type '{t}' != registered '{registered_type}'")

Type guard

def envelope_matches(msg: dict, expected: str) -> bool:
    return isinstance(msg, dict) and msg.get("type") == expected

Try / catch

try:
    data = decode_custom_data_payload(json_bytes)
except ValueError as e:
    log.error(f"CustomData decode failed: {e}")
    metrics.decode_mismatch.inc()
    return None

Prevention

When it happens

Trigger: Calling decode_custom_data_payload on JSON whose 'type' string differs from the custom data type name registered for the message; e.g. publishing {'type': 'MyIndicator', ...} while the payload registers/decodes as 'MyOtherType', or type names changed between versions.

Common situations: Renaming a custom data class after persisting messages, publishing with a hand-written 'type' string that has a typo or wrong casing, cross-version replay where the custom type was renamed, or registering the wrong custom type for a topic.

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 nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/14f6f7d601ed76c6. Report an issue: GitHub.