dotnet/orleans · error · AggregateException

Unable to convert from storage format GrainStateEntity.Data=

Error message

Unable to convert from storage format GrainStateEntity.Data={entity.State}Data Value={dataValue} Type={dataValue.GetType()}

What it means

Thrown by the StateEntity overload of ConvertFromStorageFormat<T> when this.serializer.Deserialize<T>(entity.State) throws while turning a stored StateEntity's binary State back into a TState. The library wraps the deserializer's exception in an AggregateException whose message echoes the raw Data bytes and, if a partial value exists, the partially-deserialized value and its type. A failure here means the bytes in DynamoDB cannot be turned into the requested CLR type by the configured IGrainStorageSerializer.

Solutions

  1. Identify which StateEntity row is failing from the log (PartitionKey/SequenceId) and inspect its State bytes.
  2. Revert or migrate the TState type / serializer to be backward-compatible with the persisted rows, or implement a custom IGrainStorageSerializer that can read the legacy format.
  3. If the row is definitively corrupt, delete the offending state row(s) for that grain so a fresh state is created (data loss for that grain).
  4. Pin the same GrainStorageSerializer and TState shape across all deployments that share the table.

Example fix

// before: serializer changed between releases
options.GrainStorageSerializer = new JsonGrainStorageSerializer();

// after: keep a stable, version-tolerant serializer across releases
options.GrainStorageSerializer = new OrleansPersistenceCustomJsonCodec(/* config with version tolerance */);
Defensive patterns

Strategy: try-catch

Validate before calling

// Before loading, sanity-check that the configured serializer can round-trip your state type
var sample = new TState();
var bytes = options.GrainStorageSerializer.Serialize(sample).ToArray();
var roundTrip = options.GrainStorageSerializer.Deserialize<TState>(bytes);
if (roundTrip is null) throw new InvalidOperationException("Serializer cannot round-trip TState");

Type guard

static bool CanDeserializeState(IGrainStorageSerializer s, byte[] data)
    where TState : class, new()
{
    try { return s.Deserialize<TState>(data) is not null; }
    catch { return false; }
}

Try / catch

try
{
    await grain.LoadOrRecover();
}
catch (AggregateException ax) when (ax.InnerException is not null
    && ax.Message.Contains("Unable to convert from storage format"))
{
    _logger.LogCritical(ax.InnerException, "State deserialization failed; row format is incompatible with TState");
    // quarantine the grain or reset its state per your data-loss policy
    throw;
}

Prevention

When it happens

Trigger: Produced while recovering pending/prepared transaction states during Load() (each pending StateEntity is deserialized via ConvertFromStorageFormat(StateEntity)). It fires when entity.State is non-empty but the serializer throws: e.g., the TState class changed in an incompatible way, the serializer was changed (e.g., from Newtonsoft.Json to System.Text.Json) without a custom serializer, or the stored bytes are corrupt.

Common situations: Deploying a new version of the grain state class whose fields/types no longer match persisted rows; switching GrainStorageSerializer config between deployments; hand-edited or partially-written rows in the table; encryptor/compression interceptor misconfiguration that was changed since the row was written.

Related errors


AI-assisted analysis of dotnet/orleans@fca799fa70 (2026-08-13). Data as JSON: /api/errors/6a2477bfc7a70697. Report an issue: GitHub.

Appendix: source

Thrown at src/AWS/Orleans.Transactions.DynamoDB/TransactionalState/DynamoDBTransactionalStateStorage.cs:418

        }
    }

    private T ConvertFromStorageFormat<T>(StateEntity entity)
    {
        T dataValue = default!;
        try
        {
            if (entity.State is { Length: > 0 })
                dataValue = this.serializer.Deserialize<T>(entity.State)!;
        }
        catch (Exception exc)
        {
            var message = dataValue is not null
                ? $"Unable to convert from storage format GrainStateEntity.Data={entity.State}Data Value={dataValue} Type={dataValue.GetType()}"
                : $"Unable to convert from storage format GrainStateEntity.Data={entity.State}";

            LogError(logger, message);
            throw new AggregateException(message, exc);
        }

        return dataValue;
    }

    private T ConvertFromStorageFormat<T>(byte[] value)
    {
        T dataValue = default!;

        try
        {
            if (value is { Length: > 0 })
                dataValue = this.serializer.Deserialize<T>(value)!;
        }
        catch (Exception exc)
        {
            var message = $"Unable to convert from storage format, Data={value}, DataLen={value?.Length ?? -1}, StateType={typeof(T)}";
            LogError(logger, message);

View on GitHub (pinned to fca799fa70)