diff --git a/aws_lambda_powertools/utilities/data_classes/common.py b/aws_lambda_powertools/utilities/data_classes/common.py
index 2109ee3dd3e..1b671489cdd 100644
--- a/aws_lambda_powertools/utilities/data_classes/common.py
+++ b/aws_lambda_powertools/utilities/data_classes/common.py
@@ -1,9 +1,10 @@
 import base64
 import json
-from typing import Any, Dict, Optional
+from collections.abc import Mapping
+from typing import Any, Dict, Iterator, Optional
 
 
-class DictWrapper:
+class DictWrapper(Mapping):
     """Provides a single read only access to a wrapper dict"""
 
     def __init__(self, data: Dict[str, Any]):
@@ -19,6 +20,12 @@ def __eq__(self, other: Any) -> bool:
 
         return self._data == other._data
 
+    def __iter__(self) -> Iterator:
+        return iter(self._data)
+
+    def __len__(self) -> int:
+        return len(self._data)
+
     def get(self, key: str, default: Optional[Any] = None) -> Optional[Any]:
         return self._data.get(key, default)
 
diff --git a/aws_lambda_powertools/utilities/data_classes/dynamo_db_stream_event.py b/aws_lambda_powertools/utilities/data_classes/dynamo_db_stream_event.py
index 7e209fab3e2..eb674c86b60 100644
--- a/aws_lambda_powertools/utilities/data_classes/dynamo_db_stream_event.py
+++ b/aws_lambda_powertools/utilities/data_classes/dynamo_db_stream_event.py
@@ -182,8 +182,10 @@ def approximate_creation_date_time(self) -> Optional[int]:
         item = self.get("ApproximateCreationDateTime")
         return None if item is None else int(item)
 
+    # NOTE: This override breaks the Mapping protocol of DictWrapper, it's left here for backwards compatibility with
+    # a 'type: ignore' comment. See #1516 for discussion
     @property
-    def keys(self) -> Optional[Dict[str, AttributeValue]]:
+    def keys(self) -> Optional[Dict[str, AttributeValue]]:  # type: ignore[override]
         """The primary key attribute(s) for the DynamoDB item that was modified."""
         return _attribute_value_dict(self._data, "Keys")
 
diff --git a/tests/functional/test_data_classes.py b/tests/functional/test_data_classes.py
index dbef57162e2..f0ac4af0af0 100644
--- a/tests/functional/test_data_classes.py
+++ b/tests/functional/test_data_classes.py
@@ -74,6 +74,7 @@
     AttributeValueType,
     DynamoDBRecordEventName,
     DynamoDBStreamEvent,
+    StreamRecord,
     StreamViewType,
 )
 from aws_lambda_powertools.utilities.data_classes.event_source import event_source
@@ -101,6 +102,19 @@ def message(self) -> str:
     assert DataClassSample(data1).raw_event is data1
 
 
+def test_dict_wrapper_implements_mapping():
+    class DataClassSample(DictWrapper):
+        pass
+
+    data = {"message": "foo1"}
+    event_source = DataClassSample(data)
+    assert len(event_source) == len(data)
+    assert list(event_source) == list(data)
+    assert event_source.keys() == data.keys()
+    assert list(event_source.values()) == list(data.values())
+    assert event_source.items() == data.items()
+
+
 def test_cloud_watch_dashboard_event():
     event = CloudWatchDashboardCustomWidgetEvent(load_event("cloudWatchDashboardEvent.json"))
     assert event.describe is False
@@ -617,6 +631,23 @@ def test_dynamo_attribute_value_type_error():
         print(attribute_value.get_type)
 
 
+def test_stream_record_keys_with_valid_keys():
+    attribute_value = {"Foo": "Bar"}
+    record = StreamRecord({"Keys": {"Key1": attribute_value}})
+    assert record.keys == {"Key1": AttributeValue(attribute_value)}
+
+
+def test_stream_record_keys_with_no_keys():
+    record = StreamRecord({})
+    assert record.keys is None
+
+
+def test_stream_record_keys_overrides_dict_wrapper_keys():
+    data = {"Keys": {"key1": {"attr1": "value1"}}}
+    record = StreamRecord(data)
+    assert record.keys != data.keys()
+
+
 def test_event_bridge_event():
     event = EventBridgeEvent(load_event("eventBridgeEvent.json"))