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

Croway pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-spring-boot.git


The following commit(s) were added to refs/heads/main by this push:
     new 1bebacb4941 CAMEL-24531: extract a shared early-resolution properties 
parser (#1917)
1bebacb4941 is described below

commit 1bebacb49412c422785771fe577fbb8dfe037312
Author: Adriano Machado <[email protected]>
AuthorDate: Tue Sep 1 03:10:27 2026 -0400

    CAMEL-24531: extract a shared early-resolution properties parser (#1917)
    
    * CAMEL-24531: add guarded lifecycle skeleton for early-resolution parsers
    
    * CAMEL-24531: resolve whole-value placeholders in the shared parser
    
    * CAMEL-24531: aggregate early-resolution failures instead of aborting on 
the first
    
    * CAMEL-24531: address review feedback on the shared early-resolution parser
    
    * CAMEL-24531: migrate aws-secrets-manager starter to the shared parser
    
    * CAMEL-24531: migrate azure-key-vault starter to the shared parser
    
    * CAMEL-24531: migrate cyberark-vault starter to the shared parser
    
    * CAMEL-24531: migrate ibm-secrets-manager starter to the shared parser
    
    * CAMEL-24531: migrate spring-cloud-config starter to the shared parser
    
    * CAMEL-24531: migrate google-secret-manager starter and normalize its 
IOException handling
    
    * CAMEL-24531: migrate hashicorp-vault starter and normalize its 
configuration exceptions
    
    * CAMEL-24531: strengthen contract test coverage for the hashicorp vault 
parser
---
 ...pringBootAwsSecretsManagerPropertiesParser.java | 126 +++------
 ...gBootAwsSecretsManagerPropertiesParserTest.java |  41 +++
 .../SpringBootAzureKeyVaultPropertiesParser.java   | 135 +++------
 ...pringBootAzureKeyVaultPropertiesParserTest.java |  40 +++
 .../SpringBootCyberArkVaultPropertiesParser.java   | 119 +++-----
 ...pringBootCyberArkVaultPropertiesParserTest.java |  40 +++
 ...ingBootGoogleSecretManagerPropertiesParser.java |  98 ++-----
 ...ootGoogleSecretManagerPropertiesParserTest.java |  64 +++++
 .../SpringBootHashicorpVaultPropertiesParser.java  | 112 +++-----
 ...ringBootHashicorpVaultPropertiesParserTest.java | 120 ++++++++
 .../IBMSecretsManagerVaultPropertiesParser.java    |  98 ++-----
 ...IBMSecretsManagerVaultPropertiesParserTest.java |  41 +++
 .../SpringBootCloudConfigPropertiesParser.java     |  81 ++----
 .../SpringBootCloudConfigPropertiesParserTest.java |  46 +++
 .../AbstractEarlyResolutionPropertiesParser.java   | 163 +++++++++++
 ...bstractEarlyResolutionPropertiesParserTest.java | 312 +++++++++++++++++++++
 16 files changed, 1102 insertions(+), 534 deletions(-)

diff --git 
a/components-starter/camel-aws-secrets-manager-starter/src/main/java/org/apache/camel/component/aws/secretsmanager/springboot/SpringBootAwsSecretsManagerPropertiesParser.java
 
b/components-starter/camel-aws-secrets-manager-starter/src/main/java/org/apache/camel/component/aws/secretsmanager/springboot/SpringBootAwsSecretsManagerPropertiesParser.java
index 25b02114f79..d41cbb27714 100644
--- 
a/components-starter/camel-aws-secrets-manager-starter/src/main/java/org/apache/camel/component/aws/secretsmanager/springboot/SpringBootAwsSecretsManagerPropertiesParser.java
+++ 
b/components-starter/camel-aws-secrets-manager-starter/src/main/java/org/apache/camel/component/aws/secretsmanager/springboot/SpringBootAwsSecretsManagerPropertiesParser.java
@@ -18,16 +18,10 @@ package 
org.apache.camel.component.aws.secretsmanager.springboot;
 
 import org.apache.camel.RuntimeCamelException;
 import 
org.apache.camel.component.aws.secretsmanager.SecretsManagerPropertiesFunction;
+import org.apache.camel.spi.PropertiesFunction;
+import org.apache.camel.spring.boot.AbstractEarlyResolutionPropertiesParser;
 import org.apache.camel.util.ObjectHelper;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import 
org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
-import org.springframework.boot.origin.OriginTrackedValue;
-import org.springframework.context.ApplicationListener;
 import org.springframework.core.env.ConfigurableEnvironment;
-import org.springframework.core.env.MapPropertySource;
-import org.springframework.core.env.PropertiesPropertySource;
-import org.springframework.core.env.PropertySource;
 import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
 import software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider;
 import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
@@ -35,87 +29,49 @@ import software.amazon.awssdk.regions.Region;
 import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;
 import 
software.amazon.awssdk.services.secretsmanager.SecretsManagerClientBuilder;
 
-import java.util.Properties;
-
-public class SpringBootAwsSecretsManagerPropertiesParser implements 
ApplicationListener<ApplicationEnvironmentPreparedEvent> {
-    private static final Logger LOG = 
LoggerFactory.getLogger(SpringBootAwsSecretsManagerPropertiesParser.class);
+public class SpringBootAwsSecretsManagerPropertiesParser extends 
AbstractEarlyResolutionPropertiesParser {
 
     @Override
-    public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
-        SecretsManagerClient client;
-        ConfigurableEnvironment environment = event.getEnvironment();
-        // an unresolved placeholder would otherwise stay in the property 
value and become the effective
-        // secret, so resolution failures abort startup unless the operator 
opts back into the old behaviour
-        final boolean ignoreResolutionFailures
-                = 
Boolean.parseBoolean(environment.getProperty("camel.vault.ignore-resolution-failures"));
-        if 
(Boolean.parseBoolean(environment.getProperty("camel.component.aws-secrets-manager.early-resolve-properties")))
 {
-            String accessKey = 
environment.getProperty("camel.vault.aws.accessKey");
-            String secretKey = 
environment.getProperty("camel.vault.aws.secretKey");
-            String region = environment.getProperty("camel.vault.aws.region");
-            boolean useDefaultCredentialsProvider = 
Boolean.parseBoolean(environment.getProperty("camel.vault.aws.defaultCredentialsProvider"));
-            boolean useProfileCredentialsProvider = 
Boolean.parseBoolean(environment.getProperty("camel.vault.aws.profileCredentialsProvider"));
-            String profileName = 
environment.getProperty("camel.vault.aws.profileName");
-            if (ObjectHelper.isNotEmpty(accessKey) && 
ObjectHelper.isNotEmpty(secretKey) && ObjectHelper.isNotEmpty(region)) {
-                SecretsManagerClientBuilder clientBuilder = 
SecretsManagerClient.builder();
-                AwsBasicCredentials cred = 
AwsBasicCredentials.create(accessKey, secretKey);
-                clientBuilder = 
clientBuilder.credentialsProvider(StaticCredentialsProvider.create(cred));
-                clientBuilder.region(Region.of(region));
-                client = clientBuilder.build();
-            } else if (useDefaultCredentialsProvider && 
ObjectHelper.isNotEmpty(region)) {
-                SecretsManagerClientBuilder clientBuilder = 
SecretsManagerClient.builder();
-                clientBuilder.region(Region.of(region));
-                client = clientBuilder.build();
-            } else if (useProfileCredentialsProvider && 
ObjectHelper.isNotEmpty(profileName)) {
-                SecretsManagerClientBuilder clientBuilder = 
SecretsManagerClient.builder();
-                
clientBuilder.credentialsProvider(ProfileCredentialsProvider.create(profileName));
-                clientBuilder.region(Region.of(region));
-                client = clientBuilder.build();
-            } else {
-                throw new RuntimeCamelException(
-                        "Using the AWS Secrets Manager Properties Function 
requires setting AWS credentials as application properties or environment 
variables");
-            }
-            SecretsManagerPropertiesFunction secretsManagerPropertiesFunction 
= new SecretsManagerPropertiesFunction(client);
-
-            final Properties props = new Properties();
-            for (PropertySource mutablePropertySources : 
event.getEnvironment().getPropertySources()) {
-                if (mutablePropertySources instanceof MapPropertySource 
mapPropertySource) {
-                    mapPropertySource.getSource().forEach((key, value) -> {
-                        String stringValue = null;
-                        if ((value instanceof OriginTrackedValue 
originTrackedValue &&
-                                originTrackedValue.getValue() instanceof 
String v)) {
-                            stringValue = v;
-                        } else if (value instanceof String v) {
-                            stringValue = v;
-                        }
+    protected String getEarlyResolutionProperty() {
+        return "camel.component.aws-secrets-manager.early-resolve-properties";
+    }
 
-                        if (stringValue != null &&
-                                stringValue.startsWith("{{aws:") &&
-                                stringValue.endsWith("}}")) {
-                            LOG.debug("decrypting and overriding property {}", 
key);
-                            try {
-                                String element = 
secretsManagerPropertiesFunction.apply(stringValue
-                                        .replace("{{aws:", "")
-                                        .replace("}}", ""));
-                                props.put(key, element);
-                            } catch (Exception e) {
-                                if (ignoreResolutionFailures) {
-                                    LOG.warn("Failed to resolve property {} 
from the vault; the placeholder is left "
-                                             + "unresolved because 
camel.vault.ignore-resolution-failures is enabled", key, e);
-                                } else {
-                                    throw new RuntimeCamelException(
-                                            "Failed to resolve property " + 
key + " from the vault. Startup is aborted so "
-                                                            + "that the 
unresolved placeholder cannot become the effective "
-                                                            + "value; set 
camel.vault.ignore-resolution-failures=true to "
-                                                            + "continue 
anyway.",
-                                            e);
-                                }
-                            }
-                        }
-                    });
-                }
-            }
+    @Override
+    protected String getOverridePropertySourceName() {
+        return "overridden-camel-aws-secrets-manager-properties";
+    }
 
-            environment.getPropertySources().addFirst(new 
PropertiesPropertySource("overridden-camel-aws-secrets-manager-properties", 
props));
+    @Override
+    protected PropertiesFunction 
createPropertiesFunction(ConfigurableEnvironment environment) {
+        SecretsManagerClient client;
+        String accessKey = 
environment.getProperty("camel.vault.aws.accessKey");
+        String secretKey = 
environment.getProperty("camel.vault.aws.secretKey");
+        String region = environment.getProperty("camel.vault.aws.region");
+        boolean useDefaultCredentialsProvider
+                = 
Boolean.parseBoolean(environment.getProperty("camel.vault.aws.defaultCredentialsProvider"));
+        boolean useProfileCredentialsProvider
+                = 
Boolean.parseBoolean(environment.getProperty("camel.vault.aws.profileCredentialsProvider"));
+        String profileName = 
environment.getProperty("camel.vault.aws.profileName");
+        if (ObjectHelper.isNotEmpty(accessKey) && 
ObjectHelper.isNotEmpty(secretKey)
+                && ObjectHelper.isNotEmpty(region)) {
+            SecretsManagerClientBuilder clientBuilder = 
SecretsManagerClient.builder();
+            AwsBasicCredentials cred = AwsBasicCredentials.create(accessKey, 
secretKey);
+            clientBuilder = 
clientBuilder.credentialsProvider(StaticCredentialsProvider.create(cred));
+            clientBuilder.region(Region.of(region));
+            client = clientBuilder.build();
+        } else if (useDefaultCredentialsProvider && 
ObjectHelper.isNotEmpty(region)) {
+            SecretsManagerClientBuilder clientBuilder = 
SecretsManagerClient.builder();
+            clientBuilder.region(Region.of(region));
+            client = clientBuilder.build();
+        } else if (useProfileCredentialsProvider && 
ObjectHelper.isNotEmpty(profileName)) {
+            SecretsManagerClientBuilder clientBuilder = 
SecretsManagerClient.builder();
+            
clientBuilder.credentialsProvider(ProfileCredentialsProvider.create(profileName));
+            clientBuilder.region(Region.of(region));
+            client = clientBuilder.build();
+        } else {
+            throw new RuntimeCamelException(
+                    "Using the AWS Secrets Manager Properties Function 
requires setting AWS credentials as application properties or environment 
variables");
         }
+        return new SecretsManagerPropertiesFunction(client);
     }
 }
diff --git 
a/components-starter/camel-aws-secrets-manager-starter/src/test/java/org/apache/camel/component/aws/secretsmanager/springboot/SpringBootAwsSecretsManagerPropertiesParserTest.java
 
b/components-starter/camel-aws-secrets-manager-starter/src/test/java/org/apache/camel/component/aws/secretsmanager/springboot/SpringBootAwsSecretsManagerPropertiesParserTest.java
new file mode 100644
index 00000000000..25571b966f1
--- /dev/null
+++ 
b/components-starter/camel-aws-secrets-manager-starter/src/test/java/org/apache/camel/component/aws/secretsmanager/springboot/SpringBootAwsSecretsManagerPropertiesParserTest.java
@@ -0,0 +1,41 @@
+/*
+ * 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.aws.secretsmanager.springboot;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class SpringBootAwsSecretsManagerPropertiesParserTest {
+
+    private final SpringBootAwsSecretsManagerPropertiesParser parser
+            = new SpringBootAwsSecretsManagerPropertiesParser();
+
+    @Test
+    public void guardPropertyIsUnchanged() {
+        
assertEquals("camel.component.aws-secrets-manager.early-resolve-properties",
+                parser.getEarlyResolutionProperty(),
+                "a wrong guard key silently disables early resolution, leaving 
placeholders as literal values");
+    }
+
+    @Test
+    public void overridePropertySourceNameIsUnchanged() {
+        assertEquals("overridden-camel-aws-secrets-manager-properties",
+                parser.getOverridePropertySourceName(),
+                "the property source name is observable through 
/actuator/env");
+    }
+}
diff --git 
a/components-starter/camel-azure-key-vault-starter/src/main/java/org/apache/camel/component/azure/key/vault/springboot/SpringBootAzureKeyVaultPropertiesParser.java
 
b/components-starter/camel-azure-key-vault-starter/src/main/java/org/apache/camel/component/azure/key/vault/springboot/SpringBootAzureKeyVaultPropertiesParser.java
index f240c1cd013..6acf34c7691 100644
--- 
a/components-starter/camel-azure-key-vault-starter/src/main/java/org/apache/camel/component/azure/key/vault/springboot/SpringBootAzureKeyVaultPropertiesParser.java
+++ 
b/components-starter/camel-azure-key-vault-starter/src/main/java/org/apache/camel/component/azure/key/vault/springboot/SpringBootAzureKeyVaultPropertiesParser.java
@@ -24,106 +24,63 @@ import com.azure.security.keyvault.secrets.SecretClient;
 import com.azure.security.keyvault.secrets.SecretClientBuilder;
 import org.apache.camel.RuntimeCamelException;
 import org.apache.camel.component.azure.key.vault.KeyVaultPropertiesFunction;
+import org.apache.camel.spi.PropertiesFunction;
+import org.apache.camel.spring.boot.AbstractEarlyResolutionPropertiesParser;
 import org.apache.camel.util.ObjectHelper;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import 
org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
-import org.springframework.boot.origin.OriginTrackedValue;
-import org.springframework.context.ApplicationListener;
 import org.springframework.core.env.ConfigurableEnvironment;
-import org.springframework.core.env.MapPropertySource;
-import org.springframework.core.env.PropertiesPropertySource;
-import org.springframework.core.env.PropertySource;
 
-import java.util.Properties;
+public class SpringBootAzureKeyVaultPropertiesParser extends 
AbstractEarlyResolutionPropertiesParser {
 
-public class SpringBootAzureKeyVaultPropertiesParser implements 
ApplicationListener<ApplicationEnvironmentPreparedEvent> {
-    private static final Logger LOG = 
LoggerFactory.getLogger(SpringBootAzureKeyVaultPropertiesParser.class);
+    @Override
+    protected String getEarlyResolutionProperty() {
+        return "camel.component.azure-key-vault.early-resolve-properties";
+    }
+
+    @Override
+    protected String getOverridePropertySourceName() {
+        return "overridden-camel-azure-key-vault-properties";
+    }
 
     @Override
-    public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
+    protected PropertiesFunction 
createPropertiesFunction(ConfigurableEnvironment environment) {
         SecretClient client;
-        ConfigurableEnvironment environment = event.getEnvironment();
-        // an unresolved placeholder would otherwise stay in the property 
value and become the effective
-        // secret, so resolution failures abort startup unless the operator 
opts back into the old behaviour
-        final boolean ignoreResolutionFailures
-                = 
Boolean.parseBoolean(environment.getProperty("camel.vault.ignore-resolution-failures"));
-        if 
(Boolean.parseBoolean(environment.getProperty("camel.component.azure-key-vault.early-resolve-properties")))
 {
-            String vaultName = 
environment.getProperty("camel.vault.azure.vaultName");
-            String clientId = 
environment.getProperty("camel.vault.azure.clientId");
-            String clientSecret = 
environment.getProperty("camel.vault.azure.clientSecret");
-            String tenantId = 
environment.getProperty("camel.vault.azure.tenantId");
-            boolean azureIdentityEnabled = 
Boolean.parseBoolean(System.getenv("CAMEL_VAULT_AZURE_IDENTITY_ENABLED"));
-            if (ObjectHelper.isNotEmpty(vaultName) && 
ObjectHelper.isNotEmpty(clientId) && ObjectHelper.isNotEmpty(clientSecret)
-                    && ObjectHelper.isNotEmpty(tenantId) && 
!azureIdentityEnabled) {
-                String keyVaultUri = "https://"; + vaultName + 
".vault.azure.net";
+        String vaultName = 
environment.getProperty("camel.vault.azure.vaultName");
+        String clientId = 
environment.getProperty("camel.vault.azure.clientId");
+        String clientSecret = 
environment.getProperty("camel.vault.azure.clientSecret");
+        String tenantId = 
environment.getProperty("camel.vault.azure.tenantId");
+        boolean azureIdentityEnabled = 
Boolean.parseBoolean(System.getenv("CAMEL_VAULT_AZURE_IDENTITY_ENABLED"));
+        if (ObjectHelper.isNotEmpty(vaultName) && 
ObjectHelper.isNotEmpty(clientId)
+                && ObjectHelper.isNotEmpty(clientSecret)
+                && ObjectHelper.isNotEmpty(tenantId) && !azureIdentityEnabled) 
{
+            String keyVaultUri = "https://"; + vaultName + ".vault.azure.net";
 
-                // Credential
-                ClientSecretCredential credential = new 
ClientSecretCredentialBuilder()
-                        .tenantId(tenantId)
-                        .clientId(clientId)
-                        .clientSecret(clientSecret)
-                        .build();
+            // Credential
+            ClientSecretCredential credential = new 
ClientSecretCredentialBuilder()
+                    .tenantId(tenantId)
+                    .clientId(clientId)
+                    .clientSecret(clientSecret)
+                    .build();
 
-                // Build Client
-                client = new SecretClientBuilder()
-                        .vaultUrl(keyVaultUri)
-                        .credential(credential)
-                        .buildClient();
-            } else if (ObjectHelper.isNotEmpty(vaultName) && 
azureIdentityEnabled) {
-                String keyVaultUri = "https://"; + vaultName + 
".vault.azure.net";
+            // Build Client
+            client = new SecretClientBuilder()
+                    .vaultUrl(keyVaultUri)
+                    .credential(credential)
+                    .buildClient();
+        } else if (ObjectHelper.isNotEmpty(vaultName) && azureIdentityEnabled) 
{
+            String keyVaultUri = "https://"; + vaultName + ".vault.azure.net";
 
-                // Credential
-                TokenCredential credential = new 
DefaultAzureCredentialBuilder().build();
+            // Credential
+            TokenCredential credential = new 
DefaultAzureCredentialBuilder().build();
 
-                // Build Client
-                client = new SecretClientBuilder()
-                        .vaultUrl(keyVaultUri)
-                        .credential(credential)
-                        .buildClient();
-            } else {
-                throw new RuntimeCamelException(
-                        "Using the Azure Key Vault Properties Function 
requires setting Azure credentials as application properties or environment 
variables or enable the Azure Identity Authentication mechanism");
-            }
-            KeyVaultPropertiesFunction keyVaultPropertiesFunction = new 
KeyVaultPropertiesFunction(client);
-            final Properties props = new Properties();
-            for (PropertySource mutablePropertySources : 
event.getEnvironment().getPropertySources()) {
-                if (mutablePropertySources instanceof MapPropertySource 
mapPropertySource) {
-                    mapPropertySource.getSource().forEach((key, value) -> {
-                        String stringValue = null;
-                        if ((value instanceof OriginTrackedValue 
originTrackedValue &&
-                                originTrackedValue.getValue() instanceof 
String v)) {
-                            stringValue = v;
-                        } else if (value instanceof String v) {
-                            stringValue = v;
-                        }
-                        if (stringValue != null &&
-                                stringValue.startsWith("{{azure:") &&
-                                stringValue.endsWith("}}")) {
-                            LOG.debug("decrypting and overriding property {}", 
key);
-                            try {
-                                String element = 
keyVaultPropertiesFunction.apply(stringValue
-                                        .replace("{{azure:", "")
-                                        .replace("}}", ""));
-                                props.put(key, element);
-                            } catch (Exception e) {
-                                if (ignoreResolutionFailures) {
-                                    LOG.warn("Failed to resolve property {} 
from the vault; the placeholder is left "
-                                             + "unresolved because 
camel.vault.ignore-resolution-failures is enabled", key, e);
-                                } else {
-                                    throw new RuntimeCamelException(
-                                            "Failed to resolve property " + 
key + " from the vault. Startup is aborted so "
-                                                            + "that the 
unresolved placeholder cannot become the effective "
-                                                            + "value; set 
camel.vault.ignore-resolution-failures=true to "
-                                                            + "continue 
anyway.",
-                                            e);
-                                }
-                            }
-                        }
-                    });
-                }
-            }
-            environment.getPropertySources().addFirst(new 
PropertiesPropertySource("overridden-camel-azure-key-vault-properties", props));
+            // Build Client
+            client = new SecretClientBuilder()
+                    .vaultUrl(keyVaultUri)
+                    .credential(credential)
+                    .buildClient();
+        } else {
+            throw new RuntimeCamelException(
+                    "Using the Azure Key Vault Properties Function requires 
setting Azure credentials as application properties or environment variables or 
enable the Azure Identity Authentication mechanism");
         }
+        return new KeyVaultPropertiesFunction(client);
     }
 }
diff --git 
a/components-starter/camel-azure-key-vault-starter/src/test/java/org/apache/camel/component/azure/key/vault/springboot/SpringBootAzureKeyVaultPropertiesParserTest.java
 
b/components-starter/camel-azure-key-vault-starter/src/test/java/org/apache/camel/component/azure/key/vault/springboot/SpringBootAzureKeyVaultPropertiesParserTest.java
new file mode 100644
index 00000000000..44c4bd63995
--- /dev/null
+++ 
b/components-starter/camel-azure-key-vault-starter/src/test/java/org/apache/camel/component/azure/key/vault/springboot/SpringBootAzureKeyVaultPropertiesParserTest.java
@@ -0,0 +1,40 @@
+/*
+ * 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.azure.key.vault.springboot;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class SpringBootAzureKeyVaultPropertiesParserTest {
+
+    private final SpringBootAzureKeyVaultPropertiesParser parser = new 
SpringBootAzureKeyVaultPropertiesParser();
+
+    @Test
+    public void guardPropertyIsUnchanged() {
+        
assertEquals("camel.component.azure-key-vault.early-resolve-properties",
+                parser.getEarlyResolutionProperty(),
+                "a wrong guard key silently disables early resolution, leaving 
placeholders as literal values");
+    }
+
+    @Test
+    public void overridePropertySourceNameIsUnchanged() {
+        assertEquals("overridden-camel-azure-key-vault-properties",
+                parser.getOverridePropertySourceName(),
+                "the property source name is observable through 
/actuator/env");
+    }
+}
diff --git 
a/components-starter/camel-cyberark-vault-starter/src/main/java/org/apache/camel/component/cyberark/vault/springboot/SpringBootCyberArkVaultPropertiesParser.java
 
b/components-starter/camel-cyberark-vault-starter/src/main/java/org/apache/camel/component/cyberark/vault/springboot/SpringBootCyberArkVaultPropertiesParser.java
index 4242da15ea2..4ac3a08b178 100644
--- 
a/components-starter/camel-cyberark-vault-starter/src/main/java/org/apache/camel/component/cyberark/vault/springboot/SpringBootCyberArkVaultPropertiesParser.java
+++ 
b/components-starter/camel-cyberark-vault-starter/src/main/java/org/apache/camel/component/cyberark/vault/springboot/SpringBootCyberArkVaultPropertiesParser.java
@@ -20,100 +20,53 @@ import org.apache.camel.RuntimeCamelException;
 import 
org.apache.camel.component.cyberark.vault.CyberArkVaultPropertiesFunction;
 import org.apache.camel.component.cyberark.vault.client.ConjurClient;
 import org.apache.camel.component.cyberark.vault.client.ConjurClientFactory;
+import org.apache.camel.spi.PropertiesFunction;
+import org.apache.camel.spring.boot.AbstractEarlyResolutionPropertiesParser;
 import org.apache.camel.util.ObjectHelper;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import 
org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
-import org.springframework.boot.origin.OriginTrackedValue;
-import org.springframework.context.ApplicationListener;
 import org.springframework.core.env.ConfigurableEnvironment;
-import org.springframework.core.env.MapPropertySource;
-import org.springframework.core.env.PropertiesPropertySource;
-import org.springframework.core.env.PropertySource;
 
-import java.util.Properties;
+public class SpringBootCyberArkVaultPropertiesParser extends 
AbstractEarlyResolutionPropertiesParser {
 
-public class SpringBootCyberArkVaultPropertiesParser implements 
ApplicationListener<ApplicationEnvironmentPreparedEvent> {
-    private static final Logger LOG = 
LoggerFactory.getLogger(SpringBootCyberArkVaultPropertiesParser.class);
+    @Override
+    protected String getEarlyResolutionProperty() {
+        return "camel.component.cyberark-vault.early-resolve-properties";
+    }
+
+    @Override
+    protected String getOverridePropertySourceName() {
+        return "overridden-camel-cyberark-vault-properties";
+    }
 
     @Override
-    public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
+    protected PropertiesFunction 
createPropertiesFunction(ConfigurableEnvironment environment) {
         ConjurClient client;
-        ConfigurableEnvironment environment = event.getEnvironment();
-        // an unresolved placeholder would otherwise stay in the property 
value and become the effective
-        // secret, so resolution failures abort startup unless the operator 
opts back into the old behaviour
-        final boolean ignoreResolutionFailures
-                = 
Boolean.parseBoolean(environment.getProperty("camel.vault.ignore-resolution-failures"));
-        if 
(Boolean.parseBoolean(environment.getProperty("camel.component.cyberark-vault.early-resolve-properties")))
 {
-            String url = environment.getProperty("camel.vault.cyberark.url");
-            String account = 
environment.getProperty("camel.vault.cyberark.account");
-            String username = 
environment.getProperty("camel.vault.cyberark.username");
-            String password = 
environment.getProperty("camel.vault.cyberark.password");
-            String apiKey = 
environment.getProperty("camel.vault.cyberark.apiKey");
-            String authToken = 
environment.getProperty("camel.vault.cyberark.authToken");
+        String url = environment.getProperty("camel.vault.cyberark.url");
+        String account = 
environment.getProperty("camel.vault.cyberark.account");
+        String username = 
environment.getProperty("camel.vault.cyberark.username");
+        String password = 
environment.getProperty("camel.vault.cyberark.password");
+        String apiKey = environment.getProperty("camel.vault.cyberark.apiKey");
+        String authToken = 
environment.getProperty("camel.vault.cyberark.authToken");
 
-            if (ObjectHelper.isNotEmpty(url) && 
ObjectHelper.isNotEmpty(account)) {
-                // Create Conjur client based on authentication method
-                if (ObjectHelper.isNotEmpty(authToken)) {
-                    // Use pre-authenticated token
-                    client = ConjurClientFactory.createWithToken(url, account, 
authToken);
-                } else if (ObjectHelper.isNotEmpty(apiKey) && 
ObjectHelper.isNotEmpty(username)) {
-                    // Use API key authentication
-                    client = ConjurClientFactory.createWithApiKey(url, 
account, username, apiKey);
-                } else if (ObjectHelper.isNotEmpty(username) && 
ObjectHelper.isNotEmpty(password)) {
-                    // Use username/password authentication
-                    client = ConjurClientFactory.createWithCredentials(url, 
account, username, password);
-                } else {
-                    throw new RuntimeCamelException(
-                            "Using the CyberArk Conjur Vault Properties 
Function requires authentication credentials (authToken, apiKey, or 
username/password)");
-                }
+        if (ObjectHelper.isNotEmpty(url) && ObjectHelper.isNotEmpty(account)) {
+            // Create Conjur client based on authentication method
+            if (ObjectHelper.isNotEmpty(authToken)) {
+                // Use pre-authenticated token
+                client = ConjurClientFactory.createWithToken(url, account, 
authToken);
+            } else if (ObjectHelper.isNotEmpty(apiKey) && 
ObjectHelper.isNotEmpty(username)) {
+                // Use API key authentication
+                client = ConjurClientFactory.createWithApiKey(url, account, 
username, apiKey);
+            } else if (ObjectHelper.isNotEmpty(username) && 
ObjectHelper.isNotEmpty(password)) {
+                // Use username/password authentication
+                client = ConjurClientFactory.createWithCredentials(url, 
account, username, password);
             } else {
                 throw new RuntimeCamelException(
-                        "Using the CyberArk Conjur Vault Properties Function 
requires setting URL and account as application properties or environment 
variables");
+                        "Using the CyberArk Conjur Vault Properties Function 
requires authentication credentials (authToken, apiKey, or username/password)");
             }
-
-            CyberArkVaultPropertiesFunction cyberArkVaultPropertiesFunction = 
new CyberArkVaultPropertiesFunction(client);
-
-            final Properties props = new Properties();
-            for (PropertySource mutablePropertySources : 
event.getEnvironment().getPropertySources()) {
-                if (mutablePropertySources instanceof MapPropertySource 
mapPropertySource) {
-                    mapPropertySource.getSource().forEach((key, value) -> {
-                        String stringValue = null;
-                        if ((value instanceof OriginTrackedValue 
originTrackedValue &&
-                                originTrackedValue.getValue() instanceof 
String v)) {
-                            stringValue = v;
-                        } else if (value instanceof String v) {
-                            stringValue = v;
-                        }
-
-                        if (stringValue != null &&
-                                stringValue.startsWith("{{cyberark:") &&
-                                stringValue.endsWith("}}")) {
-                            LOG.debug("decrypting and overriding property {}", 
key);
-                            try {
-                                String element = 
cyberArkVaultPropertiesFunction.apply(stringValue
-                                        .replace("{{cyberark:", "")
-                                        .replace("}}", ""));
-                                props.put(key, element);
-                            } catch (Exception e) {
-                                if (ignoreResolutionFailures) {
-                                    LOG.warn("Failed to resolve property {} 
from the vault; the placeholder is left "
-                                             + "unresolved because 
camel.vault.ignore-resolution-failures is enabled", key, e);
-                                } else {
-                                    throw new RuntimeCamelException(
-                                            "Failed to resolve property " + 
key + " from the vault. Startup is aborted so "
-                                                            + "that the 
unresolved placeholder cannot become the effective "
-                                                            + "value; set 
camel.vault.ignore-resolution-failures=true to "
-                                                            + "continue 
anyway.",
-                                            e);
-                                }
-                            }
-                        }
-                    });
-                }
-            }
-
-            environment.getPropertySources().addFirst(new 
PropertiesPropertySource("overridden-camel-cyberark-vault-properties", props));
+        } else {
+            throw new RuntimeCamelException(
+                    "Using the CyberArk Conjur Vault Properties Function 
requires setting URL and account as application properties or environment 
variables");
         }
+
+        return new CyberArkVaultPropertiesFunction(client);
     }
 }
diff --git 
a/components-starter/camel-cyberark-vault-starter/src/test/java/org/apache/camel/component/cyberark/vault/springboot/SpringBootCyberArkVaultPropertiesParserTest.java
 
b/components-starter/camel-cyberark-vault-starter/src/test/java/org/apache/camel/component/cyberark/vault/springboot/SpringBootCyberArkVaultPropertiesParserTest.java
new file mode 100644
index 00000000000..620a8671b1c
--- /dev/null
+++ 
b/components-starter/camel-cyberark-vault-starter/src/test/java/org/apache/camel/component/cyberark/vault/springboot/SpringBootCyberArkVaultPropertiesParserTest.java
@@ -0,0 +1,40 @@
+/*
+ * 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.cyberark.vault.springboot;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class SpringBootCyberArkVaultPropertiesParserTest {
+
+    private final SpringBootCyberArkVaultPropertiesParser parser = new 
SpringBootCyberArkVaultPropertiesParser();
+
+    @Test
+    public void guardPropertyIsUnchanged() {
+        assertEquals("camel.component.cyberark-vault.early-resolve-properties",
+                parser.getEarlyResolutionProperty(),
+                "a wrong guard key silently disables early resolution, leaving 
placeholders as literal values");
+    }
+
+    @Test
+    public void overridePropertySourceNameIsUnchanged() {
+        assertEquals("overridden-camel-cyberark-vault-properties",
+                parser.getOverridePropertySourceName(),
+                "the property source name is observable through 
/actuator/env");
+    }
+}
diff --git 
a/components-starter/camel-google-secret-manager-starter/src/main/java/org/apache/camel/component/google/secret/manager/springboot/SpringBootGoogleSecretManagerPropertiesParser.java
 
b/components-starter/camel-google-secret-manager-starter/src/main/java/org/apache/camel/component/google/secret/manager/springboot/SpringBootGoogleSecretManagerPropertiesParser.java
index 696c9c4d0de..45d56030228 100644
--- 
a/components-starter/camel-google-secret-manager-starter/src/main/java/org/apache/camel/component/google/secret/manager/springboot/SpringBootGoogleSecretManagerPropertiesParser.java
+++ 
b/components-starter/camel-google-secret-manager-starter/src/main/java/org/apache/camel/component/google/secret/manager/springboot/SpringBootGoogleSecretManagerPropertiesParser.java
@@ -20,86 +20,42 @@ import 
com.google.cloud.secretmanager.v1.SecretManagerServiceClient;
 import com.google.cloud.secretmanager.v1.SecretManagerServiceSettings;
 import org.apache.camel.RuntimeCamelException;
 import 
org.apache.camel.component.google.secret.manager.GoogleSecretManagerPropertiesFunction;
+import org.apache.camel.spi.PropertiesFunction;
+import org.apache.camel.spring.boot.AbstractEarlyResolutionPropertiesParser;
 import org.apache.camel.util.ObjectHelper;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import 
org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
-import org.springframework.boot.origin.OriginTrackedValue;
-import org.springframework.context.ApplicationListener;
 import org.springframework.core.env.ConfigurableEnvironment;
-import org.springframework.core.env.MapPropertySource;
-import org.springframework.core.env.PropertiesPropertySource;
-import org.springframework.core.env.PropertySource;
 
 import java.io.IOException;
-import java.util.Properties;
 
-public class SpringBootGoogleSecretManagerPropertiesParser implements 
ApplicationListener<ApplicationEnvironmentPreparedEvent> {
-    private static final Logger LOG = 
LoggerFactory.getLogger(SpringBootGoogleSecretManagerPropertiesParser.class);
+public class SpringBootGoogleSecretManagerPropertiesParser extends 
AbstractEarlyResolutionPropertiesParser {
 
     @Override
-    public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
+    protected String getEarlyResolutionProperty() {
+        return 
"camel.component.google-secret-manager.early-resolve-properties";
+    }
+
+    @Override
+    protected String getOverridePropertySourceName() {
+        return "overridden-camel-google-secret-manager-properties";
+    }
+
+    @Override
+    protected PropertiesFunction 
createPropertiesFunction(ConfigurableEnvironment environment) {
         SecretManagerServiceClient client;
-        ConfigurableEnvironment environment = event.getEnvironment();
-        // an unresolved placeholder would otherwise stay in the property 
value and become the effective
-        // secret, so resolution failures abort startup unless the operator 
opts back into the old behaviour
-        final boolean ignoreResolutionFailures
-                = 
Boolean.parseBoolean(environment.getProperty("camel.vault.ignore-resolution-failures"));
-        String projectId;
-        if 
(Boolean.parseBoolean(environment.getProperty("camel.component.google-secret-manager.early-resolve-properties")))
 {
-            projectId = environment.getProperty("camel.vault.gcp.projectId");
-            boolean useDefaultInstance = 
Boolean.parseBoolean(environment.getProperty("camel.vault.gcp.useDefaultInstance"));
-            if (useDefaultInstance && ObjectHelper.isNotEmpty(projectId)) {
-                SecretManagerServiceSettings settings = null;
-                try {
-                    settings = 
SecretManagerServiceSettings.newBuilder().build();
-                    client = SecretManagerServiceClient.create(settings);
-                } catch (IOException e) {
-                    throw new RuntimeException(e);
-                }
-            } else {
-                throw new RuntimeCamelException(
-                        "Using the GCP Secret Manager Properties Function in 
Spring Boot early resolver mode requires setting GCP project Id as application 
properties and use default instance option to true");
-            }
-            GoogleSecretManagerPropertiesFunction 
secretsManagerPropertiesFunction = new 
GoogleSecretManagerPropertiesFunction(client, projectId);
-            final Properties props = new Properties();
-            for (PropertySource mutablePropertySources : 
event.getEnvironment().getPropertySources()) {
-                if (mutablePropertySources instanceof MapPropertySource 
mapPropertySource) {
-                    mapPropertySource.getSource().forEach((key, value) -> {
-                        String stringValue = null;
-                        if ((value instanceof OriginTrackedValue 
originTrackedValue &&
-                                originTrackedValue.getValue() instanceof 
String v)) {
-                            stringValue = v;
-                        } else if (value instanceof String v) {
-                            stringValue = v;
-                        }
-                        if (stringValue != null &&
-                                stringValue.startsWith("{{gcp:") &&
-                                stringValue.endsWith("}}")) {
-                            LOG.debug("decrypting and overriding property {}", 
key);
-                            try {
-                                String element = 
secretsManagerPropertiesFunction.apply(stringValue
-                                        .replace("{{gcp:", "")
-                                        .replace("}}", ""));
-                                props.put(key, element);
-                            } catch (Exception e) {
-                                if (ignoreResolutionFailures) {
-                                    LOG.warn("Failed to resolve property {} 
from the vault; the placeholder is left "
-                                             + "unresolved because 
camel.vault.ignore-resolution-failures is enabled", key, e);
-                                } else {
-                                    throw new RuntimeCamelException(
-                                            "Failed to resolve property " + 
key + " from the vault. Startup is aborted so "
-                                                            + "that the 
unresolved placeholder cannot become the effective "
-                                                            + "value; set 
camel.vault.ignore-resolution-failures=true to "
-                                                            + "continue 
anyway.",
-                                            e);
-                                }
-                            }
-                        }
-                    });
-                }
+        String projectId = 
environment.getProperty("camel.vault.gcp.projectId");
+        boolean useDefaultInstance
+                = 
Boolean.parseBoolean(environment.getProperty("camel.vault.gcp.useDefaultInstance"));
+        if (useDefaultInstance && ObjectHelper.isNotEmpty(projectId)) {
+            try {
+                SecretManagerServiceSettings settings = 
SecretManagerServiceSettings.newBuilder().build();
+                client = SecretManagerServiceClient.create(settings);
+            } catch (IOException e) {
+                throw new RuntimeCamelException(e);
             }
-            environment.getPropertySources().addFirst(new 
PropertiesPropertySource("overridden-camel-google-secret-manager-properties", 
props));
+        } else {
+            throw new RuntimeCamelException(
+                    "Using the GCP Secret Manager Properties Function in 
Spring Boot early resolver mode requires setting GCP project Id as application 
properties and use default instance option to true");
         }
+        return new GoogleSecretManagerPropertiesFunction(client, projectId);
     }
 }
diff --git 
a/components-starter/camel-google-secret-manager-starter/src/test/java/org/apache/camel/component/google/secret/manager/springboot/SpringBootGoogleSecretManagerPropertiesParserTest.java
 
b/components-starter/camel-google-secret-manager-starter/src/test/java/org/apache/camel/component/google/secret/manager/springboot/SpringBootGoogleSecretManagerPropertiesParserTest.java
new file mode 100644
index 00000000000..a4cd10a5961
--- /dev/null
+++ 
b/components-starter/camel-google-secret-manager-starter/src/test/java/org/apache/camel/component/google/secret/manager/springboot/SpringBootGoogleSecretManagerPropertiesParserTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.google.secret.manager.springboot;
+
+import org.apache.camel.RuntimeCamelException;
+import org.junit.jupiter.api.Test;
+import org.springframework.core.env.StandardEnvironment;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class SpringBootGoogleSecretManagerPropertiesParserTest {
+
+    private final SpringBootGoogleSecretManagerPropertiesParser parser
+            = new SpringBootGoogleSecretManagerPropertiesParser();
+
+    @Test
+    public void guardPropertyIsUnchanged() {
+        
assertEquals("camel.component.google-secret-manager.early-resolve-properties",
+                parser.getEarlyResolutionProperty(),
+                "a wrong guard key silently disables early resolution, leaving 
placeholders as literal values");
+    }
+
+    @Test
+    public void overridePropertySourceNameIsUnchanged() {
+        assertEquals("overridden-camel-google-secret-manager-properties",
+                parser.getOverridePropertySourceName(),
+                "the property source name is observable through 
/actuator/env");
+    }
+
+    /**
+     * The integration test in this module sets {@code camel.vault.gcp.*} 
through {@code System.setProperty},
+     * and Surefire reuses the JVM across test classes. Dropping the system 
property sources keeps this test
+     * independent of execution order.
+     */
+    private static StandardEnvironment isolatedEnvironment() {
+        StandardEnvironment environment = new StandardEnvironment();
+        
environment.getPropertySources().remove(StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME);
+        
environment.getPropertySources().remove(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME);
+        return environment;
+    }
+
+    @Test
+    public void missingConfigurationFailsAsACamelException() {
+        assertThrows(RuntimeCamelException.class,
+                () -> parser.createPropertiesFunction(isolatedEnvironment()),
+                "configuration problems must surface as RuntimeCamelException 
so operators and callers can "
+                        + "treat them as one category");
+    }
+}
diff --git 
a/components-starter/camel-hashicorp-vault-starter/src/main/java/org/apache/camel/component/hashicorp/vault/springboot/SpringBootHashicorpVaultPropertiesParser.java
 
b/components-starter/camel-hashicorp-vault-starter/src/main/java/org/apache/camel/component/hashicorp/vault/springboot/SpringBootHashicorpVaultPropertiesParser.java
index eb930f40d1f..1a90708674d 100644
--- 
a/components-starter/camel-hashicorp-vault-starter/src/main/java/org/apache/camel/component/hashicorp/vault/springboot/SpringBootHashicorpVaultPropertiesParser.java
+++ 
b/components-starter/camel-hashicorp-vault-starter/src/main/java/org/apache/camel/component/hashicorp/vault/springboot/SpringBootHashicorpVaultPropertiesParser.java
@@ -18,93 +18,57 @@ package 
org.apache.camel.component.hashicorp.vault.springboot;
 
 import org.apache.camel.RuntimeCamelException;
 import 
org.apache.camel.component.hashicorp.vault.HashicorpVaultPropertiesFunction;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import 
org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
-import org.springframework.boot.origin.OriginTrackedValue;
-import org.springframework.context.ApplicationListener;
+import org.apache.camel.spi.PropertiesFunction;
+import org.apache.camel.spring.boot.AbstractEarlyResolutionPropertiesParser;
+import org.apache.camel.util.ObjectHelper;
 import org.springframework.core.env.ConfigurableEnvironment;
-import org.springframework.core.env.MapPropertySource;
-import org.springframework.core.env.PropertiesPropertySource;
-import org.springframework.core.env.PropertySource;
 import org.springframework.vault.authentication.TokenAuthentication;
 import org.springframework.vault.client.VaultEndpoint;
 import org.springframework.vault.core.VaultTemplate;
 
-import java.util.Objects;
-import java.util.Properties;
-
-public class SpringBootHashicorpVaultPropertiesParser implements 
ApplicationListener<ApplicationEnvironmentPreparedEvent> {
-    private static final Logger LOG = 
LoggerFactory.getLogger(SpringBootHashicorpVaultPropertiesParser.class);
+public class SpringBootHashicorpVaultPropertiesParser extends 
AbstractEarlyResolutionPropertiesParser {
 
     @Override
-    public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
-        ConfigurableEnvironment environment = event.getEnvironment();
-        // an unresolved placeholder would otherwise stay in the property 
value and become the effective
-        // secret, so resolution failures abort startup unless the operator 
opts back into the old behaviour
-        final boolean ignoreResolutionFailures
-                = 
Boolean.parseBoolean(environment.getProperty("camel.vault.ignore-resolution-failures"));
-        if 
(Boolean.parseBoolean(environment.getProperty("camel.component.hashicorp-vault.early-resolve-properties")))
 {
-            
Objects.requireNonNull(environment.getProperty("camel.vault.hashicorp.token"), 
"Hashicorp Vault token is required");
-            
Objects.requireNonNull(environment.getProperty("camel.vault.hashicorp.host"), 
"Hashicorp Vault host is required");
-            
Objects.requireNonNull(environment.getProperty("camel.vault.hashicorp.port"), 
"Hashicorp Vault port is required");
-            
Objects.requireNonNull(environment.getProperty("camel.vault.hashicorp.scheme"), 
"Hashicorp Vault scheme is required");
-
-            String token = 
environment.getProperty("camel.vault.hashicorp.token");
-            String host = 
environment.getProperty("camel.vault.hashicorp.host");
+    protected String getEarlyResolutionProperty() {
+        return "camel.component.hashicorp-vault.early-resolve-properties";
+    }
 
-            int port = 
Integer.parseInt(environment.getProperty("camel.vault.hashicorp.port"));
-            String scheme = 
environment.getProperty("camel.vault.hashicorp.scheme");
+    @Override
+    protected String getOverridePropertySourceName() {
+        return "overridden-camel-hashicorp-vault-properties";
+    }
 
-            VaultEndpoint vaultEndpoint = new VaultEndpoint();
-            vaultEndpoint.setHost(host);
-            vaultEndpoint.setPort(port);
-            vaultEndpoint.setScheme(scheme);
+    @Override
+    protected PropertiesFunction 
createPropertiesFunction(ConfigurableEnvironment environment) {
+        String token = required(environment, "camel.vault.hashicorp.token", 
"Hashicorp Vault token is required");
+        String host = required(environment, "camel.vault.hashicorp.host", 
"Hashicorp Vault host is required");
+        String portValue = required(environment, "camel.vault.hashicorp.port", 
"Hashicorp Vault port is required");
+        String scheme = required(environment, "camel.vault.hashicorp.scheme", 
"Hashicorp Vault scheme is required");
 
-            VaultTemplate client = new VaultTemplate(
-                    vaultEndpoint,
-                    new TokenAuthentication(token));
-            HashicorpVaultPropertiesFunction hashicorpVaultPropertiesFunction 
= new HashicorpVaultPropertiesFunction(client);
+        int port;
+        try {
+            port = Integer.parseInt(portValue);
+        } catch (NumberFormatException e) {
+            throw new RuntimeCamelException(
+                    "camel.vault.hashicorp.port must be a number but was: " + 
portValue, e);
+        }
 
-            final Properties props = new Properties();
-            for (PropertySource mutablePropertySources : 
event.getEnvironment().getPropertySources()) {
-                if (mutablePropertySources instanceof MapPropertySource 
mapPropertySource) {
-                    mapPropertySource.getSource().forEach((key, value) -> {
-                        String stringValue = null;
-                        if ((value instanceof OriginTrackedValue 
originTrackedValue &&
-                                originTrackedValue.getValue() instanceof 
String v)) {
-                            stringValue = v;
-                        } else if (value instanceof String v) {
-                            stringValue = v;
-                        }
+        VaultEndpoint vaultEndpoint = new VaultEndpoint();
+        vaultEndpoint.setHost(host);
+        vaultEndpoint.setPort(port);
+        vaultEndpoint.setScheme(scheme);
 
-                        if (stringValue != null &&
-                                stringValue.startsWith("{{hashicorp:") &&
-                                stringValue.endsWith("}}")) {
-                            LOG.debug("decrypting and overriding property {}", 
key);
-                            try {
-                                props.put(key, 
hashicorpVaultPropertiesFunction.apply(stringValue
-                                        .replace("{{hashicorp:", "")
-                                        .replace("}}", "")));
-                            } catch (Exception e) {
-                                if (ignoreResolutionFailures) {
-                                    LOG.warn("Failed to resolve property {} 
from the vault; the placeholder is left "
-                                             + "unresolved because 
camel.vault.ignore-resolution-failures is enabled", key, e);
-                                } else {
-                                    throw new RuntimeCamelException(
-                                            "Failed to resolve property " + 
key + " from the vault. Startup is aborted so "
-                                                            + "that the 
unresolved placeholder cannot become the effective "
-                                                            + "value; set 
camel.vault.ignore-resolution-failures=true to "
-                                                            + "continue 
anyway.",
-                                            e);
-                                }
-                            }
-                        }
-                    });
-                }
-            }
+        VaultTemplate client = new VaultTemplate(
+                vaultEndpoint,
+                new TokenAuthentication(token));
+        return new HashicorpVaultPropertiesFunction(client);
+    }
 
-            environment.getPropertySources().addFirst(new 
PropertiesPropertySource("overridden-camel-hashicorp-vault-properties", props));
+    private static String required(ConfigurableEnvironment environment, String 
key, String message) {
+        String value = environment.getProperty(key);
+        if (ObjectHelper.isEmpty(value)) {
+            throw new RuntimeCamelException(message + " (set " + key + ")");
         }
+        return value;
     }
 }
diff --git 
a/components-starter/camel-hashicorp-vault-starter/src/test/java/org/apache/camel/component/hashicorp/vault/springboot/SpringBootHashicorpVaultPropertiesParserTest.java
 
b/components-starter/camel-hashicorp-vault-starter/src/test/java/org/apache/camel/component/hashicorp/vault/springboot/SpringBootHashicorpVaultPropertiesParserTest.java
new file mode 100644
index 00000000000..c6c79905ad6
--- /dev/null
+++ 
b/components-starter/camel-hashicorp-vault-starter/src/test/java/org/apache/camel/component/hashicorp/vault/springboot/SpringBootHashicorpVaultPropertiesParserTest.java
@@ -0,0 +1,120 @@
+/*
+ * 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.hashicorp.vault.springboot;
+
+import org.apache.camel.RuntimeCamelException;
+import org.junit.jupiter.api.Test;
+import org.springframework.core.env.MapPropertySource;
+import org.springframework.core.env.StandardEnvironment;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class SpringBootHashicorpVaultPropertiesParserTest {
+
+    private final SpringBootHashicorpVaultPropertiesParser parser = new 
SpringBootHashicorpVaultPropertiesParser();
+
+    /**
+     * The integration tests in this module set {@code 
camel.vault.hashicorp.*} through
+     * {@code System.setProperty}, and Surefire reuses the JVM across test 
classes. Dropping the system
+     * property sources keeps these tests independent of execution order.
+     */
+    private static StandardEnvironment environmentWith(Map<String, ?> 
properties) {
+        StandardEnvironment environment = new StandardEnvironment();
+        
environment.getPropertySources().remove(StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME);
+        
environment.getPropertySources().remove(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME);
+        environment.getPropertySources()
+                .addFirst(new MapPropertySource("test-properties", new 
LinkedHashMap<>(properties)));
+        return environment;
+    }
+
+    @Test
+    public void guardPropertyIsUnchanged() {
+        
assertEquals("camel.component.hashicorp-vault.early-resolve-properties",
+                parser.getEarlyResolutionProperty(),
+                "a wrong guard key silently disables early resolution, leaving 
placeholders as literal values");
+    }
+
+    @Test
+    public void overridePropertySourceNameIsUnchanged() {
+        assertEquals("overridden-camel-hashicorp-vault-properties",
+                parser.getOverridePropertySourceName(),
+                "the property source name is observable through 
/actuator/env");
+    }
+
+    @Test
+    public void missingTokenFailsAsACamelExceptionNamingTheProperty() {
+        RuntimeCamelException thrown = 
assertThrows(RuntimeCamelException.class,
+                () -> 
parser.createPropertiesFunction(environmentWith(Map.of())),
+                "a missing setting is an operator error, not a programming 
defect, so it must not surface "
+                        + "as NullPointerException");
+
+        assertTrue(thrown.getMessage().contains("token"),
+                "the failure must name the missing setting, was: " + 
thrown.getMessage());
+    }
+
+    @Test
+    public void missingHostFailsAsACamelExceptionNamingTheProperty() {
+        RuntimeCamelException thrown = 
assertThrows(RuntimeCamelException.class,
+                () -> parser.createPropertiesFunction(environmentWith(Map.of(
+                        "camel.vault.hashicorp.token", "a-token"))),
+                "a missing setting is an operator error, not a programming 
defect, so it must not surface "
+                        + "as NullPointerException");
+
+        assertTrue(thrown.getMessage().contains("host"),
+                "the failure must name the missing setting, was: " + 
thrown.getMessage());
+        assertTrue(thrown.getMessage().contains("camel.vault.hashicorp.host"),
+                "the failure must name the missing property, was: " + 
thrown.getMessage());
+    }
+
+    @Test
+    public void missingSchemeFailsAsACamelExceptionNamingTheProperty() {
+        RuntimeCamelException thrown = 
assertThrows(RuntimeCamelException.class,
+                () -> parser.createPropertiesFunction(environmentWith(Map.of(
+                        "camel.vault.hashicorp.token", "a-token",
+                        "camel.vault.hashicorp.host", "127.0.0.1",
+                        "camel.vault.hashicorp.port", "8200"))),
+                "a missing setting is an operator error, not a programming 
defect, so it must not surface "
+                        + "as NullPointerException");
+
+        assertTrue(thrown.getMessage().contains("scheme"),
+                "the failure must name the missing setting, was: " + 
thrown.getMessage());
+        
assertTrue(thrown.getMessage().contains("camel.vault.hashicorp.scheme"),
+                "the failure must name the missing property, was: " + 
thrown.getMessage());
+    }
+
+    @Test
+    public void nonNumericPortFailsAsACamelExceptionNamingTheProperty() {
+        RuntimeCamelException thrown = 
assertThrows(RuntimeCamelException.class,
+                () -> parser.createPropertiesFunction(environmentWith(Map.of(
+                        "camel.vault.hashicorp.token", "a-token",
+                        "camel.vault.hashicorp.host", "127.0.0.1",
+                        "camel.vault.hashicorp.port", "not-a-number",
+                        "camel.vault.hashicorp.scheme", "http"))),
+                "a bare NumberFormatException never says which property was 
malformed");
+
+        assertTrue(thrown.getMessage().contains("camel.vault.hashicorp.port"),
+                "the failure must name the malformed property, was: " + 
thrown.getMessage());
+        assertInstanceOf(NumberFormatException.class, thrown.getCause(),
+                "the cause must be preserved so the original 
NumberFormatException is not lost");
+    }
+}
diff --git 
a/components-starter/camel-ibm-secrets-manager-starter/src/main/java/org/apache/camel/component/ibm/secrets/manager/springboot/IBMSecretsManagerVaultPropertiesParser.java
 
b/components-starter/camel-ibm-secrets-manager-starter/src/main/java/org/apache/camel/component/ibm/secrets/manager/springboot/IBMSecretsManagerVaultPropertiesParser.java
index 5bca3dc9ef8..1bfdbcc75d2 100644
--- 
a/components-starter/camel-ibm-secrets-manager-starter/src/main/java/org/apache/camel/component/ibm/secrets/manager/springboot/IBMSecretsManagerVaultPropertiesParser.java
+++ 
b/components-starter/camel-ibm-secrets-manager-starter/src/main/java/org/apache/camel/component/ibm/secrets/manager/springboot/IBMSecretsManagerVaultPropertiesParser.java
@@ -20,84 +20,38 @@ import com.ibm.cloud.sdk.core.security.IamAuthenticator;
 import com.ibm.cloud.secrets_manager_sdk.secrets_manager.v2.SecretsManager;
 import org.apache.camel.RuntimeCamelException;
 import 
org.apache.camel.component.ibm.secrets.manager.IBMSecretsManagerPropertiesFunction;
+import org.apache.camel.spi.PropertiesFunction;
+import org.apache.camel.spring.boot.AbstractEarlyResolutionPropertiesParser;
 import org.apache.camel.util.ObjectHelper;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import 
org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
-import org.springframework.boot.origin.OriginTrackedValue;
-import org.springframework.context.ApplicationListener;
 import org.springframework.core.env.ConfigurableEnvironment;
-import org.springframework.core.env.MapPropertySource;
-import org.springframework.core.env.PropertiesPropertySource;
-import org.springframework.core.env.PropertySource;
 
-import java.util.Properties;
+public class IBMSecretsManagerVaultPropertiesParser extends 
AbstractEarlyResolutionPropertiesParser {
 
-public class IBMSecretsManagerVaultPropertiesParser implements 
ApplicationListener<ApplicationEnvironmentPreparedEvent> {
-    private static final Logger LOG = 
LoggerFactory.getLogger(IBMSecretsManagerVaultPropertiesParser.class);
+    @Override
+    protected String getEarlyResolutionProperty() {
+        return "camel.component.ibm-secrets-manager.early-resolve-properties";
+    }
+
+    @Override
+    protected String getOverridePropertySourceName() {
+        return "overridden-ibm-secrets-manager-properties";
+    }
 
     @Override
-    public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
+    protected PropertiesFunction 
createPropertiesFunction(ConfigurableEnvironment environment) {
         SecretsManager client;
-        ConfigurableEnvironment environment = event.getEnvironment();
-        // an unresolved placeholder would otherwise stay in the property 
value and become the effective
-        // secret, so resolution failures abort startup unless the operator 
opts back into the old behaviour
-        final boolean ignoreResolutionFailures
-                = 
Boolean.parseBoolean(environment.getProperty("camel.vault.ignore-resolution-failures"));
-        String token;
-        String serviceUrl;
-        if 
(Boolean.parseBoolean(environment.getProperty("camel.component.ibm-secrets-manager.early-resolve-properties")))
 {
-            token = environment.getProperty("camel.vault.ibm.token");
-            serviceUrl = environment.getProperty("camel.vault.ibm.serviceUrl");
-            if (ObjectHelper.isNotEmpty(token) && 
ObjectHelper.isNotEmpty(serviceUrl)) {
-                IamAuthenticator iamAuthenticator = new 
IamAuthenticator.Builder()
-                        .apikey(token)
-                        .build();
-                client = new SecretsManager("Camel Secrets Manager Service for 
Properties", iamAuthenticator);
-                client.setServiceUrl(serviceUrl);
-            } else {
-                throw new RuntimeCamelException(
-                        "Using the IBM Secrets Manager Properties Function 
requires setting IBM Credentials and service url as application properties or 
environment variables");
-            }
-            IBMSecretsManagerPropertiesFunction 
secretsManagerPropertiesFunction = new 
IBMSecretsManagerPropertiesFunction(client);
-            final Properties props = new Properties();
-            for (PropertySource mutablePropertySources : 
event.getEnvironment().getPropertySources()) {
-                if (mutablePropertySources instanceof MapPropertySource 
mapPropertySource) {
-                    mapPropertySource.getSource().forEach((key, value) -> {
-                        String stringValue = null;
-                        if ((value instanceof OriginTrackedValue 
originTrackedValue &&
-                                originTrackedValue.getValue() instanceof 
String v)) {
-                            stringValue = v;
-                        } else if (value instanceof String v) {
-                            stringValue = v;
-                        }
-                        if (stringValue != null &&
-                                stringValue.startsWith("{{ibm:") &&
-                                stringValue.endsWith("}}")) {
-                            LOG.debug("decrypting and overriding property {}", 
key);
-                            try {
-                                String element = 
secretsManagerPropertiesFunction.apply(stringValue
-                                        .replace("{{ibm:", "")
-                                        .replace("}}", ""));
-                                props.put(key, element);
-                            } catch (Exception e) {
-                                if (ignoreResolutionFailures) {
-                                    LOG.warn("Failed to resolve property {} 
from the vault; the placeholder is left "
-                                             + "unresolved because 
camel.vault.ignore-resolution-failures is enabled", key, e);
-                                } else {
-                                    throw new RuntimeCamelException(
-                                            "Failed to resolve property " + 
key + " from the vault. Startup is aborted so "
-                                                            + "that the 
unresolved placeholder cannot become the effective "
-                                                            + "value; set 
camel.vault.ignore-resolution-failures=true to "
-                                                            + "continue 
anyway.",
-                                            e);
-                                }
-                            }
-                        }
-                    });
-                }
-            }
-            environment.getPropertySources().addFirst(new 
PropertiesPropertySource("overridden-ibm-secrets-manager-properties", props));
+        String token = environment.getProperty("camel.vault.ibm.token");
+        String serviceUrl = 
environment.getProperty("camel.vault.ibm.serviceUrl");
+        if (ObjectHelper.isNotEmpty(token) && 
ObjectHelper.isNotEmpty(serviceUrl)) {
+            IamAuthenticator iamAuthenticator = new IamAuthenticator.Builder()
+                    .apikey(token)
+                    .build();
+            client = new SecretsManager("Camel Secrets Manager Service for 
Properties", iamAuthenticator);
+            client.setServiceUrl(serviceUrl);
+        } else {
+            throw new RuntimeCamelException(
+                    "Using the IBM Secrets Manager Properties Function 
requires setting IBM Credentials and service url as application properties or 
environment variables");
         }
+        return new IBMSecretsManagerPropertiesFunction(client);
     }
-}
\ No newline at end of file
+}
diff --git 
a/components-starter/camel-ibm-secrets-manager-starter/src/test/java/org/apache/camel/component/ibm/secrets/manager/springboot/IBMSecretsManagerVaultPropertiesParserTest.java
 
b/components-starter/camel-ibm-secrets-manager-starter/src/test/java/org/apache/camel/component/ibm/secrets/manager/springboot/IBMSecretsManagerVaultPropertiesParserTest.java
new file mode 100644
index 00000000000..be46bb86a75
--- /dev/null
+++ 
b/components-starter/camel-ibm-secrets-manager-starter/src/test/java/org/apache/camel/component/ibm/secrets/manager/springboot/IBMSecretsManagerVaultPropertiesParserTest.java
@@ -0,0 +1,41 @@
+/*
+ * 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.springboot;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class IBMSecretsManagerVaultPropertiesParserTest {
+
+    private final IBMSecretsManagerVaultPropertiesParser parser = new 
IBMSecretsManagerVaultPropertiesParser();
+
+    @Test
+    public void guardPropertyIsUnchanged() {
+        
assertEquals("camel.component.ibm-secrets-manager.early-resolve-properties",
+                parser.getEarlyResolutionProperty(),
+                "a wrong guard key silently disables early resolution, leaving 
placeholders as literal values");
+    }
+
+    @Test
+    public void overridePropertySourceNameKeepsItsHistoricalSpelling() {
+        assertEquals("overridden-ibm-secrets-manager-properties",
+                parser.getOverridePropertySourceName(),
+                "this name lacks the camel- segment the other starters use; it 
is observable through "
+                        + "/actuator/env so it must not be normalised");
+    }
+}
diff --git 
a/components-starter/camel-spring-cloud-config-starter/src/main/java/org/apache/camel/component/spring/cloud/config/springboot/SpringBootCloudConfigPropertiesParser.java
 
b/components-starter/camel-spring-cloud-config-starter/src/main/java/org/apache/camel/component/spring/cloud/config/springboot/SpringBootCloudConfigPropertiesParser.java
index d2c951b9023..b157d4749ab 100644
--- 
a/components-starter/camel-spring-cloud-config-starter/src/main/java/org/apache/camel/component/spring/cloud/config/springboot/SpringBootCloudConfigPropertiesParser.java
+++ 
b/components-starter/camel-spring-cloud-config-starter/src/main/java/org/apache/camel/component/spring/cloud/config/springboot/SpringBootCloudConfigPropertiesParser.java
@@ -16,72 +16,33 @@
  */
 package org.apache.camel.component.spring.cloud.config.springboot;
 
-import org.apache.camel.RuntimeCamelException;
 import 
org.apache.camel.component.spring.cloud.config.SpringCloudConfigPropertiesFunction;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import 
org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
-import org.springframework.boot.origin.OriginTrackedValue;
-import org.springframework.context.ApplicationListener;
+import org.apache.camel.spi.PropertiesFunction;
+import org.apache.camel.spring.boot.AbstractEarlyResolutionPropertiesParser;
 import org.springframework.core.env.ConfigurableEnvironment;
-import org.springframework.core.env.MapPropertySource;
-import org.springframework.core.env.PropertiesPropertySource;
-import org.springframework.core.env.PropertySource;
 
-import java.util.Properties;
+public class SpringBootCloudConfigPropertiesParser extends 
AbstractEarlyResolutionPropertiesParser {
 
-public class SpringBootCloudConfigPropertiesParser implements 
ApplicationListener<ApplicationEnvironmentPreparedEvent> {
-    private static final Logger LOG = 
LoggerFactory.getLogger(SpringBootCloudConfigPropertiesParser.class);
+    @Override
+    protected String getEarlyResolutionProperty() {
+        return "camel.component.spring-cloud-config.early-resolve-properties";
+    }
 
     @Override
-    public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
-        Properties properties = new Properties();
-        ConfigurableEnvironment environment = event.getEnvironment();
-        // an unresolved placeholder would otherwise stay in the property 
value and become the effective
-        // configuration value, so resolution failures abort startup unless 
the operator opts back in
-        final boolean ignoreResolutionFailures
-                = 
Boolean.parseBoolean(environment.getProperty("camel.vault.ignore-resolution-failures"));
+    protected String getOverridePropertySourceName() {
+        return "overridden-camel-spring-config-properties";
+    }
 
-        if (Boolean.parseBoolean(
-                
environment.getProperty("camel.component.spring-cloud-config.early-resolve-properties")))
 {
-            SpringCloudConfigPropertiesFunction 
springCloudConfigPropertiesFunction = new SpringCloudConfigPropertiesFunction();
-            springCloudConfigPropertiesFunction.setEnvironment(environment);
-            for (PropertySource mutablePropertySources : 
event.getEnvironment().getPropertySources()) {
-                if (mutablePropertySources instanceof MapPropertySource 
mapPropertySource) {
-                    mapPropertySource.getSource().forEach((key, value) -> {
-                        String stringValue = null;
-                        if ((value instanceof OriginTrackedValue 
originTrackedValue
-                                && originTrackedValue.getValue() instanceof 
String v)) {
-                            stringValue = v;
-                        } else if (value instanceof String v) {
-                            stringValue = v;
-                        }
-                        if (stringValue != null && 
stringValue.startsWith("{{spring-config:")
-                                && stringValue.endsWith("}}")) {
-                            LOG.debug("decrypting and overriding property {}", 
key);
-                            try {
-                                String element = 
springCloudConfigPropertiesFunction
-                                        
.apply(stringValue.replace("{{spring-config:", "").replace("}}", ""));
-                                properties.put(key, element);
-                            } catch (Exception e) {
-                                if (ignoreResolutionFailures) {
-                                    LOG.warn("Failed to resolve property {} 
from Spring Cloud Config; the placeholder is left "
-                                             + "unresolved because 
camel.vault.ignore-resolution-failures is enabled", key, e);
-                                } else {
-                                    throw new RuntimeCamelException(
-                                            "Failed to resolve property " + 
key + " from Spring Cloud Config. Startup is aborted "
-                                                            + "so that the 
unresolved placeholder cannot become the effective "
-                                                            + "value; set 
camel.vault.ignore-resolution-failures=true to "
-                                                            + "continue 
anyway.",
-                                            e);
-                                }
-                            }
-                        }
-                    });
-                }
-            }
-            environment.getPropertySources()
-                    .addFirst(new 
PropertiesPropertySource("overridden-camel-spring-config-properties", 
properties));
-        }
+    @Override
+    protected String getSourceDescription() {
+        return "Spring Cloud Config";
+    }
+
+    @Override
+    protected PropertiesFunction 
createPropertiesFunction(ConfigurableEnvironment environment) {
+        SpringCloudConfigPropertiesFunction springCloudConfigPropertiesFunction
+                = new SpringCloudConfigPropertiesFunction();
+        springCloudConfigPropertiesFunction.setEnvironment(environment);
+        return springCloudConfigPropertiesFunction;
     }
 }
diff --git 
a/components-starter/camel-spring-cloud-config-starter/src/test/java/org/apache/camel/component/spring/cloud/config/springboot/SpringBootCloudConfigPropertiesParserTest.java
 
b/components-starter/camel-spring-cloud-config-starter/src/test/java/org/apache/camel/component/spring/cloud/config/springboot/SpringBootCloudConfigPropertiesParserTest.java
new file mode 100644
index 00000000000..f0fe8e48df3
--- /dev/null
+++ 
b/components-starter/camel-spring-cloud-config-starter/src/test/java/org/apache/camel/component/spring/cloud/config/springboot/SpringBootCloudConfigPropertiesParserTest.java
@@ -0,0 +1,46 @@
+/*
+ * 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.spring.cloud.config.springboot;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class SpringBootCloudConfigPropertiesParserTest {
+
+    private final SpringBootCloudConfigPropertiesParser parser = new 
SpringBootCloudConfigPropertiesParser();
+
+    @Test
+    public void guardPropertyIsUnchanged() {
+        
assertEquals("camel.component.spring-cloud-config.early-resolve-properties",
+                parser.getEarlyResolutionProperty(),
+                "a wrong guard key silently disables early resolution, leaving 
placeholders as literal values");
+    }
+
+    @Test
+    public void overridePropertySourceNameIsUnchanged() {
+        assertEquals("overridden-camel-spring-config-properties",
+                parser.getOverridePropertySourceName(),
+                "the property source name is observable through 
/actuator/env");
+    }
+
+    @Test
+    public void diagnosticsNameSpringCloudConfigRatherThanAVault() {
+        assertEquals("Spring Cloud Config", parser.getSourceDescription(),
+                "this starter reads a config server, so calling it a vault 
would misdirect the operator");
+    }
+}
diff --git 
a/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/AbstractEarlyResolutionPropertiesParser.java
 
b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/AbstractEarlyResolutionPropertiesParser.java
new file mode 100644
index 00000000000..3067617deb7
--- /dev/null
+++ 
b/core/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/AbstractEarlyResolutionPropertiesParser.java
@@ -0,0 +1,163 @@
+/*
+ * 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.spring.boot;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Properties;
+import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.spi.PropertiesFunction;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import 
org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
+import org.springframework.boot.origin.OriginTrackedValue;
+import org.springframework.context.ApplicationListener;
+import org.springframework.core.env.ConfigurableEnvironment;
+import org.springframework.core.env.MapPropertySource;
+import org.springframework.core.env.PropertiesPropertySource;
+import org.springframework.core.env.PropertySource;
+
+/**
+ * Base class for the early-resolution listeners used by the Camel vault and 
secrets starters.
+ * <p/>
+ * These listeners run on {@link ApplicationEnvironmentPreparedEvent}, before 
the application context exists, so
+ * they cannot use dependency injection or conditional auto-configuration. 
This class owns the parts that are the
+ * same for every component: reading the guard property, walking the property 
sources, matching placeholders,
+ * resolving them and registering the resolved values. Subclasses contribute 
only the component-specific client
+ * construction and naming.
+ * <p/>
+ * A property value is an early-resolution candidate only when it starts with 
<code>{{&lt;functionName&gt;:</code>
+ * and ends with <code>}}</code>; everything in between is passed to the 
properties function verbatim. A value
+ * that concatenates two placeholders, such as {@code 
{{aws:user}}:{{aws:pass}}}, satisfies both conditions and
+ * is therefore not handled correctly; this is a known limitation, not a 
supported use case.
+ */
+public abstract class AbstractEarlyResolutionPropertiesParser
+        implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {
+
+    /**
+     * Lets an operator tolerate resolution failures instead of aborting 
startup.
+     */
+    public static final String IGNORE_RESOLUTION_FAILURES = 
"camel.vault.ignore-resolution-failures";
+
+    private static final String SUFFIX = "}}";
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(AbstractEarlyResolutionPropertiesParser.class);
+
+    /**
+     * The property that enables early resolution for this component, for 
example
+     * {@code camel.component.aws-secrets-manager.early-resolve-properties}.
+     */
+    protected abstract String getEarlyResolutionProperty();
+
+    /**
+     * The name of the property source holding the resolved values, for example
+     * {@code overridden-camel-aws-secrets-manager-properties}. These names 
are observable through
+     * {@code /actuator/env}, so they must not change.
+     */
+    protected abstract String getOverridePropertySourceName();
+
+    /**
+     * Builds the component client and its resolver. Called only after the 
guard property is enabled, so that
+     * client construction and its configuration validation never run for 
applications that did not opt in.
+     */
+    protected abstract PropertiesFunction 
createPropertiesFunction(ConfigurableEnvironment environment);
+
+    /**
+     * Wording used in diagnostics to name where a value was being resolved 
from.
+     */
+    protected String getSourceDescription() {
+        return "the vault";
+    }
+
+    @Override
+    public final void onApplicationEvent(ApplicationEnvironmentPreparedEvent 
event) {
+        ConfigurableEnvironment environment = event.getEnvironment();
+        if 
(!Boolean.parseBoolean(environment.getProperty(getEarlyResolutionProperty()))) {
+            return;
+        }
+
+        // an unresolved placeholder would otherwise stay in the property 
value and become the effective
+        // secret, so resolution failures abort startup unless the operator 
opts back into the old behaviour
+        final boolean ignoreResolutionFailures
+                = 
Boolean.parseBoolean(environment.getProperty(IGNORE_RESOLUTION_FAILURES));
+
+        PropertiesFunction propertiesFunction = 
createPropertiesFunction(environment);
+        LOG.debug("Early resolving properties using the {} function", 
propertiesFunction.getName());
+
+        final String prefix = "{{" + propertiesFunction.getName() + ":";
+        final Properties props = new Properties();
+        final Map<String, RuntimeCamelException> failures = new 
LinkedHashMap<>();
+
+        for (PropertySource<?> propertySource : 
environment.getPropertySources()) {
+            if (propertySource instanceof MapPropertySource mapPropertySource) 
{
+                mapPropertySource.getSource().forEach((key, value) -> {
+                    String stringValue = asString(value);
+                    if (stringValue != null && stringValue.startsWith(prefix) 
&& stringValue.endsWith(SUFFIX)) {
+                        String remainder = 
stringValue.substring(prefix.length(),
+                                stringValue.length() - SUFFIX.length());
+                        LOG.debug("Resolving and overriding property {}", key);
+                        try {
+                            String resolved = 
propertiesFunction.apply(remainder);
+                            if (resolved == null) {
+                                throw new RuntimeCamelException(
+                                        "The " + propertiesFunction.getName()
+                                                + " properties function 
returned no value for " + remainder);
+                            }
+                            props.put(key, resolved);
+                        } catch (Exception e) {
+                            if (ignoreResolutionFailures) {
+                                LOG.warn("Failed to resolve property {} from 
{}; the placeholder is left "
+                                         + "unresolved because {} is enabled",
+                                        key, getSourceDescription(), 
IGNORE_RESOLUTION_FAILURES, e);
+                            } else {
+                                failures.put(key, new RuntimeCamelException(
+                                        "Failed to resolve property " + key + 
" from " + getSourceDescription()
+                                                + ".",
+                                        e));
+                            }
+                        }
+                    }
+                });
+            }
+        }
+
+        if (!failures.isEmpty()) {
+            RuntimeCamelException aggregated = new RuntimeCamelException(
+                    "Failed to resolve " + failures.size() + " property 
placeholder(s) from "
+                            + getSourceDescription() + ": " + String.join(", 
", failures.keySet())
+                            + ". Startup is aborted so that the unresolved 
placeholders cannot "
+                            + "become the effective values; set " + 
IGNORE_RESOLUTION_FAILURES
+                            + "=true to continue anyway.");
+            failures.values().forEach(aggregated::addSuppressed);
+            throw aggregated;
+        }
+
+        environment.getPropertySources()
+                .addFirst(new 
PropertiesPropertySource(getOverridePropertySourceName(), props));
+    }
+
+    private static String asString(Object value) {
+        if (value instanceof OriginTrackedValue originTrackedValue
+                && originTrackedValue.getValue() instanceof String v) {
+            return v;
+        }
+        if (value instanceof String v) {
+            return v;
+        }
+        return null;
+    }
+}
diff --git 
a/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/AbstractEarlyResolutionPropertiesParserTest.java
 
b/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/AbstractEarlyResolutionPropertiesParserTest.java
new file mode 100644
index 00000000000..0ea03ef856c
--- /dev/null
+++ 
b/core/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/AbstractEarlyResolutionPropertiesParserTest.java
@@ -0,0 +1,312 @@
+/*
+ * 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.spring.boot;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.spi.PropertiesFunction;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.bootstrap.DefaultBootstrapContext;
+import 
org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
+import org.springframework.boot.origin.OriginTrackedValue;
+import org.springframework.core.env.ConfigurableEnvironment;
+import org.springframework.core.env.MapPropertySource;
+import org.springframework.core.env.StandardEnvironment;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class AbstractEarlyResolutionPropertiesParserTest {
+
+    private static final String GUARD = 
"camel.component.test-vault.early-resolve-properties";
+    private static final String OVERRIDE_SOURCE = 
"overridden-camel-test-vault-properties";
+
+    /**
+     * Records every key the parser asks the environment for, so that the 
order in which the guard and the
+     * opt-out flag are read can be asserted.
+     */
+    private static final class RecordingEnvironment extends 
StandardEnvironment {
+        private final List<String> queried = new ArrayList<>();
+
+        @Override
+        public String getProperty(String key) {
+            queried.add(key);
+            return super.getProperty(key);
+        }
+    }
+
+    /**
+     * Stands in for a vault client. Records what it was asked to resolve and 
fails for a configured set of
+     * remainders, so no container or network is needed.
+     */
+    private static final class StubPropertiesFunction implements 
PropertiesFunction {
+        private final Map<String, String> values;
+        private final Set<String> failing;
+        private final List<String> applied = new ArrayList<>();
+
+        StubPropertiesFunction(Map<String, String> values, Set<String> 
failing) {
+            this.values = values;
+            this.failing = failing;
+        }
+
+        @Override
+        public String getName() {
+            return "test";
+        }
+
+        @Override
+        public String apply(String remainder) {
+            applied.add(remainder);
+            if (failing.contains(remainder)) {
+                throw new IllegalStateException("cannot resolve " + remainder);
+            }
+            return values.get(remainder);
+        }
+    }
+
+    private static final class TestParser extends 
AbstractEarlyResolutionPropertiesParser {
+        private final StubPropertiesFunction function;
+        private int factoryCalls;
+
+        TestParser(StubPropertiesFunction function) {
+            this.function = function;
+        }
+
+        @Override
+        protected String getEarlyResolutionProperty() {
+            return GUARD;
+        }
+
+        @Override
+        protected String getOverridePropertySourceName() {
+            return OVERRIDE_SOURCE;
+        }
+
+        @Override
+        protected PropertiesFunction 
createPropertiesFunction(ConfigurableEnvironment environment) {
+            factoryCalls++;
+            return function;
+        }
+    }
+
+    private static ApplicationEnvironmentPreparedEvent 
eventFor(ConfigurableEnvironment environment) {
+        return new ApplicationEnvironmentPreparedEvent(
+                new DefaultBootstrapContext(), new SpringApplication(), new 
String[0], environment);
+    }
+
+    /**
+     * Accepts {@code Map<String, ?>} so that both {@code Map.of("k", "v")} 
(which infers
+     * {@code Map<String, String>}) and maps holding an OriginTrackedValue can 
be passed.
+     */
+    private static RecordingEnvironment environmentWith(Map<String, ?> 
properties) {
+        RecordingEnvironment environment = new RecordingEnvironment();
+        environment.getPropertySources()
+                .addFirst(new MapPropertySource("test-properties", new 
LinkedHashMap<>(properties)));
+        return environment;
+    }
+
+    @Test
+    public void guardDisabledDoesNotBuildTheClient() {
+        RecordingEnvironment environment = environmentWith(Map.of(
+                "my.secret", "{{test:some/secret}}"));
+        TestParser parser = new TestParser(new 
StubPropertiesFunction(Map.of(), Set.of()));
+
+        parser.onApplicationEvent(eventFor(environment));
+
+        assertEquals(0, parser.factoryCalls,
+                "building a vault client while early resolution is disabled 
would abort startup for every "
+                        + "application that never opted in");
+        assertNull(environment.getPropertySources().get(OVERRIDE_SOURCE),
+                "no override property source may be added when early 
resolution is disabled");
+    }
+
+    @Test
+    public void guardEnabledBuildsTheClientAndAddsTheOverrideSource() {
+        RecordingEnvironment environment = environmentWith(Map.of(GUARD, 
"true"));
+        TestParser parser = new TestParser(new 
StubPropertiesFunction(Map.of(), Set.of()));
+
+        parser.onApplicationEvent(eventFor(environment));
+
+        assertEquals(1, parser.factoryCalls, "the client is built exactly once 
per event");
+        assertNotNull(environment.getPropertySources().get(OVERRIDE_SOURCE),
+                "the override source must be registered so later property 
resolution sees it");
+    }
+
+    @Test
+    public void resolvesWholeValuePlaceholders() {
+        RecordingEnvironment environment = environmentWith(Map.of(
+                GUARD, "true",
+                "my.secret", "{{test:database/password}}"));
+        TestParser parser = new TestParser(
+                new StubPropertiesFunction(Map.of("database/password", 
"pazzword"), Set.of()));
+
+        parser.onApplicationEvent(eventFor(environment));
+
+        assertEquals("pazzword", environment.getProperty("my.secret"),
+                "the resolved value must take effect, because the placeholder 
text is not a usable secret");
+    }
+
+    @Test
+    public void leavesNonMatchingValuesAlone() {
+        RecordingEnvironment environment = environmentWith(Map.of(
+                GUARD, "true",
+                "embedded.placeholder", "jdbc://{{test:host}}/db",
+                "foreign.prefix", "{{other:host}}",
+                "plain.value", "just-a-string"));
+        StubPropertiesFunction function = new 
StubPropertiesFunction(Map.of("host", "resolved-host"), Set.of());
+        TestParser parser = new TestParser(function);
+
+        parser.onApplicationEvent(eventFor(environment));
+
+        assertEquals(List.of(), function.applied,
+                "early resolution deliberately handles only values that are 
entirely one placeholder; "
+                        + "embedded placeholders belong to Camel's normal 
property parser");
+        assertEquals("jdbc://{{test:host}}/db", 
environment.getProperty("embedded.placeholder"));
+        assertEquals("{{other:host}}", 
environment.getProperty("foreign.prefix"));
+        assertEquals("just-a-string", environment.getProperty("plain.value"));
+    }
+
+    @Test
+    public void stripsOnlyTheLeadingPrefixAndTrailingDelimiters() {
+        RecordingEnvironment environment = environmentWith(Map.of(
+                GUARD, "true",
+                "my.secret", "{{test:a}}b}}"));
+        StubPropertiesFunction function = new 
StubPropertiesFunction(Map.of("a}}b", "resolved"), Set.of());
+        TestParser parser = new TestParser(function);
+
+        parser.onApplicationEvent(eventFor(environment));
+
+        assertEquals(List.of("a}}b"), function.applied,
+                "a global replace of the delimiters would corrupt a remainder 
that itself contains them");
+        assertEquals("resolved", environment.getProperty("my.secret"));
+    }
+
+    @Test
+    public void resolvesOriginTrackedValues() {
+        Map<String, Object> properties = new LinkedHashMap<>();
+        properties.put(GUARD, "true");
+        properties.put("my.secret", 
OriginTrackedValue.of("{{test:database/password}}"));
+        RecordingEnvironment environment = environmentWith(properties);
+        TestParser parser = new TestParser(
+                new StubPropertiesFunction(Map.of("database/password", 
"pazzword"), Set.of()));
+
+        parser.onApplicationEvent(eventFor(environment));
+
+        assertEquals("pazzword", environment.getProperty("my.secret"),
+                "values loaded from application.properties arrive wrapped in 
OriginTrackedValue");
+    }
+
+    @Test
+    public void guardDisabledDoesNotReadTheIgnoreFlag() {
+        RecordingEnvironment environment = environmentWith(Map.of(
+                "my.secret", "{{test:some/secret}}"));
+        TestParser parser = new TestParser(new 
StubPropertiesFunction(Map.of(), Set.of()));
+
+        parser.onApplicationEvent(eventFor(environment));
+
+        assertTrue(environment.queried.contains(GUARD),
+                "the guard property must be queried so that the ordering below 
is actually exercised");
+        assertFalse(environment.queried.contains(
+                
AbstractEarlyResolutionPropertiesParser.IGNORE_RESOLUTION_FAILURES),
+                "the opt-out flag is only meaningful once early resolution is 
enabled, so it must not be "
+                        + "evaluated before the guard");
+    }
+
+    @Test
+    public void failClosedReportsEveryFailingPropertyTogether() {
+        RecordingEnvironment environment = environmentWith(Map.of(
+                GUARD, "true",
+                "first.secret", "{{test:missing/one}}",
+                "second.secret", "{{test:missing/two}}"));
+        StubPropertiesFunction function = new StubPropertiesFunction(
+                Map.of(), Set.of("missing/one", "missing/two"));
+        TestParser parser = new TestParser(function);
+
+        RuntimeCamelException thrown = 
assertThrows(RuntimeCamelException.class,
+                () -> parser.onApplicationEvent(eventFor(environment)));
+
+        assertTrue(thrown.getMessage().contains("first.secret"),
+                "the failure must name every property that could not be 
resolved, was: " + thrown.getMessage());
+        assertTrue(thrown.getMessage().contains("second.secret"),
+                "the failure must name every property that could not be 
resolved, was: " + thrown.getMessage());
+        assertTrue(thrown.getMessage().contains(
+                
AbstractEarlyResolutionPropertiesParser.IGNORE_RESOLUTION_FAILURES),
+                "the failure must point at the opt-out property, was: " + 
thrown.getMessage());
+        assertEquals(2, thrown.getSuppressed().length,
+                "each individual failure is attached so its cause survives");
+        assertEquals(2, function.applied.size(),
+                "aborting on the first failure would hide the remaining broken 
placeholders from the operator");
+    }
+
+    @Test
+    public void nullResolutionResultIsTreatedAsAFailure() {
+        RecordingEnvironment environment = environmentWith(Map.of(
+                GUARD, "true",
+                "my.secret", "{{test:missing/subkey}}"));
+        TestParser parser = new TestParser(new 
StubPropertiesFunction(Map.of(), Set.of()));
+
+        RuntimeCamelException thrown = 
assertThrows(RuntimeCamelException.class,
+                () -> parser.onApplicationEvent(eventFor(environment)));
+
+        assertTrue(thrown.getMessage().contains("my.secret"),
+                "a null result is a resolution failure like any other, so it 
must name the property, was: "
+                        + thrown.getMessage());
+        assertNull(environment.getPropertySources().get(OVERRIDE_SOURCE),
+                "a null resolution result must abort startup rather than 
silently storing no value");
+    }
+
+    @Test
+    public void failClosedDoesNotRegisterAnOverrideSource() {
+        RecordingEnvironment environment = environmentWith(Map.of(
+                GUARD, "true",
+                "my.secret", "{{test:missing}}"));
+        TestParser parser = new TestParser(new 
StubPropertiesFunction(Map.of(), Set.of("missing")));
+
+        assertThrows(RuntimeCamelException.class, () -> 
parser.onApplicationEvent(eventFor(environment)));
+
+        assertNull(environment.getPropertySources().get(OVERRIDE_SOURCE),
+                "a partially populated override source must not be left behind 
after an aborted startup");
+    }
+
+    @Test
+    public void ignoreResolutionFailuresKeepsStartupGoing() {
+        RecordingEnvironment environment = environmentWith(Map.of(
+                GUARD, "true",
+                
AbstractEarlyResolutionPropertiesParser.IGNORE_RESOLUTION_FAILURES, "true",
+                "good.secret", "{{test:present}}",
+                "bad.secret", "{{test:missing}}"));
+        TestParser parser = new TestParser(
+                new StubPropertiesFunction(Map.of("present", "resolved"), 
Set.of("missing")));
+
+        assertDoesNotThrow(() -> 
parser.onApplicationEvent(eventFor(environment)));
+
+        assertEquals("resolved", environment.getProperty("good.secret"),
+                "a failure elsewhere must not discard the values that did 
resolve");
+        assertEquals("{{test:missing}}", environment.getProperty("bad.secret"),
+                "in tolerant mode the unresolved placeholder is deliberately 
left in place");
+    }
+}

Reply via email to