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

davsclaus 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 dfcc00d01fff CAMEL-25553: vault properties functions - make the shared 
state thread safe (#27680)
dfcc00d01fff is described below

commit dfcc00d01fff688705b7086e62203be3889c39df
Author: Claus Ibsen <[email protected]>
AuthorDate: Sun Oct 11 09:01:25 2026 +0200

    CAMEL-25553: vault properties functions - make the shared state thread safe 
(#27680)
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
---
 .../docs/ibm-secrets-manager-component.adoc        |   1 +
 .../ParameterStorePropertiesFunction.java          |   8 +-
 .../SecretsManagerPropertiesFunction.java          |   8 +-
 .../key/vault/KeyVaultPropertiesFunction.java      |   8 +-
 .../vault/CyberArkVaultPropertiesFunction.java     |   8 +-
 .../GoogleSecretManagerPropertiesFunction.java     |   8 +-
 .../main/docs/ibm-secrets-manager-component.adoc   |   1 +
 .../IBMSecretsManagerPropertiesFunction.java       |  21 +++-
 .../IBMSecretsManagerPropertiesFunctionTest.java   | 137 +++++++++++++++++++++
 .../config/SpringCloudConfigReloadTriggerTask.java |   6 +-
 .../cloud/config/SpringConfigRemaindersCache.java  |  13 +-
 .../ROOT/pages/camel-4x-upgrade-guide-4_23.adoc    |   7 ++
 12 files changed, 202 insertions(+), 24 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/ibm-secrets-manager-component.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/ibm-secrets-manager-component.adoc
index fc7edff23397..43a9e69fa908 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/ibm-secrets-manager-component.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/ibm-secrets-manager-component.adoc
@@ -411,6 +411,7 @@ YAML::
 ====
 
 Where route will be the name of the secret stored in the IBM Secrets Manager 
Vault instance, in the 'default' secret group.
+The secret group can be omitted, such as `{{ibm:route}}`, and then the secret 
is looked up in the 'default' secret group.
 
 You could specify a default value in case the secret is not present on IBM 
Secrets Manager Vault instance:
 
diff --git 
a/components/camel-aws/camel-aws-parameter-store/src/main/java/org/apache/camel/component/aws/parameterstore/ParameterStorePropertiesFunction.java
 
b/components/camel-aws/camel-aws-parameter-store/src/main/java/org/apache/camel/component/aws/parameterstore/ParameterStorePropertiesFunction.java
index 799ef380c079..7fae03943c0f 100644
--- 
a/components/camel-aws/camel-aws-parameter-store/src/main/java/org/apache/camel/component/aws/parameterstore/ParameterStorePropertiesFunction.java
+++ 
b/components/camel-aws/camel-aws-parameter-store/src/main/java/org/apache/camel/component/aws/parameterstore/ParameterStorePropertiesFunction.java
@@ -17,8 +17,9 @@
 package org.apache.camel.component.aws.parameterstore;
 
 import java.net.URI;
-import java.util.HashSet;
+import java.util.Collections;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 
 import org.apache.camel.CamelContext;
 import org.apache.camel.CamelContextAware;
@@ -99,7 +100,8 @@ public class ParameterStorePropertiesFunction extends 
ServiceSupport implements
     private CamelContext camelContext;
     private SsmClient client;
 
-    private final Set<String> parameters = new HashSet<>();
+    // this function is shared and invoked concurrently
+    private final Set<String> parameters = ConcurrentHashMap.newKeySet();
 
     private String region;
     private boolean defaultCredentialsProvider;
@@ -278,7 +280,7 @@ public class ParameterStorePropertiesFunction extends 
ServiceSupport implements
      * Names of the parameters in use
      */
     public Set<String> getParameters() {
-        return parameters;
+        return Collections.unmodifiableSet(parameters);
     }
 
     /**
diff --git 
a/components/camel-aws/camel-aws-secrets-manager/src/main/java/org/apache/camel/component/aws/secretsmanager/SecretsManagerPropertiesFunction.java
 
b/components/camel-aws/camel-aws-secrets-manager/src/main/java/org/apache/camel/component/aws/secretsmanager/SecretsManagerPropertiesFunction.java
index 8813a510e721..25643800649a 100644
--- 
a/components/camel-aws/camel-aws-secrets-manager/src/main/java/org/apache/camel/component/aws/secretsmanager/SecretsManagerPropertiesFunction.java
+++ 
b/components/camel-aws/camel-aws-secrets-manager/src/main/java/org/apache/camel/component/aws/secretsmanager/SecretsManagerPropertiesFunction.java
@@ -18,8 +18,9 @@ package org.apache.camel.component.aws.secretsmanager;
 
 import java.net.URI;
 import java.util.Base64;
-import java.util.HashSet;
+import java.util.Collections;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.JsonNode;
@@ -103,7 +104,8 @@ public class SecretsManagerPropertiesFunction extends 
ServiceSupport implements
     private CamelContext camelContext;
     private SecretsManagerClient client;
 
-    private final Set<String> secrets = new HashSet<>();
+    // this function is shared and invoked concurrently
+    private final Set<String> secrets = ConcurrentHashMap.newKeySet();
 
     private String region;
     private boolean defaultCredentialsProvider;
@@ -326,7 +328,7 @@ public class SecretsManagerPropertiesFunction extends 
ServiceSupport implements
      * Ids of the secrets in use
      */
     public Set<String> getSecrets() {
-        return secrets;
+        return Collections.unmodifiableSet(secrets);
     }
 
     /**
diff --git 
a/components/camel-azure/camel-azure-key-vault/src/main/java/org/apache/camel/component/azure/key/vault/KeyVaultPropertiesFunction.java
 
b/components/camel-azure/camel-azure-key-vault/src/main/java/org/apache/camel/component/azure/key/vault/KeyVaultPropertiesFunction.java
index 97b04ef74835..8ae685120e18 100644
--- 
a/components/camel-azure/camel-azure-key-vault/src/main/java/org/apache/camel/component/azure/key/vault/KeyVaultPropertiesFunction.java
+++ 
b/components/camel-azure/camel-azure-key-vault/src/main/java/org/apache/camel/component/azure/key/vault/KeyVaultPropertiesFunction.java
@@ -16,8 +16,9 @@
  */
 package org.apache.camel.component.azure.key.vault;
 
-import java.util.HashSet;
+import java.util.Collections;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 
 import com.azure.core.credential.TokenCredential;
 import com.azure.core.exception.ResourceNotFoundException;
@@ -86,7 +87,8 @@ public class KeyVaultPropertiesFunction extends 
ServiceSupport implements Proper
     private static final String CAMEL_VAULT_AZURE_IDENTITY_ENABLED = 
"CAMEL_VAULT_AZURE_IDENTITY_ENABLED";
     private CamelContext camelContext;
     private SecretClient client;
-    private final Set<String> secrets = new HashSet<>();
+    // this function is shared and invoked concurrently
+    private final Set<String> secrets = ConcurrentHashMap.newKeySet();
 
     public KeyVaultPropertiesFunction() {
         super();
@@ -264,6 +266,6 @@ public class KeyVaultPropertiesFunction extends 
ServiceSupport implements Proper
      * Ids of the secrets in use
      */
     public Set<String> getSecrets() {
-        return secrets;
+        return Collections.unmodifiableSet(secrets);
     }
 }
diff --git 
a/components/camel-cyberark-vault/src/main/java/org/apache/camel/component/cyberark/vault/CyberArkVaultPropertiesFunction.java
 
b/components/camel-cyberark-vault/src/main/java/org/apache/camel/component/cyberark/vault/CyberArkVaultPropertiesFunction.java
index 365bb2d5ba69..7ba9f35bdfba 100644
--- 
a/components/camel-cyberark-vault/src/main/java/org/apache/camel/component/cyberark/vault/CyberArkVaultPropertiesFunction.java
+++ 
b/components/camel-cyberark-vault/src/main/java/org/apache/camel/component/cyberark/vault/CyberArkVaultPropertiesFunction.java
@@ -16,8 +16,9 @@
  */
 package org.apache.camel.component.cyberark.vault;
 
-import java.util.HashSet;
+import java.util.Collections;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.JsonNode;
@@ -82,7 +83,8 @@ public class CyberArkVaultPropertiesFunction extends 
ServiceSupport implements P
     private CamelContext camelContext;
     private ConjurClient client;
 
-    private final Set<String> secrets = new HashSet<>();
+    // this function is shared and invoked concurrently
+    private final Set<String> secrets = ConcurrentHashMap.newKeySet();
 
     private String url;
     private String account;
@@ -269,7 +271,7 @@ public class CyberArkVaultPropertiesFunction extends 
ServiceSupport implements P
      * Ids of the secrets in use
      */
     public Set<String> getSecrets() {
-        return secrets;
+        return Collections.unmodifiableSet(secrets);
     }
 
     /**
diff --git 
a/components/camel-google/camel-google-secret-manager/src/main/java/org/apache/camel/component/google/secret/manager/GoogleSecretManagerPropertiesFunction.java
 
b/components/camel-google/camel-google-secret-manager/src/main/java/org/apache/camel/component/google/secret/manager/GoogleSecretManagerPropertiesFunction.java
index 7acba28405ed..1911f3b67534 100644
--- 
a/components/camel-google/camel-google-secret-manager/src/main/java/org/apache/camel/component/google/secret/manager/GoogleSecretManagerPropertiesFunction.java
+++ 
b/components/camel-google/camel-google-secret-manager/src/main/java/org/apache/camel/component/google/secret/manager/GoogleSecretManagerPropertiesFunction.java
@@ -17,8 +17,9 @@
 package org.apache.camel.component.google.secret.manager;
 
 import java.io.InputStream;
-import java.util.HashSet;
+import java.util.Collections;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.JsonNode;
@@ -86,7 +87,8 @@ public class GoogleSecretManagerPropertiesFunction extends 
ServiceSupport implem
     private CamelContext camelContext;
     private SecretManagerServiceClient client;
     private String projectId;
-    private final Set<String> secrets = new HashSet<>();
+    // this function is shared and invoked concurrently
+    private final Set<String> secrets = ConcurrentHashMap.newKeySet();
 
     public GoogleSecretManagerPropertiesFunction() {
         super();
@@ -250,7 +252,7 @@ public class GoogleSecretManagerPropertiesFunction extends 
ServiceSupport implem
      * Ids of the secrets in use
      */
     public Set<String> getSecrets() {
-        return secrets;
+        return Collections.unmodifiableSet(secrets);
     }
 
     /**
diff --git 
a/components/camel-ibm/camel-ibm-secrets-manager/src/main/docs/ibm-secrets-manager-component.adoc
 
b/components/camel-ibm/camel-ibm-secrets-manager/src/main/docs/ibm-secrets-manager-component.adoc
index fc7edff23397..43a9e69fa908 100644
--- 
a/components/camel-ibm/camel-ibm-secrets-manager/src/main/docs/ibm-secrets-manager-component.adoc
+++ 
b/components/camel-ibm/camel-ibm-secrets-manager/src/main/docs/ibm-secrets-manager-component.adoc
@@ -411,6 +411,7 @@ YAML::
 ====
 
 Where route will be the name of the secret stored in the IBM Secrets Manager 
Vault instance, in the 'default' secret group.
+The secret group can be omitted, such as `{{ibm:route}}`, and then the secret 
is looked up in the 'default' secret group.
 
 You could specify a default value in case the secret is not present on IBM 
Secrets Manager Vault instance:
 
diff --git 
a/components/camel-ibm/camel-ibm-secrets-manager/src/main/java/org/apache/camel/component/ibm/secrets/manager/IBMSecretsManagerPropertiesFunction.java
 
b/components/camel-ibm/camel-ibm-secrets-manager/src/main/java/org/apache/camel/component/ibm/secrets/manager/IBMSecretsManagerPropertiesFunction.java
index bf57267763fa..eadab7bb85f1 100644
--- 
a/components/camel-ibm/camel-ibm-secrets-manager/src/main/java/org/apache/camel/component/ibm/secrets/manager/IBMSecretsManagerPropertiesFunction.java
+++ 
b/components/camel-ibm/camel-ibm-secrets-manager/src/main/java/org/apache/camel/component/ibm/secrets/manager/IBMSecretsManagerPropertiesFunction.java
@@ -16,9 +16,10 @@
  */
 package org.apache.camel.component.ibm.secrets.manager;
 
-import java.util.HashSet;
+import java.util.Collections;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 
 import com.ibm.cloud.sdk.core.http.Response;
 import com.ibm.cloud.sdk.core.security.IamAuthenticator;
@@ -58,6 +59,7 @@ import 
org.apache.camel.vault.IBMSecretsManagerVaultConfiguration;
  * This implementation is to return the secret value associated with a key. 
The properties related to this kind of
  * Properties Function are all prefixed with <tt>ibm:</tt>. For example asking 
for <tt>ibm:default:token</tt>, will
  * return the secret value associated to the secret named token on IBM Secrets 
Manager, in the Secret group "default".
+ * When the secret group is omitted, such as <tt>ibm:token</tt>, then the 
secret group "default" is used.
  *
  * Another way of retrieving a secret value is using the following notation 
<tt>ibm:default:database/username</tt>: in
  * this case the field username of the secret database, in the secret group 
"default", will be returned. As a fallback,
@@ -72,11 +74,16 @@ public class IBMSecretsManagerPropertiesFunction extends 
ServiceSupport implemen
     private static final String CAMEL_VAULT_IBM_TOKEN_ENV = 
"CAMEL_VAULT_IBM_TOKEN";
     private static final String CAMEL_VAULT_IBM_SERVICE_URL_ENV = 
"CAMEL_VAULT_IBM_SERVICE_URL";
 
+    /**
+     * The secret group used when the lookup does not specify one (such as 
<tt>ibm:token</tt>).
+     */
+    static final String DEFAULT_SECRET_GROUP = "default";
+
     private CamelContext camelContext;
     private SecretsManager client;
-    private String secretGroup;
 
-    private final Set<String> secrets = new HashSet<>();
+    // this function is shared and invoked concurrently
+    private final Set<String> secrets = ConcurrentHashMap.newKeySet();
 
     public IBMSecretsManagerPropertiesFunction() {
         super();
@@ -140,6 +147,8 @@ public class IBMSecretsManagerPropertiesFunction extends 
ServiceSupport implemen
         String returnValue = null;
         String defaultValue = null;
         String version = null;
+        // the secret group is per lookup (this function is shared by all 
threads)
+        String secretGroup = DEFAULT_SECRET_GROUP;
         if (remainder.contains("#")) {
             String keyRemainder = StringHelper.before(remainder, "#");
             secretGroup = StringHelper.before(keyRemainder, ":");
@@ -187,7 +196,7 @@ public class IBMSecretsManagerPropertiesFunction extends 
ServiceSupport implemen
 
         if (key != null) {
             try {
-                returnValue = getSecretFromSource(key, subkey, defaultValue, 
version);
+                returnValue = getSecretFromSource(secretGroup, key, subkey, 
defaultValue, version);
             } catch (Exception e) {
                 throw new RuntimeCamelException(
                         "Error getting secret from vault using key: " + key + 
" due to: " + e.getMessage(), e);
@@ -198,7 +207,7 @@ public class IBMSecretsManagerPropertiesFunction extends 
ServiceSupport implemen
     }
 
     private String getSecretFromSource(
-            String key, String subkey, String defaultValue, String version) {
+            String secretGroup, String key, String subkey, String 
defaultValue, String version) {
         // capture name of secret
         secrets.add(key);
         String returnValue = "";
@@ -273,6 +282,6 @@ public class IBMSecretsManagerPropertiesFunction extends 
ServiceSupport implemen
      * Ids of the secrets in use
      */
     public Set<String> getSecrets() {
-        return secrets;
+        return Collections.unmodifiableSet(secrets);
     }
 }
diff --git 
a/components/camel-ibm/camel-ibm-secrets-manager/src/test/java/org/apache/camel/component/ibm/secrets/manager/IBMSecretsManagerPropertiesFunctionTest.java
 
b/components/camel-ibm/camel-ibm-secrets-manager/src/test/java/org/apache/camel/component/ibm/secrets/manager/IBMSecretsManagerPropertiesFunctionTest.java
new file mode 100644
index 000000000000..f17a7d213a93
--- /dev/null
+++ 
b/components/camel-ibm/camel-ibm-secrets-manager/src/test/java/org/apache/camel/component/ibm/secrets/manager/IBMSecretsManagerPropertiesFunctionTest.java
@@ -0,0 +1,137 @@
+/*
+ * 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.ibm.secrets.manager;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import com.ibm.cloud.sdk.core.http.Response;
+import com.ibm.cloud.sdk.core.http.ServiceCall;
+import com.ibm.cloud.sdk.core.http.ServiceCallback;
+import com.ibm.cloud.sdk.core.security.NoAuthAuthenticator;
+import com.ibm.cloud.secrets_manager_sdk.secrets_manager.v2.SecretsManager;
+import 
com.ibm.cloud.secrets_manager_sdk.secrets_manager.v2.model.GetSecretByNameTypeOptions;
+import com.ibm.cloud.secrets_manager_sdk.secrets_manager.v2.model.Secret;
+import io.reactivex.Single;
+import okhttp3.Protocol;
+import okhttp3.Request;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class IBMSecretsManagerPropertiesFunctionTest {
+
+    @Test
+    public void testSecretGroupIsPerLookup() {
+        RecordingSecretsManager client = new RecordingSecretsManager();
+        IBMSecretsManagerPropertiesFunction function = new 
IBMSecretsManagerPropertiesFunction(client);
+
+        assertEquals("groupA/route", function.apply("groupA:route"));
+        assertEquals("groupB-user", 
function.apply("groupB:database#username"));
+        // no group must not reuse the group of the previous lookup
+        assertEquals("default/route", function.apply("route"));
+        assertEquals("groupA/route", function.apply("groupA:route:fallback"));
+
+        assertEquals(List.of("groupA", "groupB", "default", "groupA"), 
client.groups);
+        assertEquals(Set.of("route", "database"), function.getSecrets());
+    }
+
+    @Test
+    public void testSecretsIsReadOnlyView() {
+        IBMSecretsManagerPropertiesFunction function = new 
IBMSecretsManagerPropertiesFunction(new RecordingSecretsManager());
+        function.apply("groupA:route");
+
+        Set<String> secrets = function.getSecrets();
+        assertThrows(UnsupportedOperationException.class, () -> 
secrets.add("other"));
+
+        // the view reflects later lookups
+        function.apply("groupA:other");
+        assertTrue(secrets.contains("other"));
+    }
+
+    /**
+     * Returns a secret whose payload and data are derived from the requested 
secret group and name.
+     */
+    private static final class RecordingSecretsManager extends SecretsManager {
+
+        private final List<String> groups = new CopyOnWriteArrayList<>();
+
+        RecordingSecretsManager() {
+            super("test", new NoAuthAuthenticator());
+        }
+
+        @Override
+        public ServiceCall<Secret> 
getSecretByNameType(GetSecretByNameTypeOptions options) {
+            String group = options.secretGroupName();
+            groups.add(group);
+            Secret secret = new TestSecret(group + "/" + options.name(), 
Map.of("username", group + "-user"));
+            okhttp3.Response http = new okhttp3.Response.Builder()
+                    .request(new 
Request.Builder().url("http://localhost/";).build())
+                    .protocol(Protocol.HTTP_1_1)
+                    .code(200)
+                    .message("OK")
+                    .build();
+            return new FixedServiceCall<>(new Response<>(secret, http));
+        }
+    }
+
+    private static final class TestSecret extends Secret {
+
+        TestSecret(String payload, Map<String, Object> data) {
+            this.payload = payload;
+            this.data = data;
+        }
+    }
+
+    private static final class FixedServiceCall<T> implements ServiceCall<T> {
+
+        private final Response<T> response;
+
+        FixedServiceCall(Response<T> response) {
+            this.response = response;
+        }
+
+        @Override
+        public ServiceCall<T> addHeader(String name, String value) {
+            return this;
+        }
+
+        @Override
+        public Response<T> execute() {
+            return response;
+        }
+
+        @Override
+        public void enqueue(ServiceCallback<T> callback) {
+            callback.onResponse(response);
+        }
+
+        @Override
+        public Single<Response<T>> reactiveRequest() {
+            return Single.just(response);
+        }
+
+        @Override
+        public void cancel() {
+            // noop
+        }
+    }
+}
diff --git 
a/components/camel-spring-parent/camel-spring-cloud-config/src/main/java/org/apache/camel/component/spring/cloud/config/SpringCloudConfigReloadTriggerTask.java
 
b/components/camel-spring-parent/camel-spring-cloud-config/src/main/java/org/apache/camel/component/spring/cloud/config/SpringCloudConfigReloadTriggerTask.java
index 29d6da71ed81..e35fd9872f73 100644
--- 
a/components/camel-spring-parent/camel-spring-cloud-config/src/main/java/org/apache/camel/component/spring/cloud/config/SpringCloudConfigReloadTriggerTask.java
+++ 
b/components/camel-spring-parent/camel-spring-cloud-config/src/main/java/org/apache/camel/component/spring/cloud/config/SpringCloudConfigReloadTriggerTask.java
@@ -46,13 +46,15 @@ public class SpringCloudConfigReloadTriggerTask extends 
ServiceSupport implement
 
     @Override
     public void run() {
-        if (SpringConfigRemaindersCache.get().isEmpty()) {
+        // iterate a copy as the properties function can add entries 
concurrently
+        Map<String, String> remainders = 
SpringConfigRemaindersCache.snapshot();
+        if (remainders.isEmpty()) {
             return;
         }
 
         ConfigData configData = new 
SpringConfigProvider().getConfigData(getCamelContext());
 
-        for (Map.Entry<String, String> entry : 
SpringConfigRemaindersCache.get().entrySet()) {
+        for (Map.Entry<String, String> entry : remainders.entrySet()) {
             for (PropertySource propertySource : 
configData.getPropertySources()) {
                 if (propertySource.containsProperty(entry.getKey())
                         && 
!propertySource.getProperty(entry.getKey()).equals(entry.getValue())) {
diff --git 
a/components/camel-spring-parent/camel-spring-cloud-config/src/main/java/org/apache/camel/component/spring/cloud/config/SpringConfigRemaindersCache.java
 
b/components/camel-spring-parent/camel-spring-cloud-config/src/main/java/org/apache/camel/component/spring/cloud/config/SpringConfigRemaindersCache.java
index e365d1e6d3cb..ac04cf55644c 100644
--- 
a/components/camel-spring-parent/camel-spring-cloud-config/src/main/java/org/apache/camel/component/spring/cloud/config/SpringConfigRemaindersCache.java
+++ 
b/components/camel-spring-parent/camel-spring-cloud-config/src/main/java/org/apache/camel/component/spring/cloud/config/SpringConfigRemaindersCache.java
@@ -16,6 +16,7 @@
  */
 package org.apache.camel.component.spring.cloud.config;
 
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -24,7 +25,8 @@ public class SpringConfigRemaindersCache {
     private SpringConfigRemaindersCache() {
     }
 
-    private static final Map<String, String> REMAINDERS_CACHE = new 
HashMap<>();
+    // written by the properties function (any thread) and read by the reload 
task; values can be null
+    private static final Map<String, String> REMAINDERS_CACHE = 
Collections.synchronizedMap(new HashMap<>());
 
     public static Map<String, String> get() {
         return REMAINDERS_CACHE;
@@ -33,4 +35,13 @@ public class SpringConfigRemaindersCache {
     public static void put(String remainder, String value) {
         REMAINDERS_CACHE.put(remainder, value);
     }
+
+    /**
+     * A copy of the cache that is safe to iterate while properties are being 
resolved.
+     */
+    public static Map<String, String> snapshot() {
+        synchronized (REMAINDERS_CACHE) {
+            return new HashMap<>(REMAINDERS_CACHE);
+        }
+    }
 }
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
index e79e7ed43dfe..c2568bcd695c 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
@@ -2687,6 +2687,13 @@ breaking change for routes that reference the header by 
its literal string name:
 `CamelIBMCOSCacheControl`, although the change is trivial to adapt. Routes 
using the
 `IBMCOSConstants.CACHE_CONTROL` constant are unaffected.
 
+=== camel-ibm-secrets-manager - a lookup without a secret group uses the 
default group
+
+The `ibm:` properties function now uses the `default` secret group when the 
lookup does not specify one, such as
+`{{ibm:route}}`. Previously such a lookup failed when it was the first one, 
and otherwise reused the secret group of
+whichever lookup ran before it (possibly from another thread). Specify the 
group, such as `{{ibm:mygroup:route}}`,
+to read a secret from another group.
+
 === camel-knative
 
 The Knative HTTP consumer no longer returns the stack trace of a failed 
exchange to the caller.

Reply via email to