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
- Identify which StateEntity row is failing from the log (PartitionKey/SequenceId) and inspect its State bytes.
- 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.
- 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).
- 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
- Never change the TState shape or serializer without a backward-compatible migration path.
- Pin the same IGrainStorageSerializer across all environments that share a table.
- Keep grain state types simple and additive; use [JsonExtensionData] (or equivalent) to tolerate new fields.
- Store a schema/version tag alongside state so you can detect incompatible rows proactively.
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
- Unable to convert from storage format, Data={value}, DataLen
- The transactional state storage provider name is required.
- Configuration for DynamoDBTransactionalStateStorage {this.na
- Configuration for DynamoDBTransactionalStateStorage {this.na
- Configuration for DynamoDBTransactionalStateStorage {this.na
AI-assisted analysis of dotnet/orleans@fca799fa70 (2026-08-13).
Data as JSON: /api/errors/6a2477bfc7a70697.
Report an issue: GitHub.