{"record":{"id":"a8304d011352e31e","repo":"dotnet/orleans","slug":"unable-to-convert-from-storage-format-data-value","errorCode":null,"errorMessage":"Unable to convert from storage format, Data={value}, DataLen={value?.Length ?? -1}, StateType={typeof(T)}","messagePattern":"Unable to convert from storage format, Data=(.+?), DataLen=(.+?), StateType=(.+?)","errorType":"exception","errorClass":"AggregateException","httpStatus":null,"severity":"error","filePath":"src/AWS/Orleans.Transactions.DynamoDB/TransactionalState/DynamoDBTransactionalStateStorage.cs","lineNumber":437,"sourceCode":"        }\n\n        return dataValue;\n    }\n\n    private T ConvertFromStorageFormat<T>(byte[] value)\n    {\n        T dataValue = default!;\n\n        try\n        {\n            if (value is { Length: > 0 })\n                dataValue = this.serializer.Deserialize<T>(value)!;\n        }\n        catch (Exception exc)\n        {\n            var message = $\"Unable to convert from storage format, Data={value}, DataLen={value?.Length ?? -1}, StateType={typeof(T)}\";\n            LogError(logger, message);\n            throw new AggregateException(message, exc);\n        }\n\n        return dataValue;\n    }\n\n    private byte[] ConvertToStorageFormat<T>(T value) => this.serializer.Serialize(value).ToArray();\n\n    private void ValidateKeyItemSize(byte[] metadata, DateTimeOffset timestamp, long committedSequenceId)\n    {\n        var candidate = new KeyEntity(this.partitionKey)\n        {\n            CommittedSequenceId = committedSequenceId,\n            Metadata = metadata,\n            Timestamp = timestamp,\n            ETag = long.MaxValue\n        };\n\n        ValidateItemSize(candidate.ToStorageFormat(), nameof(metadata));","sourceCodeStart":419,"sourceCodeEnd":455,"githubUrl":"https://github.com/dotnet/orleans/blob/fca799fa70ecb6ad975224271703ca43221f58de/src/AWS/Orleans.Transactions.DynamoDB/TransactionalState/DynamoDBTransactionalStateStorage.cs#L419-L455","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: state type mutated, breaking older persisted rows\npublic class MyState { public int Count; }\n// later deploy:\npublic class MyState { public long Count; public string Name; } // incompatible\n\n// after: keep types backward-compatible or migrate via a custom serializer that bridges old->new\npublic class MyState {\n  public long Count { get; set; }\n  [JsonExtensionData] public Dictionary<string,object>? Extra { get; set; } // tolerate unknown fields\n}","handlingStrategy":"try-catch","validationCode":"// Round-trip test the configured serializer against the persisted shape at deploy time\nvar probe = new TState();\nvar wire = options.GrainStorageSerializer.Serialize(probe).ToArray();\nif (options.GrainStorageSerializer.Deserialize<TState>(wire) is null)\n    throw new InvalidOperationException(\"TState is not round-trippable by the configured serializer\");","typeGuard":"static bool CanReadCommittedState(IGrainStorageSerializer s, byte[] value)\n    where TState : class, new()\n{\n    try { return value is { Length: > 0 } && s.Deserialize<TState>(value) is not null; }\n    catch { return false; }\n}","tryCatchPattern":"try\n{\n    await grain.ActivateAndLoad();\n}\ncatch (AggregateException ax) when (ax.Message.Contains(\"Unable to convert from storage format\"))\n{\n    _logger.LogCritical(ax.InnerException, \"Committed state deserialization failed; StateType={Type}\", typeof(TState));\n    // engage recovery: migrate serializer, or reset the grain's rows\n    throw;\n}","preventionTips":["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."],"tags":["dynamodb","transactions","serialization","deserialization","committed-state"],"backgroundTag":null,"analyzedSha":"fca799fa70ecb6ad975224271703ca43221f58de","analyzedAt":"2026-08-13T19:55:57.938Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}