You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2021/03/05 14:05:30 UTC

[camel] 01/02: CAMEL-16302: camel-core - Optional property placeholders in properties component.

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

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

commit ace4998312724006ba00e79745147a5ffe2edcd3
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Fri Mar 5 14:39:24 2021 +0100

    CAMEL-16302: camel-core - Optional property placeholders in properties component.
---
 .../spi/BridgePropertyPlaceholderConfigurer.java   |  12 +-
 .../main/java/org/apache/camel/CamelContext.java   |   3 +
 .../org/apache/camel/ExtendedCamelContext.java     |  14 ++
 .../org/apache/camel/spi/PropertiesComponent.java  |  20 ++
 .../camel/impl/engine/AbstractCamelContext.java    |  18 +-
 .../properties/DefaultPropertiesParser.java        |  66 ++++--
 .../component/properties/PropertiesComponent.java  |  13 +-
 .../component/properties/PropertiesParser.java     |   4 +-
 .../impl/cloud/ServiceCallProcessorFactory.java    |   3 +-
 .../camel/impl/lw/LightweightCamelContext.java     |   5 +
 .../impl/lw/LightweightRuntimeCamelContext.java    |   7 +-
 .../camel/processor/SendDynamicProcessor.java      |   4 +-
 .../org/apache/camel/reifier/AggregateReifier.java |  17 +-
 .../apache/camel/reifier/ClaimCheckReifier.java    |   5 +-
 .../apache/camel/reifier/DynamicRouterReifier.java |   5 +-
 .../org/apache/camel/reifier/EnrichReifier.java    |   5 +-
 .../camel/reifier/IdempotentConsumerReifier.java   |   6 +-
 .../reifier/InterceptSendToEndpointReifier.java    |   5 +-
 .../org/apache/camel/reifier/MulticastReifier.java |   7 +-
 .../apache/camel/reifier/PollEnrichReifier.java    |  12 +-
 .../org/apache/camel/reifier/ProcessorReifier.java |   6 +-
 .../apache/camel/reifier/RecipientListReifier.java |  22 +-
 .../apache/camel/reifier/ResequenceReifier.java    |  25 ++-
 .../apache/camel/reifier/RoutingSlipReifier.java   |   5 +-
 .../java/org/apache/camel/reifier/SagaReifier.java |   5 +-
 .../org/apache/camel/reifier/SamplingReifier.java  |   7 +-
 .../java/org/apache/camel/reifier/SortReifier.java |   5 +-
 .../org/apache/camel/reifier/SplitReifier.java     |  13 +-
 .../org/apache/camel/reifier/ThreadsReifier.java   |  10 +-
 .../org/apache/camel/reifier/ThrottleReifier.java  |   2 +-
 .../camel/reifier/ThrowExceptionReifier.java       |   5 +-
 .../org/apache/camel/reifier/ToDynamicReifier.java |   8 +-
 .../org/apache/camel/reifier/WireTapReifier.java   |  10 +-
 .../reifier/dataformat/YAMLDataFormatReifier.java  |   7 +-
 .../loadbalancer/FailoverLoadBalancerReifier.java  |   5 +-
 .../transformer/CustomTransformeReifier.java       |   5 +-
 .../core/xml/AbstractCamelEndpointFactoryBean.java |   3 +-
 .../OptionalPropertyPlaceholderEipTest.java        |  84 ++++++++
 .../OptionalPropertyPlaceholderTest.java           | 228 +++++++++++++++++++++
 .../builder/endpoint/AbstractEndpointBuilder.java  |  19 +-
 .../endpoint/OptionalPropertyPlaceholderTest.java  | 121 +++++++++++
 .../org/apache/camel/support/EndpointHelper.java   |  92 ++++++++-
 .../component/EndpointUriFactorySupport.java       |  39 +---
 .../java/org/apache/camel/util/StringHelper.java   |  20 +-
 .../java/org/apache/camel/util/URISupport.java     |  14 ++
 45 files changed, 824 insertions(+), 167 deletions(-)

diff --git a/components/camel-spring/src/main/java/org/apache/camel/spring/spi/BridgePropertyPlaceholderConfigurer.java b/components/camel-spring/src/main/java/org/apache/camel/spring/spi/BridgePropertyPlaceholderConfigurer.java
index 2317cb3..e97c81b 100644
--- a/components/camel-spring/src/main/java/org/apache/camel/spring/spi/BridgePropertyPlaceholderConfigurer.java
+++ b/components/camel-spring/src/main/java/org/apache/camel/spring/spi/BridgePropertyPlaceholderConfigurer.java
@@ -83,9 +83,10 @@ public class BridgePropertyPlaceholderConfigurer extends PropertyPlaceholderConf
     }
 
     @Override
-    public String parseUri(String text, PropertiesLookup properties, boolean fallback) throws IllegalArgumentException {
+    public String parseUri(String text, PropertiesLookup properties, boolean fallback, boolean keepUnresolvedOptional)
+            throws IllegalArgumentException {
         // first let Camel parse the text as it may contain Camel placeholders
-        String answer = parser.parseUri(text, properties, fallback);
+        String answer = parser.parseUri(text, properties, fallback, keepUnresolvedOptional);
 
         // then let Spring parse it to resolve any Spring placeholders
         if (answer != null) {
@@ -172,15 +173,16 @@ public class BridgePropertyPlaceholderConfigurer extends PropertyPlaceholderConf
         }
 
         @Override
-        public String parseUri(String text, PropertiesLookup properties, boolean fallback) throws IllegalArgumentException {
+        public String parseUri(String text, PropertiesLookup properties, boolean fallback, boolean keepUnresolvedOptional)
+                throws IllegalArgumentException {
             String answer = null;
             if (delegate != null) {
-                answer = delegate.parseUri(text, properties, fallback);
+                answer = delegate.parseUri(text, properties, fallback, keepUnresolvedOptional);
             }
             if (answer != null) {
                 text = answer;
             }
-            return parser.parseUri(text, properties, fallback);
+            return parser.parseUri(text, properties, fallback, keepUnresolvedOptional);
         }
 
         @Override
diff --git a/core/camel-api/src/main/java/org/apache/camel/CamelContext.java b/core/camel-api/src/main/java/org/apache/camel/CamelContext.java
index 44b31ed..bbc1863 100644
--- a/core/camel-api/src/main/java/org/apache/camel/CamelContext.java
+++ b/core/camel-api/src/main/java/org/apache/camel/CamelContext.java
@@ -725,6 +725,9 @@ public interface CamelContext extends CamelContextLifecycle, RuntimeConfiguratio
 
     /**
      * Parses the given text and resolve any property placeholders - using {{key}}.
+     * <p/>
+     * <b>Important:</b> If resolving placeholders on an endpoint uri, then you SHOULD use
+     * EndpointHelper#resolveEndpointUriPropertyPlaceholders instead.
      *
      * @param  text                     the text such as an endpoint uri or the likes
      * @return                          the text with resolved property placeholders
diff --git a/core/camel-api/src/main/java/org/apache/camel/ExtendedCamelContext.java b/core/camel-api/src/main/java/org/apache/camel/ExtendedCamelContext.java
index 114eeab..654cc52 100644
--- a/core/camel-api/src/main/java/org/apache/camel/ExtendedCamelContext.java
+++ b/core/camel-api/src/main/java/org/apache/camel/ExtendedCamelContext.java
@@ -769,4 +769,18 @@ public interface ExtendedCamelContext extends CamelContext {
      */
     String getTestExcludeRoutes();
 
+    /**
+     * Parses the given text and resolve any property placeholders - using {{key}}.
+     * <p/>
+     * <b>Important:</b> If resolving placeholders on an endpoint uri, then you SHOULD use
+     * EndpointHelper#resolveEndpointUriPropertyPlaceholders instead.
+     *
+     * @param  text                     the text such as an endpoint uri or the likes
+     * @param  keepUnresolvedOptional   whether to keep placeholders that are optional and was unresolved
+     * @return                          the text with resolved property placeholders
+     * @throws IllegalArgumentException is thrown if property placeholders was used and there was an error resolving
+     *                                  them
+     */
+    String resolvePropertyPlaceholders(String text, boolean keepUnresolvedOptional);
+
 }
diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/PropertiesComponent.java b/core/camel-api/src/main/java/org/apache/camel/spi/PropertiesComponent.java
index 83e2fd9..80dd27e 100644
--- a/core/camel-api/src/main/java/org/apache/camel/spi/PropertiesComponent.java
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/PropertiesComponent.java
@@ -46,6 +46,16 @@ public interface PropertiesComponent extends StaticService {
     String SUFFIX_TOKEN = "}}";
 
     /**
+     * The token for marking a placeholder as optional
+     */
+    String OPTIONAL_TOKEN = "?";
+
+    /**
+     * The prefix and optional tokens
+     */
+    String PREFIX_OPTIONAL_TOKEN = PREFIX_TOKEN + OPTIONAL_TOKEN;
+
+    /**
      * Parses the input text and resolve all property placeholders from within the text.
      *
      * @param  uri                      input text
@@ -55,6 +65,16 @@ public interface PropertiesComponent extends StaticService {
     String parseUri(String uri);
 
     /**
+     * Parses the input text and resolve all property placeholders from within the text.
+     *
+     * @param  uri                      input text
+     * @param  keepUnresolvedOptional   whether to keep placeholders that are optional and was unresolved
+     * @return                          text with resolved property placeholders
+     * @throws IllegalArgumentException is thrown if error during parsing
+     */
+    String parseUri(String uri, boolean keepUnresolvedOptional);
+
+    /**
      * Looks up the property with the given key
      *
      * @param  key the name of the property
diff --git a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java
index 45a1a92..3d2d334 100644
--- a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java
+++ b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java
@@ -818,7 +818,7 @@ public abstract class AbstractCamelContext extends BaseService
     @Override
     public NormalizedEndpointUri normalizeUri(String uri) {
         try {
-            uri = resolvePropertyPlaceholders(uri);
+            uri = EndpointHelper.resolveEndpointUriPropertyPlaceholders(this, uri);
             uri = URISupport.normalizeUri(uri);
             return new NormalizedUri(uri);
         } catch (Exception e) {
@@ -874,14 +874,9 @@ public abstract class AbstractCamelContext extends BaseService
 
         LOG.trace("Getting endpoint with uri: {} and parameters: {}", uri, parameters);
 
-        // in case path has property placeholders then try to let property
-        // component resolve those
+        // in case path has property placeholders then try to let property component resolve those
         if (!normalized) {
-            try {
-                uri = resolvePropertyPlaceholders(uri);
-            } catch (Exception e) {
-                throw new ResolveEndpointFailedException(uri, e);
-            }
+            uri = EndpointHelper.resolveEndpointUriPropertyPlaceholders(this, uri);
         }
 
         final String rawUri = uri;
@@ -1767,9 +1762,14 @@ public abstract class AbstractCamelContext extends BaseService
 
     @Override
     public String resolvePropertyPlaceholders(String text) {
+        return resolvePropertyPlaceholders(text, false);
+    }
+
+    @Override
+    public String resolvePropertyPlaceholders(String text, boolean keepUnresolvedOptional) {
         if (text != null && text.contains(PropertiesComponent.PREFIX_TOKEN)) {
             // the parser will throw exception if property key was not found
-            String answer = getPropertiesComponent().parseUri(text);
+            String answer = getPropertiesComponent().parseUri(text, keepUnresolvedOptional);
             LOG.debug("Resolved text: {} -> {}", text, answer);
             return answer;
         }
diff --git a/core/camel-base/src/main/java/org/apache/camel/component/properties/DefaultPropertiesParser.java b/core/camel-base/src/main/java/org/apache/camel/component/properties/DefaultPropertiesParser.java
index e83b20a..9d13f56 100644
--- a/core/camel-base/src/main/java/org/apache/camel/component/properties/DefaultPropertiesParser.java
+++ b/core/camel-base/src/main/java/org/apache/camel/component/properties/DefaultPropertiesParser.java
@@ -25,6 +25,7 @@ import org.apache.camel.util.StringHelper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import static org.apache.camel.spi.PropertiesComponent.OPTIONAL_TOKEN;
 import static org.apache.camel.spi.PropertiesComponent.PREFIX_TOKEN;
 import static org.apache.camel.spi.PropertiesComponent.SUFFIX_TOKEN;
 import static org.apache.camel.util.IOHelper.lookupEnvironmentVariable;
@@ -33,6 +34,8 @@ import static org.apache.camel.util.IOHelper.lookupEnvironmentVariable;
  * A parser to parse a string which contains property placeholders.
  */
 public class DefaultPropertiesParser implements PropertiesParser {
+    private static final String UNRESOLVED_PREFIX_TOKEN = "@@[";
+    private static final String UNRESOLVED_SUFFIX_TOKEN = "]@@";
     private static final String GET_OR_ELSE_TOKEN = ":";
 
     protected final Logger log = LoggerFactory.getLogger(getClass());
@@ -55,10 +58,17 @@ public class DefaultPropertiesParser implements PropertiesParser {
     }
 
     @Override
-    public String parseUri(String text, PropertiesLookup properties, boolean defaultFallbackEnabled)
+    public String parseUri(
+            String text, PropertiesLookup properties, boolean defaultFallbackEnabled, boolean keepUnresolvedOptional)
             throws IllegalArgumentException {
-        ParsingContext context = new ParsingContext(properties, defaultFallbackEnabled);
-        return context.parse(text);
+        ParsingContext context = new ParsingContext(properties, defaultFallbackEnabled, keepUnresolvedOptional);
+        String answer = context.parse(text);
+        if (keepUnresolvedOptional && answer != null && answer.contains(UNRESOLVED_PREFIX_TOKEN)) {
+            // replace temporary unresolved keys back to with placeholders so they are kept as-is
+            answer = StringHelper.replaceAll(answer, UNRESOLVED_PREFIX_TOKEN, PREFIX_TOKEN);
+            answer = StringHelper.replaceAll(answer, UNRESOLVED_SUFFIX_TOKEN, SUFFIX_TOKEN);
+        }
+        return answer;
     }
 
     @Override
@@ -72,10 +82,12 @@ public class DefaultPropertiesParser implements PropertiesParser {
     private final class ParsingContext {
         private final PropertiesLookup properties;
         private final boolean defaultFallbackEnabled;
+        private final boolean keepUnresolvedOptional;
 
-        ParsingContext(PropertiesLookup properties, boolean defaultFallbackEnabled) {
+        ParsingContext(PropertiesLookup properties, boolean defaultFallbackEnabled, boolean keepUnresolvedOptional) {
             this.properties = properties;
             this.defaultFallbackEnabled = defaultFallbackEnabled;
+            this.keepUnresolvedOptional = keepUnresolvedOptional;
         }
 
         /**
@@ -102,10 +114,15 @@ public class DefaultPropertiesParser implements PropertiesParser {
             String answer = input;
             Property property;
             while ((property = readProperty(answer)) != null) {
-                // Check for circular references
                 if (replacedPropertyKeys.contains(property.getKey())) {
-                    throw new IllegalArgumentException(
-                            "Circular reference detected with key [" + property.getKey() + "] from text: " + input);
+                    // Check for circular references (skip optional)
+                    boolean optional = property.getKey().startsWith(OPTIONAL_TOKEN);
+                    if (optional) {
+                        break;
+                    } else {
+                        throw new IllegalArgumentException(
+                                "Circular reference detected with key [" + property.getKey() + "] from text: " + input);
+                    }
                 }
 
                 Set<String> newReplaced = new HashSet<>(replacedPropertyKeys);
@@ -113,7 +130,18 @@ public class DefaultPropertiesParser implements PropertiesParser {
 
                 String before = answer.substring(0, property.getBeginIndex());
                 String after = answer.substring(property.getEndIndex());
-                answer = before + doParse(property.getValue(), newReplaced) + after;
+                String parsed = doParse(property.getValue(), newReplaced);
+                if (parsed != null) {
+                    answer = before + parsed + after;
+                } else {
+                    if (property.getBeginIndex() == 0 && input.length() == property.getEndIndex()) {
+                        // its only a single placeholder which is parsed as null
+                        answer = null;
+                        break;
+                    } else {
+                        answer = before + after;
+                    }
+                }
             }
             return answer;
         }
@@ -237,6 +265,11 @@ public class DefaultPropertiesParser implements PropertiesParser {
                 key = StringHelper.before(key, GET_OR_ELSE_TOKEN);
             }
 
+            boolean optional = key != null && key.startsWith(OPTIONAL_TOKEN);
+            if (optional) {
+                key = key.substring(OPTIONAL_TOKEN.length());
+            }
+
             String value = doGetPropertyValue(key);
             if (value == null && defaultValue != null) {
                 log.debug("Property with key [{}] not found, using default value: {}", key, defaultValue);
@@ -244,10 +277,19 @@ public class DefaultPropertiesParser implements PropertiesParser {
             }
 
             if (value == null) {
-                StringBuilder esb = new StringBuilder();
-                esb.append("Property with key [").append(key).append("] ");
-                esb.append("not found in properties from text: ").append(input);
-                throw new IllegalArgumentException(esb.toString());
+                if (!optional) {
+                    StringBuilder esb = new StringBuilder();
+                    esb.append("Property with key [").append(key).append("] ");
+                    esb.append("not found in properties from text: ").append(input);
+                    throw new IllegalArgumentException(esb.toString());
+                } else {
+                    if (keepUnresolvedOptional) {
+                        // mark the key as unresolved
+                        return UNRESOLVED_PREFIX_TOKEN + OPTIONAL_TOKEN + key + UNRESOLVED_SUFFIX_TOKEN;
+                    } else {
+                        return null;
+                    }
+                }
             }
 
             return value;
diff --git a/core/camel-base/src/main/java/org/apache/camel/component/properties/PropertiesComponent.java b/core/camel-base/src/main/java/org/apache/camel/component/properties/PropertiesComponent.java
index 52c5e29..0bb2868 100644
--- a/core/camel-base/src/main/java/org/apache/camel/component/properties/PropertiesComponent.java
+++ b/core/camel-base/src/main/java/org/apache/camel/component/properties/PropertiesComponent.java
@@ -152,13 +152,18 @@ public class PropertiesComponent extends ServiceSupport
 
     @Override
     public String parseUri(String uri) {
-        return parseUri(uri, propertiesLookup);
+        return parseUri(uri, false);
+    }
+
+    @Override
+    public String parseUri(String uri, boolean keepUnresolvedOptional) {
+        return parseUri(uri, propertiesLookup, keepUnresolvedOptional);
     }
 
     @Override
     public Optional<String> resolveProperty(String key) {
         try {
-            String value = parseUri(key, propertiesLookup);
+            String value = parseUri(key, propertiesLookup, false);
             return Optional.of(value);
         } catch (IllegalArgumentException e) {
             // property not found
@@ -243,7 +248,7 @@ public class PropertiesComponent extends ServiceSupport
         return prop;
     }
 
-    protected String parseUri(String uri, PropertiesLookup properties) {
+    protected String parseUri(String uri, PropertiesLookup properties, boolean keepUnresolvedOptional) {
         // enclose tokens if missing
         if (!uri.contains(PREFIX_TOKEN) && !uri.startsWith(PREFIX_TOKEN)) {
             uri = PREFIX_TOKEN + uri;
@@ -253,7 +258,7 @@ public class PropertiesComponent extends ServiceSupport
         }
 
         LOG.trace("Parsing uri {}", uri);
-        return propertiesParser.parseUri(uri, properties, defaultFallbackEnabled);
+        return propertiesParser.parseUri(uri, properties, defaultFallbackEnabled, keepUnresolvedOptional);
     }
 
     @Override
diff --git a/core/camel-base/src/main/java/org/apache/camel/component/properties/PropertiesParser.java b/core/camel-base/src/main/java/org/apache/camel/component/properties/PropertiesParser.java
index 6a558da..d251640 100644
--- a/core/camel-base/src/main/java/org/apache/camel/component/properties/PropertiesParser.java
+++ b/core/camel-base/src/main/java/org/apache/camel/component/properties/PropertiesParser.java
@@ -27,10 +27,12 @@ public interface PropertiesParser {
      * @param  text                     the text to be parsed
      * @param  properties               the properties resolved which values should be looked up
      * @param  fallback                 whether to support using fallback values if a property cannot be found
+     * @param  keepUnresolvedOptional   whether to keep placeholders that are optional and was unresolved
      * @return                          the parsed text with replaced placeholders
      * @throws IllegalArgumentException if uri syntax is not valid or a property is not found
      */
-    String parseUri(String text, PropertiesLookup properties, boolean fallback) throws IllegalArgumentException;
+    String parseUri(String text, PropertiesLookup properties, boolean fallback, boolean keepUnresolvedOptional)
+            throws IllegalArgumentException;
 
     /**
      * While parsing the uri using parseUri method each parsed property found invokes this callback.
diff --git a/core/camel-cloud/src/main/java/org/apache/camel/impl/cloud/ServiceCallProcessorFactory.java b/core/camel-cloud/src/main/java/org/apache/camel/impl/cloud/ServiceCallProcessorFactory.java
index 0124765..9227576 100644
--- a/core/camel-cloud/src/main/java/org/apache/camel/impl/cloud/ServiceCallProcessorFactory.java
+++ b/core/camel-cloud/src/main/java/org/apache/camel/impl/cloud/ServiceCallProcessorFactory.java
@@ -41,6 +41,7 @@ import org.apache.camel.model.cloud.ServiceCallConfigurationDefinition;
 import org.apache.camel.model.cloud.ServiceCallDefinition;
 import org.apache.camel.model.cloud.ServiceCallDefinitionConstants;
 import org.apache.camel.support.CamelContextHelper;
+import org.apache.camel.support.EndpointHelper;
 import org.apache.camel.support.TypedProcessorFactory;
 import org.apache.camel.util.ObjectHelper;
 import org.apache.camel.util.function.Suppliers;
@@ -126,7 +127,7 @@ public class ServiceCallProcessorFactory extends TypedProcessorFactory<ServiceCa
 
         endpointScheme = ThrowingHelper.applyIfNotEmpty(endpointScheme, camelContext::resolvePropertyPlaceholders,
                 () -> ServiceCallDefinitionConstants.DEFAULT_COMPONENT);
-        endpointUri = ThrowingHelper.applyIfNotEmpty(endpointUri, camelContext::resolvePropertyPlaceholders, () -> null);
+        endpointUri = EndpointHelper.resolveEndpointUriPropertyPlaceholders(camelContext, endpointUri);
         ExchangePattern pattern = CamelContextHelper.parse(camelContext, ExchangePattern.class, definition.getPattern());
 
         Expression expression = retrieveExpression(camelContext, endpointScheme);
diff --git a/core/camel-core-engine/src/main/java/org/apache/camel/impl/lw/LightweightCamelContext.java b/core/camel-core-engine/src/main/java/org/apache/camel/impl/lw/LightweightCamelContext.java
index 16f4521..6101ee5 100644
--- a/core/camel-core-engine/src/main/java/org/apache/camel/impl/lw/LightweightCamelContext.java
+++ b/core/camel-core-engine/src/main/java/org/apache/camel/impl/lw/LightweightCamelContext.java
@@ -640,6 +640,11 @@ public class LightweightCamelContext implements ExtendedCamelContext, CatalogCam
     }
 
     @Override
+    public String resolvePropertyPlaceholders(String text, boolean keepUnresolvedOptional) {
+        return getExtendedCamelContext().resolvePropertyPlaceholders(text, keepUnresolvedOptional);
+    }
+
+    @Override
     public PropertiesComponent getPropertiesComponent() {
         return delegate.getPropertiesComponent();
     }
diff --git a/core/camel-core-engine/src/main/java/org/apache/camel/impl/lw/LightweightRuntimeCamelContext.java b/core/camel-core-engine/src/main/java/org/apache/camel/impl/lw/LightweightRuntimeCamelContext.java
index afed430..74af3ab 100644
--- a/core/camel-core-engine/src/main/java/org/apache/camel/impl/lw/LightweightRuntimeCamelContext.java
+++ b/core/camel-core-engine/src/main/java/org/apache/camel/impl/lw/LightweightRuntimeCamelContext.java
@@ -914,9 +914,14 @@ public class LightweightRuntimeCamelContext implements ExtendedCamelContext, Cat
 
     @Override
     public String resolvePropertyPlaceholders(String text) {
+        return resolvePropertyPlaceholders(text, false);
+    }
+
+    @Override
+    public String resolvePropertyPlaceholders(String text, boolean keepUnresolvedOptional) {
         if (text != null && text.contains(PropertiesComponent.PREFIX_TOKEN)) {
             // the parser will throw exception if property key was not found
-            return getPropertiesComponent().parseUri(text);
+            return getPropertiesComponent().parseUri(text, keepUnresolvedOptional);
         }
         // is the value a known field (currently we only support
         // constants from Exchange.class)
diff --git a/core/camel-core-processor/src/main/java/org/apache/camel/processor/SendDynamicProcessor.java b/core/camel-core-processor/src/main/java/org/apache/camel/processor/SendDynamicProcessor.java
index 9f26dee..76cdc79 100644
--- a/core/camel-core-processor/src/main/java/org/apache/camel/processor/SendDynamicProcessor.java
+++ b/core/camel-core-processor/src/main/java/org/apache/camel/processor/SendDynamicProcessor.java
@@ -233,7 +233,7 @@ public class SendDynamicProcessor extends AsyncProcessorSupport implements IdAwa
 
         // in case path has property placeholders then try to let property component resolve those
         try {
-            uri = exchange.getContext().resolvePropertyPlaceholders(uri);
+            uri = EndpointHelper.resolveEndpointUriPropertyPlaceholders(exchange.getContext(), uri);
         } catch (Exception e) {
             throw new ResolveEndpointFailedException(uri, e);
         }
@@ -310,7 +310,7 @@ public class SendDynamicProcessor extends AsyncProcessorSupport implements IdAwa
 
         if ((isAllowOptimisedComponents() || isAutoStartupComponents()) && uri != null) {
             // in case path has property placeholders then try to let property component resolve those
-            String u = camelContext.resolvePropertyPlaceholders(uri);
+            String u = EndpointHelper.resolveEndpointUriPropertyPlaceholders(camelContext, uri);
             // find out which component it is
             scheme = ExchangeHelper.resolveScheme(u);
         }
diff --git a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/AggregateReifier.java b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/AggregateReifier.java
index a01b4de..97c29cc 100644
--- a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/AggregateReifier.java
+++ b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/AggregateReifier.java
@@ -207,17 +207,20 @@ public class AggregateReifier extends ProcessorReifier<AggregateDefinition> {
 
     public OptimisticLockRetryPolicy createOptimisticLockRetryPolicy(OptimisticLockRetryPolicyDefinition definition) {
         OptimisticLockRetryPolicy policy = new OptimisticLockRetryPolicy();
-        if (definition.getMaximumRetries() != null) {
-            policy.setMaximumRetries(parseInt(definition.getMaximumRetries()));
+        Integer num = parseInt(definition.getMaximumRetries());
+        if (num != null) {
+            policy.setMaximumRetries(num);
         }
-        if (definition.getRetryDelay() != null) {
-            policy.setRetryDelay(parseDuration(definition.getRetryDelay()));
+        Long dur = parseDuration(definition.getRetryDelay());
+        if (dur != null) {
+            policy.setRetryDelay(dur);
         }
-        if (definition.getMaximumRetryDelay() != null) {
-            policy.setMaximumRetryDelay(parseDuration(definition.getMaximumRetryDelay()));
+        dur = parseDuration(definition.getMaximumRetryDelay());
+        if (dur != null) {
+            policy.setMaximumRetryDelay(dur);
         }
         if (definition.getExponentialBackOff() != null) {
-            policy.setExponentialBackOff(parseBoolean(definition.getExponentialBackOff(), false));
+            policy.setExponentialBackOff(parseBoolean(definition.getExponentialBackOff(), true));
         }
         if (definition.getRandomBackOff() != null) {
             policy.setRandomBackOff(parseBoolean(definition.getRandomBackOff(), false));
diff --git a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/ClaimCheckReifier.java b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/ClaimCheckReifier.java
index 47b5a14..b6a4ce8 100644
--- a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/ClaimCheckReifier.java
+++ b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/ClaimCheckReifier.java
@@ -109,8 +109,9 @@ public class ClaimCheckReifier extends ProcessorReifier<ClaimCheckDefinition> {
 
     private AggregationStrategy createAggregationStrategy() {
         AggregationStrategy strategy = definition.getAggregationStrategy();
-        if (strategy == null && definition.getAggregationStrategyRef() != null) {
-            Object aggStrategy = lookup(parseString(definition.getAggregationStrategyRef()), Object.class);
+        String ref = parseString(definition.getAggregationStrategyRef());
+        if (strategy == null && ref != null) {
+            Object aggStrategy = lookup(ref, Object.class);
             if (aggStrategy instanceof AggregationStrategy) {
                 strategy = (AggregationStrategy) aggStrategy;
             } else if (aggStrategy != null) {
diff --git a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/DynamicRouterReifier.java b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/DynamicRouterReifier.java
index bc760e0..c4e5169 100644
--- a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/DynamicRouterReifier.java
+++ b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/DynamicRouterReifier.java
@@ -40,8 +40,9 @@ public class DynamicRouterReifier extends ExpressionReifier<DynamicRouterDefinit
         if (definition.getIgnoreInvalidEndpoints() != null) {
             dynamicRouter.setIgnoreInvalidEndpoints(parseBoolean(definition.getIgnoreInvalidEndpoints(), false));
         }
-        if (definition.getCacheSize() != null) {
-            dynamicRouter.setCacheSize(parseInt(definition.getCacheSize()));
+        Integer num = parseInt(definition.getCacheSize());
+        if (num != null) {
+            dynamicRouter.setCacheSize(num);
         }
 
         AsyncProcessor errorHandler
diff --git a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/EnrichReifier.java b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/EnrichReifier.java
index 2367851..ffe0387 100644
--- a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/EnrichReifier.java
+++ b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/EnrichReifier.java
@@ -41,8 +41,9 @@ public class EnrichReifier extends ExpressionReifier<EnrichDefinition> {
         Enricher enricher = new Enricher(exp);
         enricher.setShareUnitOfWork(isShareUnitOfWork);
         enricher.setIgnoreInvalidEndpoint(isIgnoreInvalidEndpoint);
-        if (definition.getCacheSize() != null) {
-            enricher.setCacheSize(parseInt(definition.getCacheSize()));
+        Integer num = parseInt(definition.getCacheSize());
+        if (num != null) {
+            enricher.setCacheSize(num);
         }
         AggregationStrategy strategy = createAggregationStrategy();
         if (strategy != null) {
diff --git a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/IdempotentConsumerReifier.java b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/IdempotentConsumerReifier.java
index 172bf3b..8e743b8 100644
--- a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/IdempotentConsumerReifier.java
+++ b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/IdempotentConsumerReifier.java
@@ -58,9 +58,9 @@ public class IdempotentConsumerReifier extends ExpressionReifier<IdempotentConsu
      * @return the repository
      */
     protected <T> IdempotentRepository resolveMessageIdRepository() {
-        if (definition.getMessageIdRepositoryRef() != null) {
-            definition.setMessageIdRepository(
-                    mandatoryLookup(parseString(definition.getMessageIdRepositoryRef()), IdempotentRepository.class));
+        String ref = parseString(definition.getMessageIdRepositoryRef());
+        if (ref != null) {
+            definition.setMessageIdRepository(mandatoryLookup(ref, IdempotentRepository.class));
         }
         return definition.getMessageIdRepository();
     }
diff --git a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/InterceptSendToEndpointReifier.java b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/InterceptSendToEndpointReifier.java
index 15897c8..0fe838d 100644
--- a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/InterceptSendToEndpointReifier.java
+++ b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/InterceptSendToEndpointReifier.java
@@ -44,8 +44,9 @@ public class InterceptSendToEndpointReifier extends ProcessorReifier<InterceptSe
         final Processor before = this.createChildProcessor(true);
         // create the after
         Processor afterProcessor = null;
-        if (definition.getAfterUri() != null) {
-            ToDefinition to = new ToDefinition(parseString(definition.getAfterUri()));
+        String afterUri = parseString(definition.getAfterUri());
+        if (afterUri != null) {
+            ToDefinition to = new ToDefinition(afterUri);
             // at first use custom factory
             afterProcessor = camelContext.adapt(ExtendedCamelContext.class).getProcessorFactory().createProcessor(route, to);
             // fallback to default implementation if factory did not create the processor
diff --git a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/MulticastReifier.java b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/MulticastReifier.java
index 2f1445b..a7ff33d 100644
--- a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/MulticastReifier.java
+++ b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/MulticastReifier.java
@@ -65,7 +65,7 @@ public class MulticastReifier extends ProcessorReifier<MulticastDefinition> {
         boolean shutdownThreadPool = willCreateNewThreadPool(definition, isParallelProcessing);
         ExecutorService threadPool = getConfiguredExecutorService("Multicast", definition, isParallelProcessing);
 
-        long timeout = definition.getTimeout() != null ? parseDuration(definition.getTimeout()) : 0;
+        long timeout = parseDuration(definition.getTimeout(), 0);
         if (timeout > 0 && !isParallelProcessing) {
             throw new IllegalArgumentException("Timeout is used but ParallelProcessing has not been enabled.");
         }
@@ -82,8 +82,9 @@ public class MulticastReifier extends ProcessorReifier<MulticastDefinition> {
 
     private AggregationStrategy createAggregationStrategy() {
         AggregationStrategy strategy = definition.getAggregationStrategy();
-        if (strategy == null && definition.getStrategyRef() != null) {
-            Object aggStrategy = lookup(parseString(definition.getStrategyRef()), Object.class);
+        String ref = parseString(definition.getStrategyRef());
+        if (strategy == null && ref != null) {
+            Object aggStrategy = lookup(ref, Object.class);
             if (aggStrategy instanceof AggregationStrategy) {
                 strategy = (AggregationStrategy) aggStrategy;
             } else if (aggStrategy != null) {
diff --git a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/PollEnrichReifier.java b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/PollEnrichReifier.java
index c4de005..06b18af 100644
--- a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/PollEnrichReifier.java
+++ b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/PollEnrichReifier.java
@@ -39,7 +39,7 @@ public class PollEnrichReifier extends ProcessorReifier<PollEnrichDefinition> {
     public Processor createProcessor() throws Exception {
 
         // if no timeout then we should block, and there use a negative timeout
-        long time = definition.getTimeout() != null ? parseDuration(definition.getTimeout()) : -1;
+        long time = parseDuration(definition.getTimeout(), -1);
         boolean isIgnoreInvalidEndpoint = parseBoolean(definition.getIgnoreInvalidEndpoint(), false);
 
         PollEnricher enricher;
@@ -62,8 +62,9 @@ public class PollEnrichReifier extends ProcessorReifier<PollEnrichDefinition> {
         if (definition.getAggregateOnException() != null) {
             enricher.setAggregateOnException(parseBoolean(definition.getAggregateOnException(), false));
         }
-        if (definition.getCacheSize() != null) {
-            enricher.setCacheSize(parseInt(definition.getCacheSize()));
+        Integer num = parseInt(definition.getCacheSize());
+        if (num != null) {
+            enricher.setCacheSize(num);
         }
         enricher.setIgnoreInvalidEndpoint(isIgnoreInvalidEndpoint);
 
@@ -72,8 +73,9 @@ public class PollEnrichReifier extends ProcessorReifier<PollEnrichDefinition> {
 
     private AggregationStrategy createAggregationStrategy() {
         AggregationStrategy strategy = definition.getAggregationStrategy();
-        if (strategy == null && definition.getAggregationStrategyRef() != null) {
-            Object aggStrategy = lookup(parseString(definition.getAggregationStrategyRef()), Object.class);
+        String ref = parseString(definition.getAggregationStrategyRef());
+        if (strategy == null && ref != null) {
+            Object aggStrategy = lookup(ref, Object.class);
             if (aggStrategy instanceof AggregationStrategy) {
                 strategy = (AggregationStrategy) aggStrategy;
             } else if (aggStrategy != null) {
diff --git a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/ProcessorReifier.java b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/ProcessorReifier.java
index 5805116..0c95252 100644
--- a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/ProcessorReifier.java
+++ b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/ProcessorReifier.java
@@ -362,12 +362,12 @@ public abstract class ProcessorReifier<T extends ProcessorDefinition<?>> extends
         ObjectHelper.notNull(manager, "ExecutorServiceManager", camelContext);
 
         // prefer to use explicit configured executor on the definition
+        String ref = parseString(definition.getExecutorServiceRef());
         if (definition.getExecutorService() != null) {
             return definition.getExecutorService();
-        } else if (definition.getExecutorServiceRef() != null) {
+        } else if (ref != null) {
             // lookup in registry first and use existing thread pool if exists
-            ExecutorService answer
-                    = lookupExecutorServiceRef(name, definition, parseString(definition.getExecutorServiceRef()));
+            ExecutorService answer = lookupExecutorServiceRef(name, definition, ref);
             if (answer == null) {
                 throw new IllegalArgumentException(
                         "ExecutorServiceRef " + definition.getExecutorServiceRef()
diff --git a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/RecipientListReifier.java b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/RecipientListReifier.java
index 3d44191..7d8c1a7 100644
--- a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/RecipientListReifier.java
+++ b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/RecipientListReifier.java
@@ -52,8 +52,9 @@ public class RecipientListReifier extends ProcessorReifier<RecipientListDefiniti
         boolean isStopOnAggregateException = parseBoolean(definition.getStopOnAggregateException(), false);
 
         RecipientList answer;
-        if (definition.getDelimiter() != null) {
-            answer = new RecipientList(camelContext, expression, parseString(definition.getDelimiter()));
+        String delimiter = parseString(definition.getDelimiter());
+        if (delimiter != null) {
+            answer = new RecipientList(camelContext, expression, delimiter);
         } else {
             answer = new RecipientList(camelContext, expression);
         }
@@ -65,8 +66,9 @@ public class RecipientListReifier extends ProcessorReifier<RecipientListDefiniti
         answer.setStopOnException(isStopOnException);
         answer.setIgnoreInvalidEndpoints(isIgnoreInvalidEndpoints);
         answer.setStopOnAggregateException(isStopOnAggregateException);
-        if (definition.getCacheSize() != null) {
-            answer.setCacheSize(parseInt(definition.getCacheSize()));
+        Integer num = parseInt(definition.getCacheSize());
+        if (num != null) {
+            answer.setCacheSize(num);
         }
         if (definition.getOnPrepareRef() != null) {
             definition.setOnPrepare(mandatoryLookup(definition.getOnPrepareRef(), Processor.class));
@@ -74,15 +76,16 @@ public class RecipientListReifier extends ProcessorReifier<RecipientListDefiniti
         if (definition.getOnPrepare() != null) {
             answer.setOnPrepare(definition.getOnPrepare());
         }
-        if (definition.getTimeout() != null) {
-            answer.setTimeout(parseDuration(definition.getTimeout()));
+        Long dur = parseDuration(definition.getTimeout());
+        if (dur != null) {
+            answer.setTimeout(dur);
         }
 
         boolean shutdownThreadPool = willCreateNewThreadPool(definition, isParallelProcessing);
         ExecutorService threadPool = getConfiguredExecutorService("RecipientList", definition, isParallelProcessing);
         answer.setExecutorService(threadPool);
         answer.setShutdownExecutorService(shutdownThreadPool);
-        long timeout = definition.getTimeout() != null ? parseDuration(definition.getTimeout()) : 0;
+        long timeout = parseDuration(definition.getTimeout(), 0);
         if (timeout > 0 && !isParallelProcessing) {
             throw new IllegalArgumentException("Timeout is used but ParallelProcessing has not been enabled.");
         }
@@ -110,8 +113,9 @@ public class RecipientListReifier extends ProcessorReifier<RecipientListDefiniti
 
     private AggregationStrategy createAggregationStrategy() {
         AggregationStrategy strategy = definition.getAggregationStrategy();
-        if (strategy == null && definition.getStrategyRef() != null) {
-            Object aggStrategy = lookup(parseString(definition.getStrategyRef()), Object.class);
+        String ref = parseString(definition.getStrategyRef());
+        if (strategy == null && ref != null) {
+            Object aggStrategy = lookup(ref, Object.class);
             if (aggStrategy instanceof AggregationStrategy) {
                 strategy = (AggregationStrategy) aggStrategy;
             } else if (aggStrategy != null) {
diff --git a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/ResequenceReifier.java b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/ResequenceReifier.java
index 86db86a..c7b8d06 100644
--- a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/ResequenceReifier.java
+++ b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/ResequenceReifier.java
@@ -84,8 +84,14 @@ public class ResequenceReifier extends ProcessorReifier<ResequenceDefinition> {
         boolean isAllowDuplicates = parseBoolean(config.getAllowDuplicates(), false);
 
         Resequencer resequencer = new Resequencer(camelContext, target, expression, isAllowDuplicates, isReverse);
-        resequencer.setBatchSize(parseInt(config.getBatchSize()));
-        resequencer.setBatchTimeout(parseDuration(config.getBatchTimeout()));
+        Integer num = parseInt(config.getBatchSize());
+        if (num != null) {
+            resequencer.setBatchSize(num);
+        }
+        Long dur = parseDuration(config.getBatchTimeout());
+        if (dur != null) {
+            resequencer.setBatchTimeout(dur);
+        }
         resequencer.setReverse(isReverse);
         resequencer.setAllowDuplicates(isAllowDuplicates);
         if (config.getIgnoreInvalidExchanges() != null) {
@@ -123,11 +129,18 @@ public class ResequenceReifier extends ProcessorReifier<ResequenceDefinition> {
         comparator.setExpression(expression);
 
         StreamResequencer resequencer = new StreamResequencer(camelContext, target, comparator, expression);
-        resequencer.setTimeout(parseDuration(config.getTimeout()));
-        if (config.getDeliveryAttemptInterval() != null) {
-            resequencer.setDeliveryAttemptInterval(parseDuration(config.getDeliveryAttemptInterval()));
+        Long dur = parseDuration(config.getTimeout());
+        if (dur != null) {
+            resequencer.setTimeout(dur);
+        }
+        dur = parseDuration(config.getDeliveryAttemptInterval());
+        if (dur != null) {
+            resequencer.setDeliveryAttemptInterval(dur);
+        }
+        Integer num = parseInt(config.getCapacity());
+        if (num != null) {
+            resequencer.setCapacity(num);
         }
-        resequencer.setCapacity(parseInt(config.getCapacity()));
         resequencer.setRejectOld(parseBoolean(config.getRejectOld(), false));
         if (config.getIgnoreInvalidExchanges() != null) {
             resequencer.setIgnoreInvalidExchanges(parseBoolean(config.getIgnoreInvalidExchanges(), false));
diff --git a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/RoutingSlipReifier.java b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/RoutingSlipReifier.java
index 6a871ba..0b54b7f 100644
--- a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/RoutingSlipReifier.java
+++ b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/RoutingSlipReifier.java
@@ -44,8 +44,9 @@ public class RoutingSlipReifier extends ExpressionReifier<RoutingSlipDefinition<
         if (definition.getIgnoreInvalidEndpoints() != null) {
             routingSlip.setIgnoreInvalidEndpoints(parseBoolean(definition.getIgnoreInvalidEndpoints(), false));
         }
-        if (definition.getCacheSize() != null) {
-            routingSlip.setCacheSize(parseInt(definition.getCacheSize()));
+        Integer num = parseInt(definition.getCacheSize());
+        if (num != null) {
+            routingSlip.setCacheSize(num);
         }
 
         // and wrap this in an error handler
diff --git a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/SagaReifier.java b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/SagaReifier.java
index a60152e..447adae 100644
--- a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/SagaReifier.java
+++ b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/SagaReifier.java
@@ -93,8 +93,9 @@ public class SagaReifier extends ProcessorReifier<SagaDefinition> {
             return sagaService;
         }
 
-        if (definition.getSagaServiceRef() != null) {
-            return mandatoryLookup(parseString(definition.getSagaServiceRef()), CamelSagaService.class);
+        String ref = parseString(definition.getSagaServiceRef());
+        if (ref != null) {
+            return mandatoryLookup(ref, CamelSagaService.class);
         }
 
         sagaService = camelContext.hasService(CamelSagaService.class);
diff --git a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/SamplingReifier.java b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/SamplingReifier.java
index 547719c..a07273b 100644
--- a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/SamplingReifier.java
+++ b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/SamplingReifier.java
@@ -32,11 +32,12 @@ public class SamplingReifier extends ProcessorReifier<SamplingDefinition> {
 
     @Override
     public Processor createProcessor() throws Exception {
-        if (definition.getMessageFrequency() != null) {
-            return new SamplingThrottler(parseLong(definition.getMessageFrequency()));
+        Long freq = parseLong(definition.getMessageFrequency());
+        if (freq != null) {
+            return new SamplingThrottler(freq);
         } else {
             // should default be 1 sample period
-            long time = definition.getSamplePeriod() != null ? parseDuration(definition.getSamplePeriod()) : 1L;
+            long time = parseDuration(definition.getSamplePeriod(), 1);
             // should default be in seconds
             TimeUnit tu = definition.getUnits() != null ? parse(TimeUnit.class, definition.getUnits()) : TimeUnit.SECONDS;
             return new SamplingThrottler(time, tu);
diff --git a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/SortReifier.java b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/SortReifier.java
index 6de9bca..2a2c123 100644
--- a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/SortReifier.java
+++ b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/SortReifier.java
@@ -38,8 +38,9 @@ public class SortReifier<T, U extends SortDefinition<T>> extends ExpressionReifi
     @SuppressWarnings("unchecked")
     public Processor createProcessor() throws Exception {
         // lookup in registry
-        if (isNotEmpty(definition.getComparatorRef())) {
-            definition.setComparator(lookup(parseString(definition.getComparatorRef()), Comparator.class));
+        String ref = parseString(definition.getComparatorRef());
+        if (isNotEmpty(ref)) {
+            definition.setComparator(lookup(ref, Comparator.class));
         }
 
         // if no comparator then default on to string representation
diff --git a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/SplitReifier.java b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/SplitReifier.java
index 03dd7c6..b290c3d 100644
--- a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/SplitReifier.java
+++ b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/SplitReifier.java
@@ -49,23 +49,24 @@ public class SplitReifier extends ExpressionReifier<SplitDefinition> {
         boolean shutdownThreadPool = willCreateNewThreadPool(definition, isParallelProcessing);
         ExecutorService threadPool = getConfiguredExecutorService("Split", definition, isParallelProcessing);
 
-        long timeout = definition.getTimeout() != null ? parseDuration(definition.getTimeout()) : 0;
+        long timeout = parseDuration(definition.getTimeout(), 0);
         if (timeout > 0 && !isParallelProcessing) {
             throw new IllegalArgumentException("Timeout is used but ParallelProcessing has not been enabled.");
         }
-        if (definition.getOnPrepareRef() != null) {
-            definition.setOnPrepare(mandatoryLookup(parseString(definition.getOnPrepareRef()), Processor.class));
+        String ref = parseString(definition.getOnPrepareRef());
+        if (ref != null) {
+            definition.setOnPrepare(mandatoryLookup(ref, Processor.class));
         }
 
         Expression exp = createExpression(definition.getExpression());
+        String delimiter = parseString(definition.getDelimiter());
 
         Splitter answer;
-        if (definition.getDelimiter() != null) {
+        if (delimiter != null) {
             answer = new Splitter(
                     camelContext, route, exp, childProcessor, definition.getAggregationStrategy(), isParallelProcessing,
                     threadPool, shutdownThreadPool, isStreaming, isStopOnException, timeout, definition.getOnPrepare(),
-                    isShareUnitOfWork, isParallelAggregate, isStopOnAggregateException,
-                    parseString(definition.getDelimiter()));
+                    isShareUnitOfWork, isParallelAggregate, isStopOnAggregateException, delimiter);
         } else {
             answer = new Splitter(
                     camelContext, route, exp, childProcessor, definition.getAggregationStrategy(), isParallelProcessing,
diff --git a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/ThreadsReifier.java b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/ThreadsReifier.java
index 08f57d8..0474177 100644
--- a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/ThreadsReifier.java
+++ b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/ThreadsReifier.java
@@ -37,7 +37,10 @@ public class ThreadsReifier extends ProcessorReifier<ThreadsDefinition> {
     @Override
     public Processor createProcessor() throws Exception {
         // the threads name
-        String name = definition.getThreadName() != null ? parseString(definition.getThreadName()) : "Threads";
+        String name = parseString(definition.getThreadName());
+        if (name == null || name.isEmpty()) {
+            name = "Threads";
+        }
         // prefer any explicit configured executor service
         boolean shutdownThreadPool = willCreateNewThreadPool(definition, true);
         ExecutorService threadPool = getConfiguredExecutorService(name, definition, false);
@@ -101,9 +104,10 @@ public class ThreadsReifier extends ProcessorReifier<ThreadsDefinition> {
     }
 
     protected ThreadPoolRejectedPolicy resolveRejectedPolicy() {
-        if (definition.getExecutorServiceRef() != null && definition.getRejectedPolicy() == null) {
+        String ref = parseString(definition.getExecutorServiceRef());
+        if (ref != null && definition.getRejectedPolicy() == null) {
             ThreadPoolProfile threadPoolProfile = camelContext.getExecutorServiceManager()
-                    .getThreadPoolProfile(parseString(definition.getExecutorServiceRef()));
+                    .getThreadPoolProfile(ref);
             if (threadPoolProfile != null) {
                 return threadPoolProfile.getRejectedPolicy();
             }
diff --git a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/ThrottleReifier.java b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/ThrottleReifier.java
index e1168e4..20dc0be 100644
--- a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/ThrottleReifier.java
+++ b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/ThrottleReifier.java
@@ -38,7 +38,7 @@ public class ThrottleReifier extends ExpressionReifier<ThrottleDefinition> {
         ScheduledExecutorService threadPool = getConfiguredScheduledExecutorService("Throttle", definition, true);
 
         // should be default 1000 millis
-        long period = definition.getTimePeriodMillis() != null ? parseDuration(definition.getTimePeriodMillis()) : 1000L;
+        long period = parseDuration(definition.getTimePeriodMillis(), 1000L);
 
         // max requests per period is mandatory
         Expression maxRequestsExpression = createMaxRequestsPerPeriodExpression();
diff --git a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/ThrowExceptionReifier.java b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/ThrowExceptionReifier.java
index f7959da..51e0924 100644
--- a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/ThrowExceptionReifier.java
+++ b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/ThrowExceptionReifier.java
@@ -31,8 +31,9 @@ public class ThrowExceptionReifier extends ProcessorReifier<ThrowExceptionDefini
     @Override
     public Processor createProcessor() {
         Exception exception = definition.getException();
-        if (exception == null && definition.getRef() != null) {
-            exception = lookup(parseString(definition.getRef()), Exception.class);
+        String ref = parseString(definition.getRef());
+        if (exception == null && ref != null) {
+            exception = lookup(ref, Exception.class);
         }
 
         Class<? extends Exception> exceptionClass = definition.getExceptionClass();
diff --git a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/ToDynamicReifier.java b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/ToDynamicReifier.java
index d68fb6f..fc4ad92 100644
--- a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/ToDynamicReifier.java
+++ b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/ToDynamicReifier.java
@@ -24,6 +24,7 @@ import org.apache.camel.model.ProcessorDefinition;
 import org.apache.camel.model.ToDynamicDefinition;
 import org.apache.camel.processor.SendDynamicProcessor;
 import org.apache.camel.spi.Language;
+import org.apache.camel.support.EndpointHelper;
 import org.apache.camel.util.StringHelper;
 
 public class ToDynamicReifier<T extends ToDynamicDefinition> extends ProcessorReifier<T> {
@@ -47,8 +48,9 @@ public class ToDynamicReifier<T extends ToDynamicDefinition> extends ProcessorRe
         SendDynamicProcessor processor = new SendDynamicProcessor(uri, exp);
         processor.setCamelContext(camelContext);
         processor.setPattern(parse(ExchangePattern.class, definition.getPattern()));
-        if (definition.getCacheSize() != null) {
-            processor.setCacheSize(parseInt(definition.getCacheSize()));
+        Integer num = parseInt(definition.getCacheSize());
+        if (num != null) {
+            processor.setCacheSize(num);
         }
         if (definition.getIgnoreInvalidEndpoint() != null) {
             processor.setIgnoreInvalidEndpoint(parseBoolean(definition.getIgnoreInvalidEndpoint(), false));
@@ -64,7 +66,7 @@ public class ToDynamicReifier<T extends ToDynamicDefinition> extends ProcessorRe
 
     protected Expression createExpression(String uri) {
         // make sure to parse property placeholders
-        uri = camelContext.resolvePropertyPlaceholders(uri);
+        uri = EndpointHelper.resolveEndpointUriPropertyPlaceholders(camelContext, uri);
 
         // we use simple language by default but you can configure a different language
         String language = "simple";
diff --git a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/WireTapReifier.java b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/WireTapReifier.java
index d8f1dfd..79c1d55 100644
--- a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/WireTapReifier.java
+++ b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/WireTapReifier.java
@@ -65,8 +65,9 @@ public class WireTapReifier extends ToDynamicReifier<WireTapDefinition<?>> {
                 parseBoolean(definition.getDynamicUri(), true));
         answer.setCopy(isCopy);
         Processor newExchangeProcessor = definition.getNewExchangeProcessor();
-        if (definition.getNewExchangeProcessorRef() != null) {
-            newExchangeProcessor = mandatoryLookup(parseString(definition.getNewExchangeProcessorRef()), Processor.class);
+        String ref = parseString(definition.getNewExchangeProcessorRef());
+        if (ref != null) {
+            newExchangeProcessor = mandatoryLookup(ref, Processor.class);
         }
         if (newExchangeProcessor != null) {
             answer.addNewExchangeProcessor(newExchangeProcessor);
@@ -81,8 +82,9 @@ public class WireTapReifier extends ToDynamicReifier<WireTapDefinition<?>> {
             }
         }
         Processor onPrepare = definition.getOnPrepare();
-        if (definition.getOnPrepareRef() != null) {
-            onPrepare = mandatoryLookup(parseString(definition.getOnPrepareRef()), Processor.class);
+        ref = parseString(definition.getOnPrepareRef());
+        if (ref != null) {
+            onPrepare = mandatoryLookup(ref, Processor.class);
         }
         if (onPrepare != null) {
             answer.setOnPrepare(onPrepare);
diff --git a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/dataformat/YAMLDataFormatReifier.java b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/dataformat/YAMLDataFormatReifier.java
index c455990..849bb18 100644
--- a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/dataformat/YAMLDataFormatReifier.java
+++ b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/dataformat/YAMLDataFormatReifier.java
@@ -64,15 +64,16 @@ public class YAMLDataFormatReifier extends DataFormatReifier<YAMLDataFormat> {
             List<String> typeFilterDefinitions = new ArrayList<>(definition.getTypeFilters().size());
             for (YAMLTypeFilterDefinition definition : definition.getTypeFilters()) {
                 String value = parseString(definition.getValue());
-                if (!value.startsWith("type") && !value.startsWith("regexp")) {
+                if (value != null && !value.startsWith("type") && !value.startsWith("regexp")) {
                     YAMLTypeFilterType type = parse(YAMLTypeFilterType.class, definition.getType());
                     if (type == null) {
                         type = YAMLTypeFilterType.type;
                     }
-
                     value = type.name() + ":" + value;
                 }
-                typeFilterDefinitions.add(value);
+                if (value != null) {
+                    typeFilterDefinitions.add(value);
+                }
             }
             return typeFilterDefinitions;
         } else {
diff --git a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/loadbalancer/FailoverLoadBalancerReifier.java b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/loadbalancer/FailoverLoadBalancerReifier.java
index bf4aafa..037c668 100644
--- a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/loadbalancer/FailoverLoadBalancerReifier.java
+++ b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/loadbalancer/FailoverLoadBalancerReifier.java
@@ -57,8 +57,9 @@ public class FailoverLoadBalancerReifier extends LoadBalancerReifier<FailoverLoa
             answer = new FailOverLoadBalancer(classes);
         }
 
-        if (definition.getMaximumFailoverAttempts() != null) {
-            answer.setMaximumFailoverAttempts(parseInt(definition.getMaximumFailoverAttempts()));
+        Integer num = parseInt(definition.getMaximumFailoverAttempts());
+        if (num != null) {
+            answer.setMaximumFailoverAttempts(num);
         }
         if (definition.getRoundRobin() != null) {
             answer.setRoundRobin(parseBoolean(definition.getRoundRobin(), false));
diff --git a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/transformer/CustomTransformeReifier.java b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/transformer/CustomTransformeReifier.java
index cf1e2ff..ae3531d 100644
--- a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/transformer/CustomTransformeReifier.java
+++ b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/transformer/CustomTransformeReifier.java
@@ -33,8 +33,9 @@ public class CustomTransformeReifier extends TransformerReifier<CustomTransforme
             throw new IllegalArgumentException("'ref' or 'className' must be specified for customTransformer");
         }
         Transformer transformer;
-        if (definition.getRef() != null) {
-            transformer = lookup(parseString(definition.getRef()), Transformer.class);
+        String ref = parseString(definition.getRef());
+        if (ref != null) {
+            transformer = lookup(ref, Transformer.class);
             if (transformer == null) {
                 throw new IllegalArgumentException("Cannot find transformer with ref:" + definition.getRef());
             }
diff --git a/core/camel-core-xml/src/main/java/org/apache/camel/core/xml/AbstractCamelEndpointFactoryBean.java b/core/camel-core-xml/src/main/java/org/apache/camel/core/xml/AbstractCamelEndpointFactoryBean.java
index 407d56e..32f2086 100644
--- a/core/camel-core-xml/src/main/java/org/apache/camel/core/xml/AbstractCamelEndpointFactoryBean.java
+++ b/core/camel-core-xml/src/main/java/org/apache/camel/core/xml/AbstractCamelEndpointFactoryBean.java
@@ -31,6 +31,7 @@ import org.apache.camel.Endpoint;
 import org.apache.camel.NoSuchEndpointException;
 import org.apache.camel.model.PropertyDefinition;
 import org.apache.camel.spi.Metadata;
+import org.apache.camel.support.EndpointHelper;
 import org.apache.camel.util.URISupport;
 
 @XmlAccessorType(XmlAccessType.FIELD)
@@ -48,7 +49,7 @@ public abstract class AbstractCamelEndpointFactoryBean extends AbstractCamelFact
     public Endpoint getObject() throws Exception {
         if (endpoint == null || !endpoint.isSingleton()) {
             // resolve placeholders (but leave the original uri unchanged)
-            String resolved = getCamelContext().resolvePropertyPlaceholders(uri);
+            String resolved = EndpointHelper.resolveEndpointUriPropertyPlaceholders(getCamelContext(), uri);
             String target = createUri(resolved);
             this.endpoint = getCamelContext().getEndpoint(target);
             if (endpoint == null) {
diff --git a/core/camel-core/src/test/java/org/apache/camel/component/properties/OptionalPropertyPlaceholderEipTest.java b/core/camel-core/src/test/java/org/apache/camel/component/properties/OptionalPropertyPlaceholderEipTest.java
new file mode 100644
index 0000000..24ad76a
--- /dev/null
+++ b/core/camel-core/src/test/java/org/apache/camel/component/properties/OptionalPropertyPlaceholderEipTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.properties;
+
+import java.util.Properties;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.builder.RouteBuilder;
+import org.junit.jupiter.api.Test;
+
+public class OptionalPropertyPlaceholderEipTest extends ContextTestSupport {
+
+    @Override
+    public boolean isUseRouteBuilder() {
+        return false;
+    }
+
+    @Test
+    public void testQueryOptionalNotPresent() throws Exception {
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from("direct:start")
+                        .split(body()).delimiter("{{?myDelim}}")
+                        .to("mock:line");
+            }
+        });
+        context.start();
+
+        getMockEndpoint("mock:line").expectedMessageCount(2);
+        template.sendBody("direct:start", "A,B");
+        assertMockEndpointsSatisfied();
+    }
+
+    @Test
+    public void testQueryOptionalPresent() throws Exception {
+        Properties prop = new Properties();
+        prop.put("myDelim", ";");
+        context.getPropertiesComponent().setInitialProperties(prop);
+
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from("direct:start")
+                        .split(body()).delimiter("{{?myDelim}}")
+                        .to("mock:line");
+            }
+        });
+        context.start();
+
+        getMockEndpoint("mock:line").expectedMessageCount(1);
+        template.sendBody("direct:start", "A,B");
+        assertMockEndpointsSatisfied();
+
+        resetMocks();
+
+        getMockEndpoint("mock:line").expectedMessageCount(3);
+        template.sendBody("direct:start", "A;B;C");
+        assertMockEndpointsSatisfied();
+    }
+
+    @Override
+    protected CamelContext createCamelContext() throws Exception {
+        CamelContext context = super.createCamelContext();
+        context.getPropertiesComponent().setLocation("classpath:org/apache/camel/component/properties/myproperties.properties");
+        return context;
+    }
+
+}
diff --git a/core/camel-core/src/test/java/org/apache/camel/component/properties/OptionalPropertyPlaceholderTest.java b/core/camel-core/src/test/java/org/apache/camel/component/properties/OptionalPropertyPlaceholderTest.java
new file mode 100644
index 0000000..63edb17
--- /dev/null
+++ b/core/camel-core/src/test/java/org/apache/camel/component/properties/OptionalPropertyPlaceholderTest.java
@@ -0,0 +1,228 @@
+/*
+ * 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.properties;
+
+import java.util.Properties;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.builder.RouteBuilder;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class OptionalPropertyPlaceholderTest extends ContextTestSupport {
+
+    // TODO: eip test
+    // TODO: reuse code in AbstractCamelContext and endpoint-dsl
+
+    @Override
+    public boolean isUseRouteBuilder() {
+        return false;
+    }
+
+    @Test
+    public void testQueryOptionalNotPresent() throws Exception {
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from("direct:start")
+                        .to("mock:result?retainFirst={{?maxKeep}}");
+            }
+        });
+        context.start();
+
+        getMockEndpoint("mock:result").expectedMessageCount(2);
+
+        template.sendBody("direct:start", "Hello World");
+        template.sendBody("direct:start", "Bye World");
+
+        assertMockEndpointsSatisfied();
+
+        assertEquals(2, getMockEndpoint("mock:result").getReceivedExchanges().size());
+    }
+
+    @Test
+    public void testQueryOptionalPresent() throws Exception {
+        Properties prop = new Properties();
+        prop.put("maxKeep", "1");
+        context.getPropertiesComponent().setInitialProperties(prop);
+
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from("direct:start")
+                        .to("mock:result?retainFirst={{?maxKeep}}");
+            }
+        });
+        context.start();
+
+        getMockEndpoint("mock:result?retainFirst=1").expectedMessageCount(2);
+
+        template.sendBody("direct:start", "Hello World");
+        template.sendBody("direct:start", "Bye World");
+
+        assertMockEndpointsSatisfied();
+
+        assertEquals(1, getMockEndpoint("mock:result?retainFirst=1").getReceivedExchanges().size());
+    }
+
+    @Test
+    public void testPathOptionalNotPresent() throws Exception {
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from("direct:start")
+                        .to("mock:res{{?unknown}}ult");
+            }
+        });
+        context.start();
+
+        getMockEndpoint("mock:result").expectedMessageCount(2);
+
+        template.sendBody("direct:start", "Hello World");
+        template.sendBody("direct:start", "Bye World");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Test
+    public void testPathOptionalPresent() throws Exception {
+        Properties prop = new Properties();
+        prop.put("whereTo", "result");
+        context.getPropertiesComponent().setInitialProperties(prop);
+
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from("direct:start")
+                        .to("mock:{{?whereTo}}");
+            }
+        });
+        context.start();
+
+        getMockEndpoint("mock:result").expectedMessageCount(2);
+
+        template.sendBody("direct:start", "Hello World");
+        template.sendBody("direct:start", "Bye World");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Test
+    public void testQueryAndPathOptionalNotPresent() throws Exception {
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from("direct:start")
+                        .to("mock:res{{?unknown}}ult?retainFirst={{?maxKeep}}");
+            }
+        });
+        context.start();
+
+        getMockEndpoint("mock:result").expectedMessageCount(2);
+
+        template.sendBody("direct:start", "Hello World");
+        template.sendBody("direct:start", "Bye World");
+
+        assertMockEndpointsSatisfied();
+
+        assertEquals(2, getMockEndpoint("mock:result").getReceivedExchanges().size());
+    }
+
+    @Test
+    public void testQueryAndPathOptionalPresent() throws Exception {
+        Properties prop = new Properties();
+        prop.put("maxKeep", "1");
+        prop.put("whereTo", "result");
+        context.getPropertiesComponent().setInitialProperties(prop);
+
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from("direct:start")
+                        .to("mock:{{?whereTo}}?retainFirst={{?maxKeep}}");
+            }
+        });
+        context.start();
+
+        getMockEndpoint("mock:result?retainFirst=1").expectedMessageCount(2);
+
+        template.sendBody("direct:start", "Hello World");
+        template.sendBody("direct:start", "Bye World");
+
+        assertMockEndpointsSatisfied();
+
+        assertEquals(1, getMockEndpoint("mock:result?retainFirst=1").getReceivedExchanges().size());
+    }
+
+    @Test
+    public void testQueryAndPathOptionalMixed() throws Exception {
+        Properties prop = new Properties();
+        prop.put("maxKeep", "1");
+        context.getPropertiesComponent().setInitialProperties(prop);
+
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from("direct:start")
+                        .to("mock:res{{?unknown}}ult?retainFirst={{?maxKeep}}");
+            }
+        });
+        context.start();
+
+        getMockEndpoint("mock:result?retainFirst=1").expectedMessageCount(2);
+
+        template.sendBody("direct:start", "Hello World");
+        template.sendBody("direct:start", "Bye World");
+
+        assertMockEndpointsSatisfied();
+
+        assertEquals(1, getMockEndpoint("mock:result?retainFirst=1").getReceivedExchanges().size());
+    }
+
+    @Test
+    public void testQueryAndPathOptionalMixedTwo() throws Exception {
+        Properties prop = new Properties();
+        prop.put("whereTo", "result");
+        context.getPropertiesComponent().setInitialProperties(prop);
+
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from("direct:start")
+                        .to("mock:{{?whereTo}}?retainFirst={{?maxKeep}}");
+            }
+        });
+        context.start();
+
+        getMockEndpoint("mock:result").expectedMessageCount(2);
+
+        template.sendBody("direct:start", "Hello World");
+        template.sendBody("direct:start", "Bye World");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Override
+    protected CamelContext createCamelContext() throws Exception {
+        CamelContext context = super.createCamelContext();
+        context.getPropertiesComponent().setLocation("classpath:org/apache/camel/component/properties/myproperties.properties");
+        return context;
+    }
+
+}
diff --git a/core/camel-endpointdsl/src/main/java/org/apache/camel/builder/endpoint/AbstractEndpointBuilder.java b/core/camel-endpointdsl/src/main/java/org/apache/camel/builder/endpoint/AbstractEndpointBuilder.java
index b6f24aa..a279a09 100644
--- a/core/camel-endpointdsl/src/main/java/org/apache/camel/builder/endpoint/AbstractEndpointBuilder.java
+++ b/core/camel-endpointdsl/src/main/java/org/apache/camel/builder/endpoint/AbstractEndpointBuilder.java
@@ -18,8 +18,10 @@ package org.apache.camel.builder.endpoint;
 
 import java.net.URISyntaxException;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.Map;
+import java.util.Set;
 import java.util.TreeMap;
 
 import org.apache.camel.CamelContext;
@@ -30,6 +32,8 @@ import org.apache.camel.NoSuchEndpointException;
 import org.apache.camel.RuntimeCamelException;
 import org.apache.camel.builder.SimpleBuilder;
 import org.apache.camel.spi.NormalizedEndpointUri;
+import org.apache.camel.spi.PropertiesComponent;
+import org.apache.camel.support.EndpointHelper;
 import org.apache.camel.support.NormalizedUri;
 import org.apache.camel.util.URISupport;
 
@@ -68,16 +72,25 @@ public class AbstractEndpointBuilder {
     }
 
     private static void resolvePropertyPlaceholders(CamelContext context, Map<String, Object> properties) {
+        Set<String> toRemove = new HashSet<>();
         for (Map.Entry<String, Object> entry : properties.entrySet()) {
             Object value = entry.getValue();
             if (value instanceof String) {
                 String text = (String) value;
-                String changed = context.resolvePropertyPlaceholders(text);
-                if (!changed.equals(text)) {
+                String changed = context.adapt(ExtendedCamelContext.class).resolvePropertyPlaceholders(text, true);
+                if (changed.startsWith(PropertiesComponent.PREFIX_OPTIONAL_TOKEN)) {
+                    // unresolved then remove it
+                    toRemove.add(entry.getKey());
+                } else if (!changed.equals(text)) {
                     entry.setValue(changed);
                 }
             }
         }
+        if (!toRemove.isEmpty()) {
+            for (String key : toRemove) {
+                properties.remove(key);
+            }
+        }
     }
 
     public <T extends Endpoint> T resolve(CamelContext context, Class<T> endpointType) throws NoSuchEndpointException {
@@ -109,7 +122,7 @@ public class AbstractEndpointBuilder {
         String targetPath = path;
         if (camelContext != null) {
             targetScheme = camelContext.resolvePropertyPlaceholders(targetScheme);
-            targetPath = camelContext.resolvePropertyPlaceholders(targetPath);
+            targetPath = EndpointHelper.resolveEndpointUriPropertyPlaceholders(camelContext, targetPath);
         }
 
         if (params.isEmpty()) {
diff --git a/core/camel-endpointdsl/src/test/java/org/apache/camel/builder/endpoint/OptionalPropertyPlaceholderTest.java b/core/camel-endpointdsl/src/test/java/org/apache/camel/builder/endpoint/OptionalPropertyPlaceholderTest.java
new file mode 100644
index 0000000..08de571
--- /dev/null
+++ b/core/camel-endpointdsl/src/test/java/org/apache/camel/builder/endpoint/OptionalPropertyPlaceholderTest.java
@@ -0,0 +1,121 @@
+/*
+ * 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.builder.endpoint;
+
+import java.util.Properties;
+
+import org.apache.camel.test.junit5.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class OptionalPropertyPlaceholderTest extends CamelTestSupport {
+
+    @Override
+    public boolean isUseRouteBuilder() {
+        return false;
+    }
+
+    @Test
+    public void testQueryOptionalNotPresent() throws Exception {
+        context.addRoutes(new EndpointRouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from(direct("start")).to(mock("result").retainFirst("{{?maxKeep}}").failFast(false));
+            }
+        });
+        context.start();
+
+        getMockEndpoint("mock:result").expectedMessageCount(2);
+
+        template.sendBody("direct:start", "Hello World");
+        template.sendBody("direct:start", "Bye World");
+
+        assertMockEndpointsSatisfied();
+
+        assertEquals(2, getMockEndpoint("mock:result").getReceivedExchanges().size());
+    }
+
+    @Test
+    public void testQueryOptionalPresent() throws Exception {
+        Properties prop = new Properties();
+        prop.put("maxKeep", "1");
+        context.getPropertiesComponent().setInitialProperties(prop);
+
+        context.addRoutes(new EndpointRouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from(direct("start")).to(mock("result").retainFirst("{{?maxKeep}}").failFast(false));
+            }
+        });
+        context.start();
+
+        getMockEndpoint("mock:result?retainFirst=1").expectedMessageCount(2);
+
+        template.sendBody("direct:start", "Hello World");
+        template.sendBody("direct:start", "Bye World");
+
+        assertMockEndpointsSatisfied();
+
+        assertEquals(1, getMockEndpoint("mock:result?retainFirst=1").getReceivedExchanges().size());
+    }
+
+    @Test
+    public void testPathOptionalNotPresent() throws Exception {
+        context.addRoutes(new EndpointRouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from(direct("start")).to(mock("res{{?whereTo}}ult").failFast(false));
+            }
+        });
+        context.start();
+
+        getMockEndpoint("mock:result").expectedMessageCount(2);
+
+        template.sendBody("direct:start", "Hello World");
+        template.sendBody("direct:start", "Bye World");
+
+        assertMockEndpointsSatisfied();
+
+        assertEquals(2, getMockEndpoint("mock:result").getReceivedExchanges().size());
+    }
+
+    @Test
+    public void testPathOptionalPresent() throws Exception {
+        Properties prop = new Properties();
+        prop.put("whereTo", "result");
+        context.getPropertiesComponent().setInitialProperties(prop);
+
+        context.addRoutes(new EndpointRouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from(direct("start")).to(mock("{{?whereTo}}").failFast(false));
+            }
+        });
+        context.start();
+
+        getMockEndpoint("mock:result").expectedMessageCount(2);
+
+        template.sendBody("direct:start", "Hello World");
+        template.sendBody("direct:start", "Bye World");
+
+        assertMockEndpointsSatisfied();
+
+        assertEquals(2, getMockEndpoint("mock:result").getReceivedExchanges().size());
+    }
+
+}
diff --git a/core/camel-support/src/main/java/org/apache/camel/support/EndpointHelper.java b/core/camel-support/src/main/java/org/apache/camel/support/EndpointHelper.java
index 97fdf66..285ca71 100644
--- a/core/camel-support/src/main/java/org/apache/camel/support/EndpointHelper.java
+++ b/core/camel-support/src/main/java/org/apache/camel/support/EndpointHelper.java
@@ -20,6 +20,7 @@ import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Iterator;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -36,6 +37,7 @@ import org.apache.camel.PollingConsumer;
 import org.apache.camel.Processor;
 import org.apache.camel.ResolveEndpointFailedException;
 import org.apache.camel.Route;
+import org.apache.camel.spi.PropertiesComponent;
 import org.apache.camel.support.service.ServiceHelper;
 import org.apache.camel.util.StringHelper;
 import org.apache.camel.util.URISupport;
@@ -57,6 +59,90 @@ public final class EndpointHelper {
     }
 
     /**
+     * Resolves the endpoint uri that may have property placeholders (supports optional property placeholders).
+     *
+     * @param  camelContext the camel context
+     * @param  uri          the endpoint uri
+     * @return              returns endpoint uri with property placeholders resolved
+     */
+    public static String resolveEndpointUriPropertyPlaceholders(CamelContext camelContext, String uri) {
+        // the uri may have optional property placeholders which is not possible to resolve
+        // so we keep the unresolved in the uri, which we then afterwards will remove
+        // which is a little complex depending on the placeholder is from context-path or query parameters
+        // in the uri string
+        try {
+            uri = camelContext.adapt(ExtendedCamelContext.class).resolvePropertyPlaceholders(uri, true);
+            if (uri == null || uri.isEmpty()) {
+                return uri;
+            }
+            String prefix = PropertiesComponent.PREFIX_OPTIONAL_TOKEN;
+            if (uri.contains(prefix)) {
+                String unresolved = uri;
+                uri = doResolveEndpointUriOptionalPropertyPlaceholders(unresolved);
+                LOG.trace("Unresolved optional placeholders removed from uri: {} -> {}", unresolved, uri);
+            }
+            LOG.trace("Resolved property placeholders with uri: {}", uri);
+        } catch (Exception e) {
+            throw new ResolveEndpointFailedException(uri, e);
+        }
+        return uri;
+    }
+
+    private static String doResolveEndpointUriOptionalPropertyPlaceholders(String uri) throws URISyntaxException {
+        String prefix = PropertiesComponent.PREFIX_OPTIONAL_TOKEN;
+
+        // find query position which is the first question mark that is not part of the optional token prefix
+        int pos = 0;
+        for (int i = 0; i < uri.length(); i++) {
+            char ch = uri.charAt(i);
+            if (ch == '?') {
+                // ensure that its not part of property prefix
+                if (i > 2) {
+                    char ch1 = uri.charAt(i - 1);
+                    char ch2 = uri.charAt(i - 2);
+                    if (ch1 != '{' && ch2 != '{') {
+                        pos = i;
+                        break;
+                    }
+                } else {
+                    pos = i;
+                    break;
+                }
+            }
+        }
+        String base = pos > 0 ? uri.substring(0, pos) : uri;
+        String query = pos > 0 ? uri.substring(pos + 1) : null;
+
+        // the base (context path) should remove all unresolved property placeholders
+        // which is done by replacing all begin...end tokens with an empty string
+        String pattern = "\\{\\{?.*}}";
+        base = base.replaceAll(pattern, "");
+
+        // the query parameters needs to be rebuild by removing the unresolved key=value pairs
+        if (query != null && query.contains(prefix)) {
+            Map<String, Object> params = URISupport.parseQuery(query);
+            Map<String, Object> keep = new LinkedHashMap<>();
+            for (Map.Entry<String, Object> entry : params.entrySet()) {
+                String key = entry.getKey();
+                if (key.startsWith(prefix)) {
+                    continue;
+                }
+                Object value = entry.getValue();
+                if (value instanceof String && ((String) value).startsWith(prefix)) {
+                    continue;
+                }
+                keep.put(key, value);
+            }
+            // rebuild query
+            query = URISupport.createQueryString(keep);
+        }
+
+        // assemble uri as answer
+        uri = query != null && !query.isEmpty() ? base + "?" + query : base;
+        return uri;
+    }
+
+    /**
      * Normalize uri so we can do endpoint hits with minor mistakes and parameters is not in the same order.
      *
      * @param  uri                            the uri
@@ -112,7 +198,7 @@ public final class EndpointHelper {
      * Matches the endpoint with the given pattern.
      * <p/>
      * The endpoint will first resolve property placeholders using
-     * {@link CamelContext#resolvePropertyPlaceholders(String)}.
+     * {@link #resolveEndpointUriPropertyPlaceholders(CamelContext, String)}
      * <p/>
      * The match rules are applied in this order:
      * <ul>
@@ -131,7 +217,7 @@ public final class EndpointHelper {
     public static boolean matchEndpoint(CamelContext context, String uri, String pattern) {
         if (context != null) {
             try {
-                uri = context.resolvePropertyPlaceholders(uri);
+                uri = resolveEndpointUriPropertyPlaceholders(context, uri);
             } catch (Exception e) {
                 throw new ResolveEndpointFailedException(uri, e);
             }
@@ -163,7 +249,7 @@ public final class EndpointHelper {
     /**
      * Toggles // separators in the given uri. If the uri does not contain ://, the slashes are added, otherwise they
      * are removed.
-     * 
+     *
      * @param  normalizedUri The uri to add/remove separators in
      * @return               The uri with separators added or removed
      */
diff --git a/core/camel-support/src/main/java/org/apache/camel/support/component/EndpointUriFactorySupport.java b/core/camel-support/src/main/java/org/apache/camel/support/component/EndpointUriFactorySupport.java
index 47b8d57..26a78df 100644
--- a/core/camel-support/src/main/java/org/apache/camel/support/component/EndpointUriFactorySupport.java
+++ b/core/camel-support/src/main/java/org/apache/camel/support/component/EndpointUriFactorySupport.java
@@ -56,9 +56,9 @@ public abstract class EndpointUriFactorySupport implements CamelContextAware, En
         }
         if (ObjectHelper.isNotEmpty(obj)) {
             String str = camelContext.getTypeConverter().convertTo(String.class, obj);
-            int occurence = StringHelper.countOccurence(uri, name);
-            if (occurence > 1) {
-                uri = StringHelper.replaceFromSecondOccurence(uri, name, str);
+            int occurrence = StringHelper.countOccurrence(uri, name);
+            if (occurrence > 1) {
+                uri = StringHelper.replaceFromSecondOccurrence(uri, name, str);
             } else {
                 uri = uri.replace(name, str);
             }
@@ -110,37 +110,4 @@ public abstract class EndpointUriFactorySupport implements CamelContextAware, En
         return uri;
     }
 
-    private int countOccurence(String str, String findStr) {
-        int lastIndex = 0;
-        int count = 0;
-
-        while (lastIndex != -1) {
-
-            lastIndex = str.indexOf(findStr, lastIndex);
-
-            if (lastIndex != -1) {
-                count++;
-                lastIndex += findStr.length();
-            }
-        }
-        return count;
-    }
-
-    private String replaceFromSecondOccurence(String str, String name, String repl) {
-        int index = str.indexOf(name);
-        boolean replace = false;
-
-        while (index != -1) {
-            String tempString = str.substring(index);
-            if (replace) {
-                tempString = tempString.replaceFirst(name, repl);
-                str = str.substring(0, index) + tempString;
-                replace = false;
-            } else {
-                replace = true;
-            }
-            index = str.indexOf(name, index + 1);
-        }
-        return str;
-    }
 }
diff --git a/core/camel-util/src/main/java/org/apache/camel/util/StringHelper.java b/core/camel-util/src/main/java/org/apache/camel/util/StringHelper.java
index 684790c..ddde325 100644
--- a/core/camel-util/src/main/java/org/apache/camel/util/StringHelper.java
+++ b/core/camel-util/src/main/java/org/apache/camel/util/StringHelper.java
@@ -1034,13 +1034,13 @@ public final class StringHelper {
     }
 
     /**
-     * Returns the occurence of a search string in to a string
+     * Returns the occurrence of a search string in to a string.
      *
      * @param  text   the text
      * @param  search the string to search
-     * @return        an integer reporting the number of occurence of the searched string in to the text
+     * @return        an integer reporting the number of occurrence of the searched string in to the text
      */
-    public static int countOccurence(String text, String search) {
+    public static int countOccurrence(String text, String search) {
         int lastIndex = 0;
         int count = 0;
         while (lastIndex != -1) {
@@ -1054,21 +1054,21 @@ public final class StringHelper {
     }
 
     /**
-     * Replaces a string in to a text starting from his second occurence
+     * Replaces a string in to a text starting from his second occurrence.
      *
-     * @param  text   the text
-     * @param  search the string to search
-     * @param  repl   the replacement for the string
-     * @return        the string with the replacement
+     * @param  text        the text
+     * @param  search      the string to search
+     * @param  replacement the replacement for the string
+     * @return             the string with the replacement
      */
-    public static String replaceFromSecondOccurence(String text, String search, String repl) {
+    public static String replaceFromSecondOccurrence(String text, String search, String replacement) {
         int index = text.indexOf(search);
         boolean replace = false;
 
         while (index != -1) {
             String tempString = text.substring(index);
             if (replace) {
-                tempString = tempString.replaceFirst(search, repl);
+                tempString = tempString.replaceFirst(search, replacement);
                 text = text.substring(0, index) + tempString;
                 replace = false;
             } else {
diff --git a/core/camel-util/src/main/java/org/apache/camel/util/URISupport.java b/core/camel-util/src/main/java/org/apache/camel/util/URISupport.java
index 6d77017..3839a86 100644
--- a/core/camel-util/src/main/java/org/apache/camel/util/URISupport.java
+++ b/core/camel-util/src/main/java/org/apache/camel/util/URISupport.java
@@ -134,6 +134,20 @@ public final class URISupport {
     }
 
     /**
+     * Strips the query parameters from the uri
+     *
+     * @param  uri the uri
+     * @return     the uri without the query parameter
+     */
+    public static String stripQuery(String uri) {
+        int idx = uri.indexOf('?');
+        if (idx > -1) {
+            uri = uri.substring(0, idx);
+        }
+        return uri;
+    }
+
+    /**
      * Parses the query part of the uri (eg the parameters).
      * <p/>
      * The URI parameters will by default be URI encoded. However you can define a parameter values with the syntax: