dotnet/orleans · error · AggregateException
Unable to convert from storage format, Data={value}, DataLen
Error message
Unable to convert from storage format, Data={value}, DataLen={value?.Length ?? -1}, StateType={typeof(T)} What it means
Thrown by the byte[] overload of ConvertFromStorageFormat<T> when this.serializer.Deserialize<T>(value) fails converting a raw byte[] back into the committed transactional state TState. Like the StateEntity overload it wraps the underlying error in an AggregateException, but its message additionally reports the data length and the target Type, making it the error seen when the committed state itself (not a pending record) cannot be deserialized.
Source
Thrown at src/AWS/Orleans.Transactions.DynamoDB/TransactionalState/DynamoDBTransactionalStateStorage.cs:437
}
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);
throw new AggregateException(message, exc);
}
return dataValue;
}
private byte[] ConvertToStorageFormat<T>(T value) => this.serializer.Serialize(value).ToArray();
private void ValidateKeyItemSize(byte[] metadata, DateTimeOffset timestamp, long committedSequenceId)
{
var candidate = new KeyEntity(this.partitionKey)
{
CommittedSequenceId = committedSequenceId,
Metadata = metadata,
Timestamp = timestamp,
ETag = long.MaxValue
};
ValidateItemSize(candidate.ToStorageFormat(), nameof(metadata));View on GitHub (pinned to fca799fa70)
Solutions
- Read DataLen and StateType from the message, then inspect the persisted bytes for the named partition/sequence to confirm the format.
- Make the TState type and the configured IGrainStorageSerializer byte-for-byte compatible with what was written (revert the breaking change or add a version-tolerant reader).
- If the committed state is unrecoverable, reset that grain's state (delete its rows in the table) so Load() returns a fresh v0 state.
- Never reuse a DynamoDB table/partition across different grain state types; ensure MakePartitionKey inputs (ServiceId, stateName) are stable.
Example fix
// before: state type mutated, breaking older persisted rows
public class MyState { public int Count; }
// later deploy:
public class MyState { public long Count; public string Name; } // incompatible
// after: keep types backward-compatible or migrate via a custom serializer that bridges old->new
public class MyState {
public long Count { get; set; }
[JsonExtensionData] public Dictionary<string,object>? Extra { get; set; } // tolerate unknown fields
} Defensive patterns
Strategy: try-catch
Validate before calling
// Round-trip test the configured serializer against the persisted shape at deploy time
var probe = new TState();
var wire = options.GrainStorageSerializer.Serialize(probe).ToArray();
if (options.GrainStorageSerializer.Deserialize<TState>(wire) is null)
throw new InvalidOperationException("TState is not round-trippable by the configured serializer"); Type guard
static bool CanReadCommittedState(IGrainStorageSerializer s, byte[] value)
where TState : class, new()
{
try { return value is { Length: > 0 } && s.Deserialize<TState>(value) is not null; }
catch { return false; }
} Try / catch
try
{
await grain.ActivateAndLoad();
}
catch (AggregateException ax) when (ax.Message.Contains("Unable to convert from storage format"))
{
_logger.LogCritical(ax.InnerException, "Committed state deserialization failed; StateType={Type}", typeof(TState));
// engage recovery: migrate serializer, or reset the grain's rows
throw;
} Prevention
- Treat TState and the serializer as a versioned contract; never mutate incompatibly.
- Do not repoint a different grain type at an existing table/partition.
- Keep ServiceId/stateName stable so partition keys never collide between types.
- Run a read probe against production data during staging before flipping a schema change.
When it happens
Trigger: Produced in Load() at line 92 when ConvertFromStorageFormat<TState>(states[pos].Value) is called to materialize the committed state for key.CommittedSequenceId. Triggered when the committed state's stored bytes are incompatible with TState: incompatible type change, switched serializer, null/compression-mangled payload, or a state written by a different TState type that shared the partition key.
Common situations: Changing the grain's state class shape after data exists; switching the storage serializer; pointing a new grain type at a table/partition that contains rows from a different grain type; partial migration of a production table.
Related errors
- Unable to convert from storage format GrainStateEntity.Data=
- 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/a8304d011352e31e.
Report an issue: GitHub.