This is an automated email from the ASF dual-hosted git repository.

oscerd pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new 2948bb6308fb CAMEL-26776: Ensure KeyValueRepository instances isolate 
namespaces across components and patterns (#29479)
2948bb6308fb is described below

commit 2948bb6308fbb2a9ce662dfb57fd8d27f5c37d33
Author: Andrea Cosentino <[email protected]>
AuthorDate: Mon Nov 9 19:53:06 2026 +0100

    CAMEL-26776: Ensure KeyValueRepository instances isolate namespaces across 
components and patterns (#29479)
    
    - Added NamespacedKeyValueRepository in camel-support, which provides a 
repository wrapper that partitions all keys under a designated namespace 
prefix, restricts clear() to delete only entries in that namespace, streams key 
counts in size(), and returns un-prefixed keys in keys().
    - Updated StateStoreComponent to isolate shared backend repositories under 
statestore:<storeName>:, escaping delimiter characters to prevent prefix 
collision between store names, and logging a warning if conflicting namespaced 
options are requested for the same store name.
    - Added a namespaced configuration option (default true) on 
StateStoreComponent and StateStoreEndpoint to allow opting out when preserving 
un-namespaced keys written in Camel 4.23.
    - Dedicated backends from StateStoreBackendFactory and fallback in-memory 
stores are kept un-namespaced.
    - Retained un-prefixed keys in CacheProcessor to preserve documented cache 
invalidation recipes (cache.delete(key) and cache.clear()).
    - Hardened KeyValueAggregationRepository to safely verify that entries are 
instances of DefaultExchangeHolder before unmarshalling or completing, logging 
a warning and returning null if an unexpected type is encountered.
    - Added documentation in state-store-component.adoc and a migration note in 
camel-4x-upgrade-guide-4_24.adoc, including an advisory for shared backends 
with Cache EIP.
    
    Co-authored-by: Antigravity <[email protected]>
---
 .../org/apache/camel/catalog/beans.properties      |   1 +
 .../beans/NamespacedKeyValueRepository.json        |  15 ++
 .../camel/catalog/components/state-store.json      |  14 +-
 .../camel/catalog/docs/state-store-component.adoc  |  16 ++
 .../camel-state-store/camel-state-store/pom.xml    |   2 +-
 .../statestore/StateStoreComponentConfigurer.java  |   3 +
 .../statestore/StateStoreEndpointConfigurer.java   |   3 +
 .../statestore/StateStoreEndpointUriFactory.java   |   3 +-
 .../camel/component/statestore/state-store.json    |  14 +-
 .../src/main/docs/state-store-component.adoc       |  16 ++
 .../component/statestore/StateStoreComponent.java  |  59 ++++-
 .../component/statestore/StateStoreEndpoint.java   |  14 +-
 .../statestore/StateStoreAutoDiscoveryTest.java    |   5 +-
 .../statestore/StateStoreBackendFactoryTest.java   |   6 +-
 .../StateStoreNamespaceIsolationTest.java          | 148 +++++++++++
 .../statestore/StateStoreStreamBodyTest.java       |   2 +-
 .../apache/camel/processor/CacheProcessorTest.java |  40 +++
 .../support/KeyValueAggregationRepositoryTest.java |  16 ++
 .../NamespacedKeyValueRepositoryConfigurer.java    |  49 ++++
 .../services/org/apache/camel/bean.properties      |   2 +-
 .../camel/bean/NamespacedKeyValueRepository.json   |  15 ++
 ...ache.camel.support.NamespacedKeyValueRepository |   2 +
 .../support/KeyValueAggregationRepository.java     |  19 +-
 .../support/NamespacedKeyValueRepository.java      | 271 +++++++++++++++++++++
 .../support/NamespacedKeyValueRepositoryTest.java  | 185 ++++++++++++++
 .../java/org/apache/camel/util/ArtifactUtils.java  |   2 +
 .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc    |  12 +
 .../dsl/StateStoreComponentBuilderFactory.java     |  19 ++
 .../dsl/StateStoreEndpointBuilderFactory.java      |  32 +++
 29 files changed, 949 insertions(+), 36 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/beans.properties
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/beans.properties
index b54d21b7ebc8..dc3b21b7dcaf 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/beans.properties
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/beans.properties
@@ -47,6 +47,7 @@ MemoryAggregationRepository
 MemoryIdempotentRepository
 MemoryKeyValueRepository
 MongoDbIdempotentRepository
+NamespacedKeyValueRepository
 OpaSecurityPolicy
 OpenFgaSecurityPolicy
 OpensearchBulkRequestAggregationStrategy
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/beans/NamespacedKeyValueRepository.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/beans/NamespacedKeyValueRepository.json
new file mode 100644
index 000000000000..55274077a939
--- /dev/null
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/beans/NamespacedKeyValueRepository.json
@@ -0,0 +1,15 @@
+{
+  "bean": {
+    "kind": "bean",
+    "name": "NamespacedKeyValueRepository",
+    "javaType": "org.apache.camel.support.NamespacedKeyValueRepository",
+    "interfaceType": "org.apache.camel.spi.KeyValueRepository",
+    "title": "Namespaced Key Value Repository",
+    "description": "A KeyValueRepository that partitions keys using a 
namespace prefix.",
+    "deprecated": false,
+    "groupId": "org.apache.camel",
+    "artifactId": "camel-support",
+    "version": "4.24.0-SNAPSHOT"
+  }
+}
+
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/state-store.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/state-store.json
index 6ba0cb8d60af..da8e8ec118e0 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/state-store.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/state-store.json
@@ -25,8 +25,9 @@
   },
   "componentProperties": {
     "lazyStartProducer": { "index": 0, "kind": "property", "displayName": 
"Lazy Start Producer", "group": "producer", "label": "producer", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": false, "description": 
"Whether the producer should be started lazy (on the first message). By 
starting lazy you can use this to allow CamelContext and routes to startup in 
situations where a producer may otherwise fail [...]
-    "autowiredEnabled": { "index": 1, "kind": "property", "displayName": 
"Autowired Enabled", "group": "advanced", "label": "advanced", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": true, "description": 
"Whether autowiring is enabled. This is used for automatic autowiring options 
(the option must be marked as autowired) by looking up in the registry to find 
if there is a single instance of matching t [...]
-    "backendFactory": { "index": 2, "kind": "property", "displayName": 
"Backend Factory", "group": "advanced", "label": "advanced", "required": false, 
"type": "object", "javaType": 
"org.apache.camel.component.statestore.StateStoreBackendFactory", "deprecated": 
false, "autowired": true, "secret": false, "description": "Factory that creates 
a dedicated backend for each store name. When set, it takes precedence over 
registry auto-discovery for endpoints without an explicit backend." }
+    "namespaced": { "index": 1, "kind": "property", "displayName": 
"Namespaced", "group": "producer", "label": "", "required": false, "type": 
"boolean", "javaType": "boolean", "deprecated": false, "autowired": false, 
"secret": false, "defaultValue": true, "description": "Whether to use a 
store-specific namespace prefix to isolate keys on a shared backend 
repository." },
+    "autowiredEnabled": { "index": 2, "kind": "property", "displayName": 
"Autowired Enabled", "group": "advanced", "label": "advanced", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": true, "description": 
"Whether autowiring is enabled. This is used for automatic autowiring options 
(the option must be marked as autowired) by looking up in the registry to find 
if there is a single instance of matching t [...]
+    "backendFactory": { "index": 3, "kind": "property", "displayName": 
"Backend Factory", "group": "advanced", "label": "advanced", "required": false, 
"type": "object", "javaType": 
"org.apache.camel.component.statestore.StateStoreBackendFactory", "deprecated": 
false, "autowired": true, "secret": false, "description": "Factory that creates 
a dedicated backend for each store name. When set, it takes precedence over 
registry auto-discovery for endpoints without an explicit backend." }
   },
   "headers": {
     "CamelStateStoreOperation": { "index": 0, "kind": "header", "displayName": 
"", "group": "producer", "label": "producer", "required": false, "javaType": 
"org.apache.camel.component.statestore.StateStoreOperations", "enum": [ "put", 
"putIfAbsent", "get", "delete", "contains", "keys", "size", "clear" ], 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "The operation to perform", "constantName": 
"org.apache.camel.component.statestore.StateSt [...]
@@ -39,9 +40,10 @@
   },
   "properties": {
     "storeName": { "index": 0, "kind": "path", "displayName": "Store Name", 
"group": "producer", "label": "", "required": true, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "The name of the state 
store" },
-    "operation": { "index": 1, "kind": "parameter", "displayName": 
"Operation", "group": "producer", "label": "", "required": false, "type": 
"enum", "javaType": 
"org.apache.camel.component.statestore.StateStoreOperations", "enum": [ "put", 
"putIfAbsent", "get", "delete", "contains", "keys", "size", "clear" ], 
"deprecated": false, "autowired": false, "secret": false, "description": "The 
default operation to perform" },
-    "ttl": { "index": 2, "kind": "parameter", "displayName": "Ttl", "group": 
"producer", "label": "", "required": false, "type": "integer", "javaType": 
"long", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": 0, "description": "Time-to-live in milliseconds for entries. 0 
means no expiry." },
-    "lazyStartProducer": { "index": 3, "kind": "parameter", "displayName": 
"Lazy Start Producer", "group": "producer (advanced)", "label": 
"producer,advanced", "required": false, "type": "boolean", "javaType": 
"boolean", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": false, "description": "Whether the producer should be started 
lazy (on the first message). By starting lazy you can use this to allow 
CamelContext and routes to startup in situations where a produc [...]
-    "backend": { "index": 4, "kind": "parameter", "displayName": "Backend", 
"group": "advanced", "label": "advanced", "required": false, "type": "object", 
"javaType": "org.apache.camel.spi.KeyValueRepository", "deprecated": false, 
"autowired": false, "secret": false, "description": "The backend to use. If not 
set, auto-discovers a single KeyValueRepository from the registry, or falls 
back to an in-memory store." }
+    "namespaced": { "index": 1, "kind": "parameter", "displayName": 
"Namespaced", "group": "producer", "label": "", "required": false, "type": 
"boolean", "javaType": "boolean", "deprecated": false, "autowired": false, 
"secret": false, "defaultValue": true, "description": "Whether to use a 
store-specific namespace prefix to isolate keys on a shared backend 
repository." },
+    "operation": { "index": 2, "kind": "parameter", "displayName": 
"Operation", "group": "producer", "label": "", "required": false, "type": 
"enum", "javaType": 
"org.apache.camel.component.statestore.StateStoreOperations", "enum": [ "put", 
"putIfAbsent", "get", "delete", "contains", "keys", "size", "clear" ], 
"deprecated": false, "autowired": false, "secret": false, "description": "The 
default operation to perform" },
+    "ttl": { "index": 3, "kind": "parameter", "displayName": "Ttl", "group": 
"producer", "label": "", "required": false, "type": "integer", "javaType": 
"long", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": 0, "description": "Time-to-live in milliseconds for entries. 0 
means no expiry." },
+    "lazyStartProducer": { "index": 4, "kind": "parameter", "displayName": 
"Lazy Start Producer", "group": "producer (advanced)", "label": 
"producer,advanced", "required": false, "type": "boolean", "javaType": 
"boolean", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": false, "description": "Whether the producer should be started 
lazy (on the first message). By starting lazy you can use this to allow 
CamelContext and routes to startup in situations where a produc [...]
+    "backend": { "index": 5, "kind": "parameter", "displayName": "Backend", 
"group": "advanced", "label": "advanced", "required": false, "type": "object", 
"javaType": "org.apache.camel.spi.KeyValueRepository", "deprecated": false, 
"autowired": false, "secret": false, "description": "The backend to use. If not 
set, auto-discovers a single KeyValueRepository from the registry, or falls 
back to an in-memory store." }
   }
 }
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/state-store-component.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/state-store-component.adoc
index 5cdd3f1b04d6..8bddc34dbb7c 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/state-store-component.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/state-store-component.adoc
@@ -206,3 +206,19 @@ for the State Store component, the Idempotent Consumer, 
the Aggregator, and the
 
 See the KeyValueRepository section in the User Manual for the full list of 
available
 backends, configuration examples, and instructions for writing a custom 
backend.
+
+== Namespace Isolation
+
+When using a shared `KeyValueRepository` (such as a single bean 
auto-discovered from the registry or referenced via `backend=#shared`), 
`state-store` automatically isolates entries per store name under the prefix 
`statestore:<storeName>:`.
+
+This ensures that:
+
+* Multiple state stores sharing the same backend do not collide on keys.
+* Calling `clear()`, `keys()`, or `size()` on one store is strictly scoped to 
that store's entries.
+* State store operations cannot interfere with entries from other patterns 
sharing the repository.
+
+To opt out of namespacing (for example, to read existing un-namespaced keys 
written in Camel 4.23 without key migration), set `namespaced=false` on the 
endpoint or component:
+
+----
+state-store:orders?namespaced=false
+----
diff --git a/components/camel-state-store/camel-state-store/pom.xml 
b/components/camel-state-store/camel-state-store/pom.xml
index a130d1351b02..025c6dffc4dd 100644
--- a/components/camel-state-store/camel-state-store/pom.xml
+++ b/components/camel-state-store/camel-state-store/pom.xml
@@ -46,7 +46,7 @@
         <!-- testing -->
         <dependency>
             <groupId>org.apache.camel</groupId>
-            <artifactId>camel-test-main-junit5</artifactId>
+            <artifactId>camel-test-junit5</artifactId>
             <scope>test</scope>
         </dependency>
         <dependency>
diff --git 
a/components/camel-state-store/camel-state-store/src/generated/java/org/apache/camel/component/statestore/StateStoreComponentConfigurer.java
 
b/components/camel-state-store/camel-state-store/src/generated/java/org/apache/camel/component/statestore/StateStoreComponentConfigurer.java
index cefdabbdcd4f..0b89deeb1866 100644
--- 
a/components/camel-state-store/camel-state-store/src/generated/java/org/apache/camel/component/statestore/StateStoreComponentConfigurer.java
+++ 
b/components/camel-state-store/camel-state-store/src/generated/java/org/apache/camel/component/statestore/StateStoreComponentConfigurer.java
@@ -30,6 +30,7 @@ public class StateStoreComponentConfigurer extends 
PropertyConfigurerSupport imp
         case "backendFactory": target.setBackendFactory(property(camelContext, 
org.apache.camel.component.statestore.StateStoreBackendFactory.class, value)); 
return true;
         case "lazystartproducer":
         case "lazyStartProducer": 
target.setLazyStartProducer(property(camelContext, boolean.class, value)); 
return true;
+        case "namespaced": target.setNamespaced(property(camelContext, 
boolean.class, value)); return true;
         default: return false;
         }
     }
@@ -48,6 +49,7 @@ public class StateStoreComponentConfigurer extends 
PropertyConfigurerSupport imp
         case "backendFactory": return 
org.apache.camel.component.statestore.StateStoreBackendFactory.class;
         case "lazystartproducer":
         case "lazyStartProducer": return boolean.class;
+        case "namespaced": return boolean.class;
         default: return null;
         }
     }
@@ -62,6 +64,7 @@ public class StateStoreComponentConfigurer extends 
PropertyConfigurerSupport imp
         case "backendFactory": return target.getBackendFactory();
         case "lazystartproducer":
         case "lazyStartProducer": return target.isLazyStartProducer();
+        case "namespaced": return target.isNamespaced();
         default: return null;
         }
     }
diff --git 
a/components/camel-state-store/camel-state-store/src/generated/java/org/apache/camel/component/statestore/StateStoreEndpointConfigurer.java
 
b/components/camel-state-store/camel-state-store/src/generated/java/org/apache/camel/component/statestore/StateStoreEndpointConfigurer.java
index 2084b9a60356..746e40080497 100644
--- 
a/components/camel-state-store/camel-state-store/src/generated/java/org/apache/camel/component/statestore/StateStoreEndpointConfigurer.java
+++ 
b/components/camel-state-store/camel-state-store/src/generated/java/org/apache/camel/component/statestore/StateStoreEndpointConfigurer.java
@@ -27,6 +27,7 @@ public class StateStoreEndpointConfigurer extends 
PropertyConfigurerSupport impl
         case "backend": target.setBackend(property(camelContext, 
org.apache.camel.spi.KeyValueRepository.class, value)); return true;
         case "lazystartproducer":
         case "lazyStartProducer": 
target.setLazyStartProducer(property(camelContext, boolean.class, value)); 
return true;
+        case "namespaced": target.setNamespaced(property(camelContext, 
boolean.class, value)); return true;
         case "operation": target.setOperation(property(camelContext, 
org.apache.camel.component.statestore.StateStoreOperations.class, value)); 
return true;
         case "ttl": target.setTtl(property(camelContext, long.class, value)); 
return true;
         default: return false;
@@ -39,6 +40,7 @@ public class StateStoreEndpointConfigurer extends 
PropertyConfigurerSupport impl
         case "backend": return org.apache.camel.spi.KeyValueRepository.class;
         case "lazystartproducer":
         case "lazyStartProducer": return boolean.class;
+        case "namespaced": return boolean.class;
         case "operation": return 
org.apache.camel.component.statestore.StateStoreOperations.class;
         case "ttl": return long.class;
         default: return null;
@@ -52,6 +54,7 @@ public class StateStoreEndpointConfigurer extends 
PropertyConfigurerSupport impl
         case "backend": return target.getBackend();
         case "lazystartproducer":
         case "lazyStartProducer": return target.isLazyStartProducer();
+        case "namespaced": return target.isNamespaced();
         case "operation": return target.getOperation();
         case "ttl": return target.getTtl();
         default: return null;
diff --git 
a/components/camel-state-store/camel-state-store/src/generated/java/org/apache/camel/component/statestore/StateStoreEndpointUriFactory.java
 
b/components/camel-state-store/camel-state-store/src/generated/java/org/apache/camel/component/statestore/StateStoreEndpointUriFactory.java
index a3e003b0b405..82bdc1b1b98c 100644
--- 
a/components/camel-state-store/camel-state-store/src/generated/java/org/apache/camel/component/statestore/StateStoreEndpointUriFactory.java
+++ 
b/components/camel-state-store/camel-state-store/src/generated/java/org/apache/camel/component/statestore/StateStoreEndpointUriFactory.java
@@ -24,9 +24,10 @@ public class StateStoreEndpointUriFactory extends 
org.apache.camel.support.compo
     private static final Set<String> ENDPOINT_IDENTITY_PROPERTY_NAMES;
     private static final Map<String, String> MULTI_VALUE_PREFIXES;
     static {
-        Set<String> props = new HashSet<>(5);
+        Set<String> props = new HashSet<>(6);
         props.add("backend");
         props.add("lazyStartProducer");
+        props.add("namespaced");
         props.add("operation");
         props.add("storeName");
         props.add("ttl");
diff --git 
a/components/camel-state-store/camel-state-store/src/generated/resources/META-INF/org/apache/camel/component/statestore/state-store.json
 
b/components/camel-state-store/camel-state-store/src/generated/resources/META-INF/org/apache/camel/component/statestore/state-store.json
index 6ba0cb8d60af..da8e8ec118e0 100644
--- 
a/components/camel-state-store/camel-state-store/src/generated/resources/META-INF/org/apache/camel/component/statestore/state-store.json
+++ 
b/components/camel-state-store/camel-state-store/src/generated/resources/META-INF/org/apache/camel/component/statestore/state-store.json
@@ -25,8 +25,9 @@
   },
   "componentProperties": {
     "lazyStartProducer": { "index": 0, "kind": "property", "displayName": 
"Lazy Start Producer", "group": "producer", "label": "producer", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": false, "description": 
"Whether the producer should be started lazy (on the first message). By 
starting lazy you can use this to allow CamelContext and routes to startup in 
situations where a producer may otherwise fail [...]
-    "autowiredEnabled": { "index": 1, "kind": "property", "displayName": 
"Autowired Enabled", "group": "advanced", "label": "advanced", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": true, "description": 
"Whether autowiring is enabled. This is used for automatic autowiring options 
(the option must be marked as autowired) by looking up in the registry to find 
if there is a single instance of matching t [...]
-    "backendFactory": { "index": 2, "kind": "property", "displayName": 
"Backend Factory", "group": "advanced", "label": "advanced", "required": false, 
"type": "object", "javaType": 
"org.apache.camel.component.statestore.StateStoreBackendFactory", "deprecated": 
false, "autowired": true, "secret": false, "description": "Factory that creates 
a dedicated backend for each store name. When set, it takes precedence over 
registry auto-discovery for endpoints without an explicit backend." }
+    "namespaced": { "index": 1, "kind": "property", "displayName": 
"Namespaced", "group": "producer", "label": "", "required": false, "type": 
"boolean", "javaType": "boolean", "deprecated": false, "autowired": false, 
"secret": false, "defaultValue": true, "description": "Whether to use a 
store-specific namespace prefix to isolate keys on a shared backend 
repository." },
+    "autowiredEnabled": { "index": 2, "kind": "property", "displayName": 
"Autowired Enabled", "group": "advanced", "label": "advanced", "required": 
false, "type": "boolean", "javaType": "boolean", "deprecated": false, 
"autowired": false, "secret": false, "defaultValue": true, "description": 
"Whether autowiring is enabled. This is used for automatic autowiring options 
(the option must be marked as autowired) by looking up in the registry to find 
if there is a single instance of matching t [...]
+    "backendFactory": { "index": 3, "kind": "property", "displayName": 
"Backend Factory", "group": "advanced", "label": "advanced", "required": false, 
"type": "object", "javaType": 
"org.apache.camel.component.statestore.StateStoreBackendFactory", "deprecated": 
false, "autowired": true, "secret": false, "description": "Factory that creates 
a dedicated backend for each store name. When set, it takes precedence over 
registry auto-discovery for endpoints without an explicit backend." }
   },
   "headers": {
     "CamelStateStoreOperation": { "index": 0, "kind": "header", "displayName": 
"", "group": "producer", "label": "producer", "required": false, "javaType": 
"org.apache.camel.component.statestore.StateStoreOperations", "enum": [ "put", 
"putIfAbsent", "get", "delete", "contains", "keys", "size", "clear" ], 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "The operation to perform", "constantName": 
"org.apache.camel.component.statestore.StateSt [...]
@@ -39,9 +40,10 @@
   },
   "properties": {
     "storeName": { "index": 0, "kind": "path", "displayName": "Store Name", 
"group": "producer", "label": "", "required": true, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "The name of the state 
store" },
-    "operation": { "index": 1, "kind": "parameter", "displayName": 
"Operation", "group": "producer", "label": "", "required": false, "type": 
"enum", "javaType": 
"org.apache.camel.component.statestore.StateStoreOperations", "enum": [ "put", 
"putIfAbsent", "get", "delete", "contains", "keys", "size", "clear" ], 
"deprecated": false, "autowired": false, "secret": false, "description": "The 
default operation to perform" },
-    "ttl": { "index": 2, "kind": "parameter", "displayName": "Ttl", "group": 
"producer", "label": "", "required": false, "type": "integer", "javaType": 
"long", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": 0, "description": "Time-to-live in milliseconds for entries. 0 
means no expiry." },
-    "lazyStartProducer": { "index": 3, "kind": "parameter", "displayName": 
"Lazy Start Producer", "group": "producer (advanced)", "label": 
"producer,advanced", "required": false, "type": "boolean", "javaType": 
"boolean", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": false, "description": "Whether the producer should be started 
lazy (on the first message). By starting lazy you can use this to allow 
CamelContext and routes to startup in situations where a produc [...]
-    "backend": { "index": 4, "kind": "parameter", "displayName": "Backend", 
"group": "advanced", "label": "advanced", "required": false, "type": "object", 
"javaType": "org.apache.camel.spi.KeyValueRepository", "deprecated": false, 
"autowired": false, "secret": false, "description": "The backend to use. If not 
set, auto-discovers a single KeyValueRepository from the registry, or falls 
back to an in-memory store." }
+    "namespaced": { "index": 1, "kind": "parameter", "displayName": 
"Namespaced", "group": "producer", "label": "", "required": false, "type": 
"boolean", "javaType": "boolean", "deprecated": false, "autowired": false, 
"secret": false, "defaultValue": true, "description": "Whether to use a 
store-specific namespace prefix to isolate keys on a shared backend 
repository." },
+    "operation": { "index": 2, "kind": "parameter", "displayName": 
"Operation", "group": "producer", "label": "", "required": false, "type": 
"enum", "javaType": 
"org.apache.camel.component.statestore.StateStoreOperations", "enum": [ "put", 
"putIfAbsent", "get", "delete", "contains", "keys", "size", "clear" ], 
"deprecated": false, "autowired": false, "secret": false, "description": "The 
default operation to perform" },
+    "ttl": { "index": 3, "kind": "parameter", "displayName": "Ttl", "group": 
"producer", "label": "", "required": false, "type": "integer", "javaType": 
"long", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": 0, "description": "Time-to-live in milliseconds for entries. 0 
means no expiry." },
+    "lazyStartProducer": { "index": 4, "kind": "parameter", "displayName": 
"Lazy Start Producer", "group": "producer (advanced)", "label": 
"producer,advanced", "required": false, "type": "boolean", "javaType": 
"boolean", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": false, "description": "Whether the producer should be started 
lazy (on the first message). By starting lazy you can use this to allow 
CamelContext and routes to startup in situations where a produc [...]
+    "backend": { "index": 5, "kind": "parameter", "displayName": "Backend", 
"group": "advanced", "label": "advanced", "required": false, "type": "object", 
"javaType": "org.apache.camel.spi.KeyValueRepository", "deprecated": false, 
"autowired": false, "secret": false, "description": "The backend to use. If not 
set, auto-discovers a single KeyValueRepository from the registry, or falls 
back to an in-memory store." }
   }
 }
diff --git 
a/components/camel-state-store/camel-state-store/src/main/docs/state-store-component.adoc
 
b/components/camel-state-store/camel-state-store/src/main/docs/state-store-component.adoc
index 5cdd3f1b04d6..8bddc34dbb7c 100644
--- 
a/components/camel-state-store/camel-state-store/src/main/docs/state-store-component.adoc
+++ 
b/components/camel-state-store/camel-state-store/src/main/docs/state-store-component.adoc
@@ -206,3 +206,19 @@ for the State Store component, the Idempotent Consumer, 
the Aggregator, and the
 
 See the KeyValueRepository section in the User Manual for the full list of 
available
 backends, configuration examples, and instructions for writing a custom 
backend.
+
+== Namespace Isolation
+
+When using a shared `KeyValueRepository` (such as a single bean 
auto-discovered from the registry or referenced via `backend=#shared`), 
`state-store` automatically isolates entries per store name under the prefix 
`statestore:<storeName>:`.
+
+This ensures that:
+
+* Multiple state stores sharing the same backend do not collide on keys.
+* Calling `clear()`, `keys()`, or `size()` on one store is strictly scoped to 
that store's entries.
+* State store operations cannot interfere with entries from other patterns 
sharing the repository.
+
+To opt out of namespacing (for example, to read existing un-namespaced keys 
written in Camel 4.23 without key migration), set `namespaced=false` on the 
endpoint or component:
+
+----
+state-store:orders?namespaced=false
+----
diff --git 
a/components/camel-state-store/camel-state-store/src/main/java/org/apache/camel/component/statestore/StateStoreComponent.java
 
b/components/camel-state-store/camel-state-store/src/main/java/org/apache/camel/component/statestore/StateStoreComponent.java
index 7f466e69f19b..c5ef6ec2561a 100644
--- 
a/components/camel-state-store/camel-state-store/src/main/java/org/apache/camel/component/statestore/StateStoreComponent.java
+++ 
b/components/camel-state-store/camel-state-store/src/main/java/org/apache/camel/component/statestore/StateStoreComponent.java
@@ -27,6 +27,7 @@ import org.apache.camel.spi.Metadata;
 import org.apache.camel.spi.annotations.Component;
 import org.apache.camel.support.DefaultComponent;
 import org.apache.camel.support.MemoryKeyValueRepository;
+import org.apache.camel.support.NamespacedKeyValueRepository;
 import org.apache.camel.support.service.ServiceHelper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -49,33 +50,56 @@ public class StateStoreComponent extends DefaultComponent {
                             + " registry auto-discovery for endpoints without 
an explicit backend.")
     private StateStoreBackendFactory backendFactory;
 
+    @Metadata(description = "Whether to use a store-specific namespace prefix 
to isolate keys on a shared backend repository.",
+              defaultValue = "true")
+    private boolean namespaced = true;
+
     @Override
     protected Endpoint createEndpoint(String uri, String remaining, 
Map<String, Object> parameters) throws Exception {
         StateStoreEndpoint endpoint = new StateStoreEndpoint(uri, this);
         endpoint.setStoreName(remaining);
+        endpoint.setNamespaced(isNamespaced());
         setProperties(endpoint, parameters);
         return endpoint;
     }
 
+    private static String toNamespace(String storeName) {
+        return "statestore:" + storeName.replace("%", "%25").replace(":", 
"%3A");
+    }
+
     /**
      * Returns the backend for the given store name, creating one if needed. 
Uses first-one-wins semantics: if a backend
      * already exists for this store name, subsequent calls return the 
existing one regardless of the explicit backend
      * passed.
      */
     KeyValueRepository getOrCreateBackend(String storeName, KeyValueRepository 
explicitBackend) {
+        return getOrCreateBackend(storeName, explicitBackend, isNamespaced());
+    }
+
+    KeyValueRepository getOrCreateBackend(String storeName, KeyValueRepository 
explicitBackend, boolean namespaced) {
         if (explicitBackend != null) {
-            KeyValueRepository existing = backends.putIfAbsent(storeName, 
explicitBackend);
+            KeyValueRepository target = namespaced
+                    ? NamespacedKeyValueRepository.namespaced(explicitBackend, 
toNamespace(storeName))
+                    : explicitBackend;
+            KeyValueRepository existing = backends.putIfAbsent(storeName, 
target);
             if (existing != null) {
-                if (existing != explicitBackend) {
+                KeyValueRepository existingDelegate
+                        = existing instanceof NamespacedKeyValueRepository n ? 
n.getDelegate() : existing;
+                if (existingDelegate != explicitBackend) {
                     LOG.warn("Store '{}' already has a backend ({}). Ignoring 
explicit backend=# reference ({}).",
-                            storeName, existing.getClass().getSimpleName(), 
explicitBackend.getClass().getSimpleName());
+                            storeName, 
existingDelegate.getClass().getSimpleName(), 
explicitBackend.getClass().getSimpleName());
+                }
+                boolean existingNamespaced = existing instanceof 
NamespacedKeyValueRepository;
+                if (existingNamespaced != namespaced) {
+                    LOG.warn("Store '{}' was already initialized with 
namespaced={}. Ignoring namespaced={} for this endpoint.",
+                            storeName, existingNamespaced, namespaced);
                 }
                 return existing;
             }
-            ServiceHelper.startService(explicitBackend);
-            return explicitBackend;
+            ServiceHelper.startService(target);
+            return target;
         }
-        return backends.computeIfAbsent(storeName, k -> {
+        KeyValueRepository result = backends.computeIfAbsent(storeName, k -> {
             if (backendFactory == null && 
backendFactoryChecked.compareAndSet(false, true)) {
                 // checked lazily as autowiring happens after the component is 
initialized
                 int count = 
getCamelContext().getRegistry().findByType(StateStoreBackendFactory.class).size();
@@ -98,6 +122,11 @@ public class StateStoreComponent extends DefaultComponent {
                 backend = found.iterator().next();
                 LOG.debug("Auto-discovered KeyValueRepository '{}' for store 
'{}'",
                         backend.getClass().getSimpleName(), storeName);
+                KeyValueRepository target = namespaced
+                        ? NamespacedKeyValueRepository.namespaced(backend, 
toNamespace(storeName))
+                        : backend;
+                ServiceHelper.startService(target);
+                return target;
             } else {
                 if (found.size() > 1) {
                     LOG.warn(
@@ -110,10 +139,24 @@ public class StateStoreComponent extends DefaultComponent 
{
                             storeName);
                 }
                 backend = new MemoryKeyValueRepository();
+                ServiceHelper.startService(backend);
+                return backend;
             }
-            ServiceHelper.startService(backend);
-            return backend;
         });
+        boolean existingNamespaced = result instanceof 
NamespacedKeyValueRepository;
+        if (existingNamespaced != namespaced && backendFactory == null && 
!(result instanceof MemoryKeyValueRepository)) {
+            LOG.warn("Store '{}' was already initialized with namespaced={}. 
Ignoring namespaced={} for this endpoint.",
+                    storeName, existingNamespaced, namespaced);
+        }
+        return result;
+    }
+
+    public boolean isNamespaced() {
+        return namespaced;
+    }
+
+    public void setNamespaced(boolean namespaced) {
+        this.namespaced = namespaced;
     }
 
     public StateStoreBackendFactory getBackendFactory() {
diff --git 
a/components/camel-state-store/camel-state-store/src/main/java/org/apache/camel/component/statestore/StateStoreEndpoint.java
 
b/components/camel-state-store/camel-state-store/src/main/java/org/apache/camel/component/statestore/StateStoreEndpoint.java
index afedd7cc04c9..6e4996087848 100644
--- 
a/components/camel-state-store/camel-state-store/src/main/java/org/apache/camel/component/statestore/StateStoreEndpoint.java
+++ 
b/components/camel-state-store/camel-state-store/src/main/java/org/apache/camel/component/statestore/StateStoreEndpoint.java
@@ -76,6 +76,10 @@ public class StateStoreEndpoint extends DefaultEndpoint {
     @UriParam(description = "Time-to-live in milliseconds for entries. 0 means 
no expiry.", defaultValue = "0")
     private long ttl;
 
+    @UriParam(description = "Whether to use a store-specific namespace prefix 
to isolate keys on a shared backend repository.",
+              defaultValue = "true")
+    private boolean namespaced = true;
+
     public StateStoreEndpoint(String endpointUri, StateStoreComponent 
component) {
         super(endpointUri, component);
     }
@@ -94,7 +98,7 @@ public class StateStoreEndpoint extends DefaultEndpoint {
     protected void doStart() throws Exception {
         super.doStart();
         StateStoreComponent comp = (StateStoreComponent) getComponent();
-        backend = comp.getOrCreateBackend(storeName, backend);
+        backend = comp.getOrCreateBackend(storeName, backend, namespaced);
     }
 
     public String getStoreName() {
@@ -128,4 +132,12 @@ public class StateStoreEndpoint extends DefaultEndpoint {
     public void setTtl(long ttl) {
         this.ttl = ttl;
     }
+
+    public boolean isNamespaced() {
+        return namespaced;
+    }
+
+    public void setNamespaced(boolean namespaced) {
+        this.namespaced = namespaced;
+    }
 }
diff --git 
a/components/camel-state-store/camel-state-store/src/test/java/org/apache/camel/component/statestore/StateStoreAutoDiscoveryTest.java
 
b/components/camel-state-store/camel-state-store/src/test/java/org/apache/camel/component/statestore/StateStoreAutoDiscoveryTest.java
index 8f1cfba27b4b..bb54888f293d 100644
--- 
a/components/camel-state-store/camel-state-store/src/test/java/org/apache/camel/component/statestore/StateStoreAutoDiscoveryTest.java
+++ 
b/components/camel-state-store/camel-state-store/src/test/java/org/apache/camel/component/statestore/StateStoreAutoDiscoveryTest.java
@@ -52,9 +52,10 @@ class StateStoreAutoDiscoveryTest extends CamelTestSupport {
                 Map.of(StateStoreConstants.KEY, "key1"));
         assertThat(result).isEqualTo("hello");
 
-        // Verify our tracking backend was used, not the default 
MemoryKeyValueRepository
+        // Verify our tracking backend was used, not the default 
MemoryKeyValueRepository,
+        // and keys are namespaced to isolate this store from other stores and 
patterns
         
assertThat(trackingBackend).isInstanceOf(TrackingKeyValueRepository.class);
-        assertThat(trackingBackend.keys()).containsExactly("key1");
+        
assertThat(trackingBackend.keys()).containsExactly("statestore:myStore:key1");
     }
 
     @Override
diff --git 
a/components/camel-state-store/camel-state-store/src/test/java/org/apache/camel/component/statestore/StateStoreBackendFactoryTest.java
 
b/components/camel-state-store/camel-state-store/src/test/java/org/apache/camel/component/statestore/StateStoreBackendFactoryTest.java
index c35b4cc723b1..d3002a48ed76 100644
--- 
a/components/camel-state-store/camel-state-store/src/test/java/org/apache/camel/component/statestore/StateStoreBackendFactoryTest.java
+++ 
b/components/camel-state-store/camel-state-store/src/test/java/org/apache/camel/component/statestore/StateStoreBackendFactoryTest.java
@@ -63,6 +63,8 @@ class StateStoreBackendFactoryTest extends CamelTestSupport {
 
         assertThat(created).containsOnlyKeys("orders", "users");
         assertThat(created.get("orders").isStarted()).isTrue();
+        assertThat(created.get("orders").get("1")).isEqualTo("order-1");
+        assertThat(created.get("users").get("1")).isEqualTo("user-1");
         assertThat(registryBackend.keys()).isEmpty();
     }
 
@@ -71,7 +73,7 @@ class StateStoreBackendFactoryTest extends CamelTestSupport {
         template.requestBodyAndHeader("state-store:fallback?operation=put", 
"v", StateStoreConstants.KEY, "k");
 
         assertThat(created).doesNotContainKey("fallback");
-        assertThat(registryBackend.get("k")).isEqualTo("v");
+        
assertThat(registryBackend.get("statestore:fallback:k")).isEqualTo("v");
     }
 
     @Test
@@ -79,7 +81,7 @@ class StateStoreBackendFactoryTest extends CamelTestSupport {
         template.requestBodyAndHeader("direct:explicit", "v", 
StateStoreConstants.KEY, "k");
 
         assertThat(created).doesNotContainKey("explicit");
-        assertThat(explicitBackend.get("k")).isEqualTo("v");
+        
assertThat(explicitBackend.get("statestore:explicit:k")).isEqualTo("v");
     }
 
     @Override
diff --git 
a/components/camel-state-store/camel-state-store/src/test/java/org/apache/camel/component/statestore/StateStoreNamespaceIsolationTest.java
 
b/components/camel-state-store/camel-state-store/src/test/java/org/apache/camel/component/statestore/StateStoreNamespaceIsolationTest.java
new file mode 100644
index 000000000000..b83e8a9be605
--- /dev/null
+++ 
b/components/camel-state-store/camel-state-store/src/test/java/org/apache/camel/component/statestore/StateStoreNamespaceIsolationTest.java
@@ -0,0 +1,148 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.statestore;
+
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.camel.BindToRegistry;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.spi.KeyValueRepository;
+import org.apache.camel.support.MemoryKeyValueRepository;
+import org.apache.camel.test.junit5.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests that multiple state stores sharing the same underlying {@link 
KeyValueRepository} keep their keys and
+ * operations isolated within their respective namespaces.
+ */
+class StateStoreNamespaceIsolationTest extends CamelTestSupport {
+
+    private final MemoryKeyValueRepository sharedBackend = new 
MemoryKeyValueRepository();
+
+    @BindToRegistry("sharedBackend")
+    public KeyValueRepository getSharedBackend() {
+        return sharedBackend;
+    }
+
+    @Test
+    @SuppressWarnings("unchecked")
+    void testCrossStoreIsolationAndClear() {
+        // Store entries in storeA
+        template.requestBodyAndHeaders("direct:putA", "valA1", 
Map.of(StateStoreConstants.KEY, "item1"));
+        template.requestBodyAndHeaders("direct:putA", "valA2", 
Map.of(StateStoreConstants.KEY, "item2"));
+
+        // Store entry in storeB with identical key name
+        template.requestBodyAndHeaders("direct:putB", "valB1", 
Map.of(StateStoreConstants.KEY, "item1"));
+
+        // Pre-existing external key in shared backend (e.g. from an 
aggregation repository)
+        sharedBackend.put("aggregate:grp1", "holderData");
+
+        // Verify isolation
+        assertThat(template.requestBodyAndHeaders("direct:getA", null, 
Map.of(StateStoreConstants.KEY, "item1")))
+                .isEqualTo("valA1");
+        assertThat(template.requestBodyAndHeaders("direct:getB", null, 
Map.of(StateStoreConstants.KEY, "item1")))
+                .isEqualTo("valB1");
+
+        // Verify keys in storeA
+        Set<String> keysA = (Set<String>) template.requestBody("direct:keysA", 
(Object) null);
+        assertThat(keysA).containsExactlyInAnyOrder("item1", "item2");
+
+        // Clear storeA
+        template.requestBody("direct:clearA", (Object) null);
+
+        // storeA is now empty
+        assertThat((Set<String>) template.requestBody("direct:keysA", (Object) 
null)).isEmpty();
+        assertThat(template.requestBodyAndHeaders("direct:getA", null, 
Map.of(StateStoreConstants.KEY, "item1")))
+                .isNull();
+
+        // storeB and shared backend entries remain intact
+        assertThat(template.requestBodyAndHeaders("direct:getB", null, 
Map.of(StateStoreConstants.KEY, "item1")))
+                .isEqualTo("valB1");
+        
assertThat(sharedBackend.get("aggregate:grp1")).isEqualTo("holderData");
+    }
+
+    @Test
+    void testPatternKeyNamesDoNotCollide() {
+        // Store a key with a prefix that resembles aggregation or idempotent 
pattern prefixes
+        template.requestBodyAndHeaders("direct:putA", "customPayload", 
Map.of(StateStoreConstants.KEY, "aggregate:grp1"));
+
+        // Ensure key in underlying repository is safely prefixed under the 
state store namespace
+        
assertThat(sharedBackend.get("statestore:storeA:aggregate:grp1")).isEqualTo("customPayload");
+
+        // Underlying raw aggregate:grp1 is not overwritten
+        assertThat(sharedBackend.get("aggregate:grp1")).isNull();
+    }
+
+    @Test
+    @SuppressWarnings("unchecked")
+    void testStoreNamesSharingAPrefixAreIsolated() {
+        template.requestBodyAndHeaders("direct:putDynamic", "archived",
+                Map.of("reproStore", "orders:archive", 
StateStoreConstants.KEY, "x"));
+        template.requestBodyAndHeaders("direct:putDynamic", "live",
+                Map.of("reproStore", "orders", StateStoreConstants.KEY, "y"));
+
+        Set<String> keys = (Set<String>) 
template.requestBodyAndHeaders("direct:keysDynamic", null,
+                Map.of("reproStore", "orders"));
+        assertThat(keys).containsExactly("y");
+
+        // Clear on 'orders' must not wipe 'orders:archive'
+        template.requestBodyAndHeaders("direct:clearDynamic", null,
+                Map.of("reproStore", "orders"));
+
+        assertThat((Set<String>) 
template.requestBodyAndHeaders("direct:keysDynamic", null,
+                Map.of("reproStore", "orders"))).isEmpty();
+        assertThat(template.requestBodyAndHeaders("direct:getDynamic", null,
+                Map.of("reproStore", "orders:archive", 
StateStoreConstants.KEY, "x")))
+                .isEqualTo("archived");
+    }
+
+    @Test
+    void testOptOutNamespacingForBackwardCompatibility() {
+        // Data written by 4.23 without namespacing can be accessed by opting 
out of namespacing
+        sharedBackend.put("legacyKey", "legacyValue");
+
+        Object value = 
template.requestBodyAndHeaders("direct:getUnnamespaced", null,
+                Map.of(StateStoreConstants.KEY, "legacyKey"));
+        assertThat(value).isEqualTo("legacyValue");
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:putA").to("state-store:storeA?operation=put");
+                from("direct:getA").to("state-store:storeA?operation=get");
+                from("direct:keysA").to("state-store:storeA?operation=keys");
+                from("direct:clearA").to("state-store:storeA?operation=clear");
+
+                from("direct:putB").to("state-store:storeB?operation=put");
+                from("direct:getB").to("state-store:storeB?operation=get");
+
+                
from("direct:putDynamic").toD("state-store:${header.reproStore}?operation=put");
+                
from("direct:getDynamic").toD("state-store:${header.reproStore}?operation=get");
+                
from("direct:keysDynamic").toD("state-store:${header.reproStore}?operation=keys");
+                
from("direct:clearDynamic").toD("state-store:${header.reproStore}?operation=clear");
+
+                
from("direct:getUnnamespaced").to("state-store:legacyStore?namespaced=false&operation=get");
+            }
+        };
+    }
+}
diff --git 
a/components/camel-state-store/camel-state-store/src/test/java/org/apache/camel/component/statestore/StateStoreStreamBodyTest.java
 
b/components/camel-state-store/camel-state-store/src/test/java/org/apache/camel/component/statestore/StateStoreStreamBodyTest.java
index 69e1820fc302..2c591322c53e 100644
--- 
a/components/camel-state-store/camel-state-store/src/test/java/org/apache/camel/component/statestore/StateStoreStreamBodyTest.java
+++ 
b/components/camel-state-store/camel-state-store/src/test/java/org/apache/camel/component/statestore/StateStoreStreamBodyTest.java
@@ -85,7 +85,7 @@ class StateStoreStreamBodyTest extends CamelTestSupport {
         // again, with the stream cache reset
         put("direct:putRedelivery", new ByteArrayInputStream(BINARY), 
"redelivered");
 
-        Object value = redeliveryBackend.get("redelivered");
+        Object value = 
redeliveryBackend.get("statestore:redeliveryStore:redelivered");
         assertThat(value).isInstanceOf(byte[].class);
         assertThat((byte[]) value).containsExactly(BINARY);
     }
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/processor/CacheProcessorTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/processor/CacheProcessorTest.java
index eb3e385e3881..35cfec932e68 100644
--- 
a/core/camel-core/src/test/java/org/apache/camel/processor/CacheProcessorTest.java
+++ 
b/core/camel-core/src/test/java/org/apache/camel/processor/CacheProcessorTest.java
@@ -16,11 +16,14 @@
  */
 package org.apache.camel.processor;
 
+import java.util.Map;
 import java.util.concurrent.TimeUnit;
 
 import org.apache.camel.ContextTestSupport;
 import org.apache.camel.builder.RouteBuilder;
 import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.spi.KeyValueRepository;
+import org.apache.camel.support.MemoryKeyValueRepository;
 import org.junit.jupiter.api.Test;
 
 import static org.assertj.core.api.Assertions.assertThat;
@@ -173,6 +176,43 @@ class CacheProcessorTest extends ContextTestSupport {
         MockEndpoint.assertIsSatisfied(context);
     }
 
+    @Test
+    void testDocumentedInvalidationRecipeDropsCachedEntry() throws Exception {
+        KeyValueRepository repo = new MemoryKeyValueRepository();
+        context.getRegistry().bind("productCache", repo);
+
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:getProduct")
+                        
.cache(simple("${header.productId}")).keyValueRepository("productCache")
+                        .process(e -> e.getMessage().setBody("v" + 
e.getIn().getHeader("version", Integer.class)))
+                        .end();
+
+                from("direct:invalidate")
+                        .process(exchange -> {
+                            String productId = 
exchange.getIn().getHeader("productId", String.class);
+                            KeyValueRepository cache = 
exchange.getContext().getRegistry()
+                                    .lookupByNameAndType("productCache", 
KeyValueRepository.class);
+                            cache.delete(productId);
+                        });
+            }
+        });
+
+        assertThat(template.requestBodyAndHeaders("direct:getProduct", null,
+                Map.of("productId", "42", "version", 1))).isEqualTo("v1");
+        // cached — version 2 ignored
+        assertThat(template.requestBodyAndHeaders("direct:getProduct", null,
+                Map.of("productId", "42", "version", 2))).isEqualTo("v1");
+
+        // invalidate via recipe from docs
+        template.sendBodyAndHeader("direct:invalidate", null, "productId", 
"42");
+
+        // cache miss — reloads with version 2
+        assertThat(template.requestBodyAndHeaders("direct:getProduct", null,
+                Map.of("productId", "42", "version", 2))).isEqualTo("v2");
+    }
+
     @Override
     protected RouteBuilder createRouteBuilder() {
         return new RouteBuilder() {
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/support/KeyValueAggregationRepositoryTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/support/KeyValueAggregationRepositoryTest.java
index 63157cfb31f3..8e3aea6fd455 100644
--- 
a/core/camel-core/src/test/java/org/apache/camel/support/KeyValueAggregationRepositoryTest.java
+++ 
b/core/camel-core/src/test/java/org/apache/camel/support/KeyValueAggregationRepositoryTest.java
@@ -251,6 +251,22 @@ class KeyValueAggregationRepositoryTest {
         
assertThat(invoices.scan(camelContext)).containsExactly(invoice.getExchangeId());
     }
 
+    @Test
+    void testUnexpectedPayloadHandledGracefully() {
+        kvRepository.put("aggregate:corruptKey", "plainStringPayload");
+        kvRepository.put("completed:corruptExchangeId", "plainStringPayload");
+
+        Exchange retrieved = aggregationRepository.get(camelContext, 
"corruptKey");
+        assertThat(retrieved).isNull();
+
+        Exchange newEx = createExchange("recovered");
+        Exchange oldEx = aggregationRepository.add(camelContext, "corruptKey", 
newEx);
+        assertThat(oldEx).isNull();
+
+        Exchange recovered = aggregationRepository.recover(camelContext, 
"corruptExchangeId");
+        assertThat(recovered).isNull();
+    }
+
     private Exchange createExchange(String body) {
         Exchange exchange = new DefaultExchange(camelContext);
         exchange.getIn().setBody(body);
diff --git 
a/core/camel-support/src/generated/java/org/apache/camel/support/NamespacedKeyValueRepositoryConfigurer.java
 
b/core/camel-support/src/generated/java/org/apache/camel/support/NamespacedKeyValueRepositoryConfigurer.java
new file mode 100644
index 000000000000..3d440a1e920c
--- /dev/null
+++ 
b/core/camel-support/src/generated/java/org/apache/camel/support/NamespacedKeyValueRepositoryConfigurer.java
@@ -0,0 +1,49 @@
+/* Generated by camel build tools - do NOT edit this file! */
+package org.apache.camel.support;
+
+import javax.annotation.processing.Generated;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.spi.ExtendedPropertyConfigurerGetter;
+import org.apache.camel.spi.PropertyConfigurerGetter;
+import org.apache.camel.spi.ConfigurerStrategy;
+import org.apache.camel.spi.GeneratedPropertyConfigurer;
+import org.apache.camel.util.CaseInsensitiveMap;
+import org.apache.camel.support.NamespacedKeyValueRepository;
+
+/**
+ * Generated by camel build tools - do NOT edit this file!
+ */
+@Generated("org.apache.camel.maven.packaging.GenerateConfigurerMojo")
+@SuppressWarnings("unchecked")
+public class NamespacedKeyValueRepositoryConfigurer extends 
org.apache.camel.support.component.PropertyConfigurerSupport implements 
GeneratedPropertyConfigurer, PropertyConfigurerGetter {
+
+    @Override
+    public boolean configure(CamelContext camelContext, Object obj, String 
name, Object value, boolean ignoreCase) {
+        org.apache.camel.support.NamespacedKeyValueRepository target = 
(org.apache.camel.support.NamespacedKeyValueRepository) obj;
+        switch (ignoreCase ? name.toLowerCase(Locale.ROOT) : name) {
+        case "namespace": target.setNamespace(property(camelContext, 
java.lang.String.class, value)); return true;
+        default: return false;
+        }
+    }
+
+    @Override
+    public Class<?> getOptionType(String name, boolean ignoreCase) {
+        switch (ignoreCase ? name.toLowerCase(Locale.ROOT) : name) {
+        case "namespace": return java.lang.String.class;
+        default: return null;
+        }
+    }
+
+    @Override
+    public Object getOptionValue(Object obj, String name, boolean ignoreCase) {
+        org.apache.camel.support.NamespacedKeyValueRepository target = 
(org.apache.camel.support.NamespacedKeyValueRepository) obj;
+        switch (ignoreCase ? name.toLowerCase(Locale.ROOT) : name) {
+        case "namespace": return target.getNamespace();
+        default: return null;
+        }
+    }
+}
+
diff --git 
a/core/camel-support/src/generated/resources/META-INF/services/org/apache/camel/bean.properties
 
b/core/camel-support/src/generated/resources/META-INF/services/org/apache/camel/bean.properties
index 0234c0b2afeb..e00906d89041 100644
--- 
a/core/camel-support/src/generated/resources/META-INF/services/org/apache/camel/bean.properties
+++ 
b/core/camel-support/src/generated/resources/META-INF/services/org/apache/camel/bean.properties
@@ -1,5 +1,5 @@
 # Generated by camel build tools - do NOT edit this file!
-bean=AcceptAllHeaderFilterStrategy DefaultHeaderFilterStrategy 
FileIdempotentRepository KeyValueAggregationRepository 
KeyValueIdempotentRepository MemoryIdempotentRepository 
MemoryKeyValueRepository ThrottlingExceptionRoutePolicy 
ThrottlingInflightRoutePolicy
+bean=AcceptAllHeaderFilterStrategy DefaultHeaderFilterStrategy 
FileIdempotentRepository KeyValueAggregationRepository 
KeyValueIdempotentRepository MemoryIdempotentRepository 
MemoryKeyValueRepository NamespacedKeyValueRepository 
ThrottlingExceptionRoutePolicy ThrottlingInflightRoutePolicy
 groupId=org.apache.camel
 artifactId=camel-support
 version=4.24.0-SNAPSHOT
diff --git 
a/core/camel-support/src/generated/resources/META-INF/services/org/apache/camel/bean/NamespacedKeyValueRepository.json
 
b/core/camel-support/src/generated/resources/META-INF/services/org/apache/camel/bean/NamespacedKeyValueRepository.json
new file mode 100644
index 000000000000..55274077a939
--- /dev/null
+++ 
b/core/camel-support/src/generated/resources/META-INF/services/org/apache/camel/bean/NamespacedKeyValueRepository.json
@@ -0,0 +1,15 @@
+{
+  "bean": {
+    "kind": "bean",
+    "name": "NamespacedKeyValueRepository",
+    "javaType": "org.apache.camel.support.NamespacedKeyValueRepository",
+    "interfaceType": "org.apache.camel.spi.KeyValueRepository",
+    "title": "Namespaced Key Value Repository",
+    "description": "A KeyValueRepository that partitions keys using a 
namespace prefix.",
+    "deprecated": false,
+    "groupId": "org.apache.camel",
+    "artifactId": "camel-support",
+    "version": "4.24.0-SNAPSHOT"
+  }
+}
+
diff --git 
a/core/camel-support/src/generated/resources/META-INF/services/org/apache/camel/configurer/org.apache.camel.support.NamespacedKeyValueRepository
 
b/core/camel-support/src/generated/resources/META-INF/services/org/apache/camel/configurer/org.apache.camel.support.NamespacedKeyValueRepository
new file mode 100644
index 000000000000..3250ddb766d4
--- /dev/null
+++ 
b/core/camel-support/src/generated/resources/META-INF/services/org/apache/camel/configurer/org.apache.camel.support.NamespacedKeyValueRepository
@@ -0,0 +1,2 @@
+# Generated by camel build tools - do NOT edit this file!
+class=org.apache.camel.support.NamespacedKeyValueRepositoryConfigurer
diff --git 
a/core/camel-support/src/main/java/org/apache/camel/support/KeyValueAggregationRepository.java
 
b/core/camel-support/src/main/java/org/apache/camel/support/KeyValueAggregationRepository.java
index 393492249da2..ea63ab6cb881 100644
--- 
a/core/camel-support/src/main/java/org/apache/camel/support/KeyValueAggregationRepository.java
+++ 
b/core/camel-support/src/main/java/org/apache/camel/support/KeyValueAggregationRepository.java
@@ -135,20 +135,20 @@ public class KeyValueAggregationRepository extends 
ServiceSupport
     public Exchange add(CamelContext camelContext, String key, Exchange 
exchange) {
         LOG.trace("Adding an Exchange with ID {} for key {}", 
exchange.getExchangeId(), key);
         DefaultExchangeHolder newHolder = 
DefaultExchangeHolder.marshal(exchange, true, allowSerializedHeaders);
-        DefaultExchangeHolder oldHolder = (DefaultExchangeHolder) 
repository.put(AGGREGATE_PREFIX + key, newHolder);
+        Object oldHolder = repository.put(AGGREGATE_PREFIX + key, newHolder);
         return unmarshallExchange(camelContext, oldHolder);
     }
 
     @Override
     public Exchange get(CamelContext camelContext, String key) {
-        return unmarshallExchange(camelContext, (DefaultExchangeHolder) 
repository.get(AGGREGATE_PREFIX + key));
+        return unmarshallExchange(camelContext, 
repository.get(AGGREGATE_PREFIX + key));
     }
 
     @Override
     public void remove(CamelContext camelContext, String key, Exchange 
exchange) {
         LOG.trace("Removing an Exchange with ID {} for key {}", 
exchange.getExchangeId(), key);
-        DefaultExchangeHolder holder = (DefaultExchangeHolder) 
repository.delete(AGGREGATE_PREFIX + key);
-        if (useRecovery && holder != null) {
+        Object holder = repository.delete(AGGREGATE_PREFIX + key);
+        if (useRecovery && holder instanceof DefaultExchangeHolder) {
             // Store the given exchange under the exchangeId for potential 
recovery. The removed holder is not used as
             // it can be older than the exchange: when a group is completed by 
an incoming exchange, the last
             // aggregated exchange is not added to the repository before it is 
removed.
@@ -190,7 +190,7 @@ public class KeyValueAggregationRepository extends 
ServiceSupport
         if (!useRecovery) {
             return null;
         }
-        return unmarshallExchange(camelContext, (DefaultExchangeHolder) 
repository.get(completedPrefix + exchangeId));
+        return unmarshallExchange(camelContext, repository.get(completedPrefix 
+ exchangeId));
     }
 
     @Override
@@ -285,12 +285,17 @@ public class KeyValueAggregationRepository extends 
ServiceSupport
         ServiceHelper.stopService(repository);
     }
 
-    private Exchange unmarshallExchange(CamelContext camelContext, 
DefaultExchangeHolder holder) {
+    private Exchange unmarshallExchange(CamelContext camelContext, Object 
holder) {
         if (holder == null) {
             return null;
         }
+        if (!(holder instanceof DefaultExchangeHolder exchangeHolder)) {
+            LOG.warn("Ignoring entry of type {} in 
KeyValueAggregationRepository as it is not a DefaultExchangeHolder",
+                    holder.getClass().getName());
+            return null;
+        }
         Exchange exchange = new DefaultExchange(camelContext);
-        DefaultExchangeHolder.unmarshal(exchange, holder);
+        DefaultExchangeHolder.unmarshal(exchange, exchangeHolder);
         return exchange;
     }
 }
diff --git 
a/core/camel-support/src/main/java/org/apache/camel/support/NamespacedKeyValueRepository.java
 
b/core/camel-support/src/main/java/org/apache/camel/support/NamespacedKeyValueRepository.java
new file mode 100644
index 000000000000..f759360c10d5
--- /dev/null
+++ 
b/core/camel-support/src/main/java/org/apache/camel/support/NamespacedKeyValueRepository.java
@@ -0,0 +1,271 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.support;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.camel.spi.Configurer;
+import org.apache.camel.spi.KeyValueRepository;
+import org.apache.camel.spi.Metadata;
+import org.apache.camel.support.service.ServiceHelper;
+import org.apache.camel.support.service.ServiceSupport;
+import org.apache.camel.util.ObjectHelper;
+
+/**
+ * A {@link KeyValueRepository} wrapper that isolates entries by prepending a 
namespace prefix to all keys.
+ * <p/>
+ * This adapter ensures that multiple components, patterns (e.g. Cache EIP, 
State Store, Aggregator, Idempotent
+ * Consumer), or named stores sharing a single underlying {@link 
KeyValueRepository} operate in isolated key partitions
+ * without key collisions or unintended cross-namespace mutations during 
operations such as {@link #clear()} or
+ * {@link #keys()}.
+ *
+ * @since 4.24
+ */
+@Metadata(label = "bean",
+          description = "A KeyValueRepository that partitions keys using a 
namespace prefix.",
+          annotations = { 
"interfaceName=org.apache.camel.spi.KeyValueRepository" })
+@Configurer(metadataOnly = true)
+public class NamespacedKeyValueRepository extends ServiceSupport implements 
KeyValueRepository {
+
+    private KeyValueRepository delegate;
+    private String prefix;
+
+    /**
+     * Creates an uninitialized namespaced repository adapter. The delegate 
repository and namespace should be set
+     * before starting.
+     */
+    public NamespacedKeyValueRepository() {
+    }
+
+    /**
+     * Creates a new namespaced wrapper around the given repository.
+     *
+     * @param delegate  the underlying key-value repository; must not be 
{@code null}
+     * @param namespace the namespace or prefix to isolate keys; must not be 
{@code null}
+     */
+    public NamespacedKeyValueRepository(KeyValueRepository delegate, String 
namespace) {
+        this.delegate = ObjectHelper.notNull(delegate, "delegate");
+        setNamespace(namespace);
+    }
+
+    /**
+     * Creates a new {@link NamespacedKeyValueRepository} wrapping the given 
repository, or returns the repository if it
+     * is already wrapped with the identical prefix.
+     *
+     * @param  repository the key-value repository to wrap
+     * @param  namespace  the namespace prefix to apply
+     * @return            the namespaced repository wrapper
+     */
+    public static KeyValueRepository namespaced(KeyValueRepository repository, 
String namespace) {
+        if (repository == null) {
+            return null;
+        }
+        ObjectHelper.notNull(namespace, "namespace");
+        String p = namespace.endsWith(":") ? namespace : namespace + ":";
+        if (repository instanceof NamespacedKeyValueRepository nkvr) {
+            if (nkvr.getNamespace().equals(p)) {
+                return repository;
+            }
+        }
+        return new NamespacedKeyValueRepository(repository, p);
+    }
+
+    /**
+     * Returns the underlying delegated {@link KeyValueRepository}.
+     */
+    public KeyValueRepository getDelegate() {
+        return delegate;
+    }
+
+    /**
+     * Sets the underlying delegated {@link KeyValueRepository}.
+     */
+    public void setDelegate(KeyValueRepository delegate) {
+        this.delegate = delegate;
+    }
+
+    /**
+     * Returns the namespace applied to all keys.
+     */
+    public String getNamespace() {
+        return prefix;
+    }
+
+    /**
+     * Sets the namespace prefix to isolate keys. If the namespace does not 
end with ':', a trailing colon is added.
+     */
+    public void setNamespace(String namespace) {
+        ObjectHelper.notNull(namespace, "namespace");
+        this.prefix = namespace.endsWith(":") ? namespace : namespace + ":";
+    }
+
+    @Override
+    public Object get(String key) {
+        ObjectHelper.notNull(delegate, "delegate");
+        ObjectHelper.notNull(prefix, "prefix");
+        ObjectHelper.notNull(key, "key");
+        return delegate.get(prefix + key);
+    }
+
+    @Override
+    public Object put(String key, Object value) {
+        ObjectHelper.notNull(delegate, "delegate");
+        ObjectHelper.notNull(prefix, "prefix");
+        ObjectHelper.notNull(key, "key");
+        return delegate.put(prefix + key, value);
+    }
+
+    @Override
+    public Object put(String key, Object value, Duration ttl) {
+        ObjectHelper.notNull(delegate, "delegate");
+        ObjectHelper.notNull(prefix, "prefix");
+        ObjectHelper.notNull(key, "key");
+        return delegate.put(prefix + key, value, ttl);
+    }
+
+    @Override
+    public Object delete(String key) {
+        ObjectHelper.notNull(delegate, "delegate");
+        ObjectHelper.notNull(prefix, "prefix");
+        ObjectHelper.notNull(key, "key");
+        return delegate.delete(prefix + key);
+    }
+
+    @Override
+    public boolean contains(String key) {
+        ObjectHelper.notNull(delegate, "delegate");
+        ObjectHelper.notNull(prefix, "prefix");
+        ObjectHelper.notNull(key, "key");
+        return delegate.contains(prefix + key);
+    }
+
+    @Override
+    public Set<String> keys() {
+        ObjectHelper.notNull(delegate, "delegate");
+        ObjectHelper.notNull(prefix, "prefix");
+        Set<String> allKeys = delegate.keys();
+        if (allKeys == null || allKeys.isEmpty()) {
+            return Collections.emptySet();
+        }
+        Set<String> filtered = new LinkedHashSet<>();
+        for (String k : allKeys) {
+            if (k != null && k.startsWith(prefix)) {
+                filtered.add(k.substring(prefix.length()));
+            }
+        }
+        return Collections.unmodifiableSet(filtered);
+    }
+
+    /**
+     * Clears all entries within this repository's namespace by deleting 
matching keys individually from the underlying
+     * delegate repository. Note that on shared backends this operation is 
non-atomic and executes in O(n) relative to
+     * the total number of keys in the delegate.
+     */
+    @Override
+    public void clear() {
+        ObjectHelper.notNull(delegate, "delegate");
+        ObjectHelper.notNull(prefix, "prefix");
+        Set<String> allKeys = delegate.keys();
+        if (allKeys != null && !allKeys.isEmpty()) {
+            List<String> toDelete = new ArrayList<>();
+            for (String k : allKeys) {
+                if (k != null && k.startsWith(prefix)) {
+                    toDelete.add(k);
+                }
+            }
+            for (String k : toDelete) {
+                delegate.delete(k);
+            }
+        }
+    }
+
+    @Override
+    public Object putIfAbsent(String key, Object value) {
+        ObjectHelper.notNull(delegate, "delegate");
+        ObjectHelper.notNull(prefix, "prefix");
+        ObjectHelper.notNull(key, "key");
+        return delegate.putIfAbsent(prefix + key, value);
+    }
+
+    @Override
+    public Object putIfAbsent(String key, Object value, Duration ttl) {
+        ObjectHelper.notNull(delegate, "delegate");
+        ObjectHelper.notNull(prefix, "prefix");
+        ObjectHelper.notNull(key, "key");
+        return delegate.putIfAbsent(prefix + key, value, ttl);
+    }
+
+    @Override
+    public boolean replace(String key, Object expectedOldValue, Object 
newValue, Duration ttl) {
+        ObjectHelper.notNull(delegate, "delegate");
+        ObjectHelper.notNull(prefix, "prefix");
+        ObjectHelper.notNull(key, "key");
+        return delegate.replace(prefix + key, expectedOldValue, newValue, ttl);
+    }
+
+    @Override
+    public boolean delete(String key, Object expectedValue) {
+        ObjectHelper.notNull(delegate, "delegate");
+        ObjectHelper.notNull(prefix, "prefix");
+        ObjectHelper.notNull(key, "key");
+        return delegate.delete(prefix + key, expectedValue);
+    }
+
+    @Override
+    public int size() {
+        ObjectHelper.notNull(delegate, "delegate");
+        ObjectHelper.notNull(prefix, "prefix");
+        Set<String> allKeys = delegate.keys();
+        if (allKeys == null || allKeys.isEmpty()) {
+            return 0;
+        }
+        int count = 0;
+        for (String k : allKeys) {
+            if (k != null && k.startsWith(prefix)) {
+                count++;
+            }
+        }
+        return count;
+    }
+
+    @Override
+    protected void doStart() throws Exception {
+        ObjectHelper.notNull(delegate, "delegate");
+        ObjectHelper.notNull(prefix, "prefix");
+        ServiceHelper.startService(delegate);
+    }
+
+    @Override
+    protected void doStop() throws Exception {
+        ServiceHelper.stopService(delegate);
+    }
+
+    @Override
+    protected void doShutdown() throws Exception {
+        ServiceHelper.stopAndShutdownService(delegate);
+    }
+
+    @Override
+    public String toString() {
+        return "NamespacedKeyValueRepository[" + prefix + " -> " + delegate + 
"]";
+    }
+}
diff --git 
a/core/camel-support/src/test/java/org/apache/camel/support/NamespacedKeyValueRepositoryTest.java
 
b/core/camel-support/src/test/java/org/apache/camel/support/NamespacedKeyValueRepositoryTest.java
new file mode 100644
index 000000000000..10d5ccd555a7
--- /dev/null
+++ 
b/core/camel-support/src/test/java/org/apache/camel/support/NamespacedKeyValueRepositoryTest.java
@@ -0,0 +1,185 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.support;
+
+import java.time.Duration;
+import java.util.Set;
+
+import org.apache.camel.spi.KeyValueRepository;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+class NamespacedKeyValueRepositoryTest {
+
+    private MemoryKeyValueRepository delegate;
+    private NamespacedKeyValueRepository repoA;
+    private NamespacedKeyValueRepository repoB;
+
+    @BeforeEach
+    void setUp() throws Exception {
+        delegate = new MemoryKeyValueRepository();
+        delegate.start();
+
+        repoA = new NamespacedKeyValueRepository(delegate, "nsA");
+        repoA.start();
+
+        repoB = new NamespacedKeyValueRepository(delegate, "nsB:");
+        repoB.start();
+    }
+
+    @AfterEach
+    void tearDown() throws Exception {
+        repoA.stop();
+        repoB.stop();
+        delegate.stop();
+    }
+
+    @Test
+    void testPutAndGet() {
+        repoA.put("key1", "valueA");
+        repoB.put("key1", "valueB");
+
+        assertThat(repoA.get("key1")).isEqualTo("valueA");
+        assertThat(repoB.get("key1")).isEqualTo("valueB");
+
+        assertThat(delegate.get("nsA:key1")).isEqualTo("valueA");
+        assertThat(delegate.get("nsB:key1")).isEqualTo("valueB");
+    }
+
+    @Test
+    void testContains() {
+        repoA.put("key1", "valueA");
+
+        assertThat(repoA.contains("key1")).isTrue();
+        assertThat(repoB.contains("key1")).isFalse();
+        assertThat(repoA.contains("key2")).isFalse();
+    }
+
+    @Test
+    void testDelete() {
+        repoA.put("key1", "valueA");
+        repoB.put("key1", "valueB");
+
+        Object deleted = repoA.delete("key1");
+        assertThat(deleted).isEqualTo("valueA");
+
+        assertThat(repoA.get("key1")).isNull();
+        assertThat(repoB.get("key1")).isEqualTo("valueB");
+        assertThat(delegate.get("nsA:key1")).isNull();
+        assertThat(delegate.get("nsB:key1")).isEqualTo("valueB");
+    }
+
+    @Test
+    void testKeys() {
+        repoA.put("key1", "v1");
+        repoA.put("key2", "v2");
+        repoB.put("key3", "v3");
+
+        Set<String> keysA = repoA.keys();
+        assertThat(keysA).containsExactlyInAnyOrder("key1", "key2");
+
+        Set<String> keysB = repoB.keys();
+        assertThat(keysB).containsExactly("key3");
+    }
+
+    @Test
+    void testSize() {
+        repoA.put("key1", "v1");
+        repoA.put("key2", "v2");
+        repoB.put("key3", "v3");
+
+        assertThat(repoA.size()).isEqualTo(2);
+        assertThat(repoB.size()).isEqualTo(1);
+    }
+
+    @Test
+    void testClearOnlyClearsOwnNamespace() {
+        repoA.put("key1", "v1");
+        repoA.put("key2", "v2");
+        repoB.put("key1", "vB1");
+        repoB.put("key2", "vB2");
+
+        repoA.clear();
+
+        assertThat(repoA.keys()).isEmpty();
+        assertThat(repoA.size()).isZero();
+
+        assertThat(repoB.keys()).containsExactlyInAnyOrder("key1", "key2");
+        assertThat(repoB.size()).isEqualTo(2);
+        assertThat(repoB.get("key1")).isEqualTo("vB1");
+    }
+
+    @Test
+    void testPutIfAbsent() {
+        Object first = repoA.putIfAbsent("key1", "val1");
+        assertThat(first).isNull();
+
+        Object second = repoA.putIfAbsent("key1", "val2");
+        assertThat(second).isEqualTo("val1");
+        assertThat(repoA.get("key1")).isEqualTo("val1");
+
+        Object bResult = repoB.putIfAbsent("key1", "valB");
+        assertThat(bResult).isNull();
+        assertThat(repoB.get("key1")).isEqualTo("valB");
+    }
+
+    @Test
+    void testReplace() {
+        repoA.put("key1", "oldVal");
+
+        boolean failed = repoA.replace("key1", "wrong", "newVal", 
Duration.ofMinutes(1));
+        assertThat(failed).isFalse();
+        assertThat(repoA.get("key1")).isEqualTo("oldVal");
+
+        boolean replaced = repoA.replace("key1", "oldVal", "newVal", 
Duration.ofMinutes(1));
+        assertThat(replaced).isTrue();
+        assertThat(repoA.get("key1")).isEqualTo("newVal");
+    }
+
+    @Test
+    void testDeleteWithValue() {
+        repoA.put("key1", "val1");
+
+        boolean failed = repoA.delete("key1", "wrong");
+        assertThat(failed).isFalse();
+        assertThat(repoA.get("key1")).isEqualTo("val1");
+
+        boolean deleted = repoA.delete("key1", "val1");
+        assertThat(deleted).isTrue();
+        assertThat(repoA.get("key1")).isNull();
+    }
+
+    @Test
+    void testFactoryMethodAvoidsRedundantWrapping() {
+        KeyValueRepository wrapped = 
NamespacedKeyValueRepository.namespaced(delegate, "myNs");
+        KeyValueRepository wrappedAgain = 
NamespacedKeyValueRepository.namespaced(wrapped, "myNs");
+
+        assertThat(wrappedAgain).isSameAs(wrapped);
+    }
+
+    @Test
+    void testNullKeyThrows() {
+        assertThatThrownBy(() -> repoA.get(null))
+                .isInstanceOf(IllegalArgumentException.class);
+        assertThatThrownBy(() -> repoA.put(null, "val"))
+                .isInstanceOf(IllegalArgumentException.class);
+    }
+}
diff --git 
a/core/camel-util/src/main/java/org/apache/camel/util/ArtifactUtils.java 
b/core/camel-util/src/main/java/org/apache/camel/util/ArtifactUtils.java
index f445cb9985a9..336f01a1ae4c 100644
--- a/core/camel-util/src/main/java/org/apache/camel/util/ArtifactUtils.java
+++ b/core/camel-util/src/main/java/org/apache/camel/util/ArtifactUtils.java
@@ -657,6 +657,8 @@ public final class ArtifactUtils {
                     
"org.apache.camel.support.MemoryKeyValueRepository|org.apache.camel.spi.KeyValueRepository|camel-support"),
             Map.<String, String> entry("MongoDbIdempotentRepository",
                     
"org.apache.camel.component.mongodb.processor.idempotent.MongoDbIdempotentRepository|org.apache.camel.spi.IdempotentRepository|camel-mongodb"),
+            Map.<String, String> entry("NamespacedKeyValueRepository",
+                    
"org.apache.camel.support.NamespacedKeyValueRepository|org.apache.camel.spi.KeyValueRepository|camel-support"),
             Map.<String, String> entry("OpaSecurityPolicy",
                     
"org.apache.camel.component.opa.security.OpaSecurityPolicy|org.apache.camel.spi.AuthorizationPolicy|camel-opa"),
             Map.<String, String> entry("OpenFgaSecurityPolicy",
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index 3bd88a9ea434..b53a6ee48f2b 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -17,6 +17,18 @@ See the xref:camel-upgrade-recipes-tool.adoc[documentation] 
page for details.
 
 The `camel-observability-services` component has been promoted from _Preview_ 
to _Stable_ support level.
 
+=== KeyValueRepository and State Store - namespace partitioning on shared 
backends
+
+In Camel 4.23, `state-store` endpoints stored keys directly into the 
underlying `KeyValueRepository` without namespacing. When a single 
`KeyValueRepository` was shared between multiple state stores, the Cache EIP, 
or repository adapters (such as `KeyValueAggregationRepository` or 
`KeyValueIdempotentRepository`), operations on one store (such as `clear()`) or 
key collisions could affect entries of another component or pattern.
+
+Starting in 4.24:
+
+* `StateStoreComponent` automatically namespaces keys on shared backend 
repositories (auto-discovered from the registry or referenced via 
`backend=#ref`) under `statestore:<storeName>:<key>`. Delimiter characters in 
store names are escaped to guarantee that store names sharing a prefix remain 
strictly isolated. Operations such as `clear()`, `keys()`, and `size()` are 
scoped to the store's namespace.
+* Dedicated backends created by `StateStoreBackendFactory` or the default 
per-store in-memory fallback are not namespaced.
+* To maintain backward compatibility with un-namespaced entries written by 
Camel 4.23 on a shared backend without migrating keys, users can opt out of 
namespacing by configuring `namespaced=false` on the endpoint 
(`state-store:myStore?namespaced=false`) or on the component 
(`camel.component.state-store.namespaced=false`).
+* The Cache EIP stores keys directly without a namespace prefix, preserving 
documented cache invalidation recipes (`cache.delete(key)` and 
`cache.clear()`). Note that because cache keys are un-prefixed, calling 
`clear()` on a `KeyValueRepository` shared with the Cache EIP will clear all 
entries in that repository, including state-store entries. It is recommended to 
use a dedicated repository for the Cache EIP when co-located with state-store.
+* `KeyValueAggregationRepository` safely verifies entry types before 
unmarshalling, ignoring unexpected types gracefully instead of failing with a 
class cast exception.
+
 === camel-ehcache - shared CacheManager and changed defaults
 
 The following user-visible changes take effect in 4.24:
diff --git 
a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/StateStoreComponentBuilderFactory.java
 
b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/StateStoreComponentBuilderFactory.java
index 99ce12088646..38fcac0c94e5 100644
--- 
a/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/StateStoreComponentBuilderFactory.java
+++ 
b/dsl/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/StateStoreComponentBuilderFactory.java
@@ -77,6 +77,24 @@ public interface StateStoreComponentBuilderFactory {
         }
     
         
+        /**
+         * Whether to use a store-specific namespace prefix to isolate keys on 
a
+         * shared backend repository.
+         * 
+         * The option is a: &lt;code&gt;boolean&lt;/code&gt; type.
+         * 
+         * Default: true
+         * Group: producer
+         * 
+         * @param namespaced the value to set
+         * @return the dsl builder
+         */
+        default StateStoreComponentBuilder namespaced(boolean namespaced) {
+            doSetProperty("namespaced", namespaced);
+            return this;
+        }
+    
+        
         /**
          * Whether autowiring is enabled. This is used for automatic autowiring
          * options (the option must be marked as autowired) by looking up in 
the
@@ -131,6 +149,7 @@ public interface StateStoreComponentBuilderFactory {
                 Object value) {
             switch (name) {
             case "lazyStartProducer": ((StateStoreComponent) 
component).setLazyStartProducer((boolean) value); return true;
+            case "namespaced": ((StateStoreComponent) 
component).setNamespaced((boolean) value); return true;
             case "autowiredEnabled": ((StateStoreComponent) 
component).setAutowiredEnabled((boolean) value); return true;
             case "backendFactory": ((StateStoreComponent) 
component).setBackendFactory((org.apache.camel.component.statestore.StateStoreBackendFactory)
 value); return true;
             default: return false;
diff --git 
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/StateStoreEndpointBuilderFactory.java
 
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/StateStoreEndpointBuilderFactory.java
index 884023f13c62..07e647f8339f 100644
--- 
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/StateStoreEndpointBuilderFactory.java
+++ 
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/StateStoreEndpointBuilderFactory.java
@@ -44,6 +44,38 @@ public interface StateStoreEndpointBuilderFactory {
             return (AdvancedStateStoreEndpointBuilder) this;
         }
 
+        /**
+         * Whether to use a store-specific namespace prefix to isolate keys on 
a
+         * shared backend repository.
+         * 
+         * The option is a: <code>boolean</code> type.
+         * 
+         * Default: true
+         * Group: producer
+         * 
+         * @param namespaced the value to set
+         * @return the dsl builder
+         */
+        default StateStoreEndpointBuilder namespaced(boolean namespaced) {
+            doSetProperty("namespaced", namespaced);
+            return this;
+        }
+        /**
+         * Whether to use a store-specific namespace prefix to isolate keys on 
a
+         * shared backend repository.
+         * 
+         * The option will be converted to a <code>boolean</code> type.
+         * 
+         * Default: true
+         * Group: producer
+         * 
+         * @param namespaced the value to set
+         * @return the dsl builder
+         */
+        default StateStoreEndpointBuilder namespaced(String namespaced) {
+            doSetProperty("namespaced", namespaced);
+            return this;
+        }
         /**
          * The default operation to perform.
          * 

Reply via email to