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.

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)

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.