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

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

commit e3ae9f9a4b314fb393b8d8ff8aaf371105f84859
Author: Luigi De Masi <[email protected]>
AuthorDate: Tue Sep 29 16:50:41 2026 +0200

    CAMEL-25138: Preserve semantic declarations across DSL loading and export
    
    Keep semantic declarations when loading JAXB routes and converting YAML or
    Java routes through MCP. Export registered question policies and prepare
    Java expression models without starting routes or invoking providers.
    
    Refresh XML preparse caches when resource content changes after a failed
    batch, while preserving unchanged preparse and deferred bean ownership.
    Add regression coverage for foreign-loader and earlier-builder failures.
    
    Preserve numeric placeholders in Java exports and resolve them before
    runtime validation. Add numeric catalog types and defaults, move optional
    component discovery into a helper, relocate the configurer SPI, and keep
    RoutesDefinition accessors together.
    
    Validation: 2978 tests across the affected Java 17 module suites, with no
    failures or errors and two existing skips. Full repository clean install
    with tests skipped passes on Java 25. Regenerated artifacts are included.
    
    Co-authored-by: Codex <[email protected]>
    Signed-off-by: Luigi De Masi <[email protected]>
---
 .../camel/catalog/docs/semantic-language.adoc      |  15 ++-
 .../models-app/semanticQuestionDefinition.json     |   6 +-
 components/camel-ai/camel-semantic/pom.xml         |   5 +
 .../src/main/docs/semantic-language.adoc           |  15 ++-
 .../DefaultSemanticDefinitionConfigurer.java       |  37 ++++--
 .../apache/camel/semantic/SemanticQuestions.java   |   4 +
 .../yaml/SemanticDefinitionDeserializer.java       |  18 +--
 .../camel/semantic/SemanticDeclarationDslTest.java | 140 ++++++++++++++++++++-
 .../model/app/semanticQuestionDefinition.json      |   6 +-
 .../org/apache/camel/builder/RouteBuilder.java     |   5 +-
 .../org/apache/camel/model/RoutesDefinition.java   |  16 +--
 ...finition.java => SemanticDefinitionHelper.java} |  39 +-----
 .../apache/camel/model/app/SemanticDefinition.java |  22 ----
 .../model/app/SemanticQuestionDefinition.java      |  20 ++-
 .../{app => spi}/SemanticDefinitionConfigurer.java |   3 +-
 .../camel/java/out/JavaDslModelWriterSupport.java  |   4 +-
 .../apache/camel/java/out/JavaDslCompileTest.java  |  15 ++-
 .../java/org/apache/camel/xml/out/ModelWriter.java |   6 +-
 dsl/camel-jbang/camel-jbang-mcp/pom.xml            |  10 ++
 .../jbang/core/commands/mcp/TransformTools.java    |  14 +++
 .../core/commands/mcp/TransformToolsTest.java      |  70 +++++++++++
 .../camel/dsl/xml/io/XmlRoutesBuilderLoader.java   |  28 +++--
 .../dsl/xml/jaxb/JaxbXmlRoutesBuilderLoader.java   |   1 +
 .../camel/dsl/yaml/SemanticQuestionTest.java       |  15 +++
 24 files changed, 401 insertions(+), 113 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/semantic-language.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/semantic-language.adoc
index c360bacb879c..06e37072ac63 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/semantic-language.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/semantic-language.adoc
@@ -167,14 +167,25 @@ XML declarations are loaded before route references are 
initialized, including t
 resource loaded together. Reload replaces the source's questions;
 removing its declarations or deleting the resource removes obsolete 
definitions.
 
+The JAXB XML loader also supports declarations inside a `routes` document 
using the
+`http://camel.apache.org/schema/spring` namespace. With JAXB, load a separate 
declaration
+resource before the routes that reference it.
+
+The numeric `threshold` and `uncertainty` options accept property placeholders 
in all three
+DSLs. For example, Java accepts `.threshold("{{semantic.threshold:0.5}}")`, 
and XML accepts
+`threshold="{{semantic.threshold:0.5}}"`. Values are resolved and validated 
when declarations
+are registered; Java model exports preserve the placeholder text.
+
 Both Java and XML declarations require `camel-semantic` and use the same 
context-wide registry,
 validation, defaults and adapters as YAML. They do not require 
`camel-yaml-dsl`. Their questions
 can also be selected together using `refs:name1,name2`, as described below.
 
 === Exporting declarations
 
-Direct exports of a `RoutesDefinition` containing semantic declarations 
preserve them, as does
-XML-to-YAML conversion. Runtime route dumps such as 
`camel.main.dumpRoutes=yaml` contain only
+Direct exports of a `RoutesDefinition` containing semantic declarations 
preserve them. The MCP
+route transformation tool also preserves declarations in XML-to-YAML, 
YAML-to-XML, and
+Java-to-YAML/XML conversions. Conversions that load a Camel context export the 
registered
+questions with their effective numeric policies. Runtime route dumps such as 
`camel.main.dumpRoutes=yaml` contain only
 the routes and omit the question declarations. Supply the declarations 
separately before
 reloading those dumps.
 
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models-app/semanticQuestionDefinition.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models-app/semanticQuestionDefinition.json
index 12d4bbcbe256..a530faab4979 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models-app/semanticQuestionDefinition.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models-app/semanticQuestionDefinition.json
@@ -15,9 +15,9 @@
     "name": { "index": 0, "kind": "attribute", "displayName": "Name", "group": 
"common", "required": true, "type": "string", "javaType": "java.lang.String", 
"deprecated": false, "autowired": false, "secret": false, "description": "The 
context-wide question name." },
     "type": { "index": 1, "kind": "attribute", "displayName": "Type", "group": 
"common", "required": true, "type": "enum", "javaType": "java.lang.String", 
"enum": [ "boolean", "choice", "score" ], "deprecated": false, "autowired": 
false, "secret": false, "description": "The question type." },
     "state": { "index": 2, "kind": "attribute", "displayName": "State", 
"group": "common", "required": false, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "autowired": false, "secret": false, 
"description": "The Simple expression selecting the message state." },
-    "threshold": { "index": 3, "kind": "attribute", "displayName": 
"Threshold", "group": "common", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "description": "The boolean decision threshold." },
-    "uncertainty": { "index": 4, "kind": "attribute", "displayName": 
"Uncertainty", "group": "common", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "description": "The boolean uncertainty band." },
-    "uncertaintyPolicy": { "index": 5, "kind": "attribute", "displayName": 
"Uncertainty Policy", "group": "common", "required": false, "type": "enum", 
"javaType": "java.lang.String", "enum": [ "fail", "non-match" ], "deprecated": 
false, "autowired": false, "secret": false, "description": "The boolean 
uncertainty policy." },
+    "threshold": { "index": 3, "kind": "attribute", "displayName": 
"Threshold", "group": "common", "required": false, "type": "number", 
"javaType": "java.lang.Double", "deprecated": false, "autowired": false, 
"secret": false, "defaultValue": "0.5", "description": "The boolean decision 
threshold." },
+    "uncertainty": { "index": 4, "kind": "attribute", "displayName": 
"Uncertainty", "group": "common", "required": false, "type": "number", 
"javaType": "java.lang.Double", "deprecated": false, "autowired": false, 
"secret": false, "defaultValue": "0", "description": "The boolean uncertainty 
band." },
+    "uncertaintyPolicy": { "index": 5, "kind": "attribute", "displayName": 
"Uncertainty Policy", "group": "common", "required": false, "type": "enum", 
"javaType": "java.lang.String", "enum": [ "fail", "non-match" ], "deprecated": 
false, "autowired": false, "secret": false, "defaultValue": "fail", 
"description": "The boolean uncertainty policy." },
     "instructions": { "index": 6, "kind": "element", "displayName": 
"Instructions", "group": "common", "required": true, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "description": "Instructions describing the judgment to make." 
},
     "criterion": { "index": 7, "kind": "element", "displayName": "Criterion", 
"group": "common", "required": false, "type": "array", "javaType": 
"java.util.List<org.apache.camel.model.PropertyDefinition>", "deprecated": 
false, "autowired": false, "secret": false, "description": "Named choice 
criteria, or optional true\/false boolean criteria." },
     "level": { "index": 8, "kind": "element", "displayName": "Level", "group": 
"common", "required": false, "type": "array", "javaType": 
"java.util.List<java.lang.String>", "deprecated": false, "autowired": false, 
"secret": false, "description": "Ordered descriptive levels for a score 
question." }
diff --git a/components/camel-ai/camel-semantic/pom.xml 
b/components/camel-ai/camel-semantic/pom.xml
index 833a7d9174d3..25e764518c15 100644
--- a/components/camel-ai/camel-semantic/pom.xml
+++ b/components/camel-ai/camel-semantic/pom.xml
@@ -52,6 +52,11 @@
             <artifactId>camel-xml-io-dsl</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-xml-jaxb-dsl</artifactId>
+            <scope>test</scope>
+        </dependency>
         <dependency>
             <groupId>org.apache.camel</groupId>
             <artifactId>camel-main</artifactId>
diff --git 
a/components/camel-ai/camel-semantic/src/main/docs/semantic-language.adoc 
b/components/camel-ai/camel-semantic/src/main/docs/semantic-language.adoc
index c360bacb879c..06e37072ac63 100644
--- a/components/camel-ai/camel-semantic/src/main/docs/semantic-language.adoc
+++ b/components/camel-ai/camel-semantic/src/main/docs/semantic-language.adoc
@@ -167,14 +167,25 @@ XML declarations are loaded before route references are 
initialized, including t
 resource loaded together. Reload replaces the source's questions;
 removing its declarations or deleting the resource removes obsolete 
definitions.
 
+The JAXB XML loader also supports declarations inside a `routes` document 
using the
+`http://camel.apache.org/schema/spring` namespace. With JAXB, load a separate 
declaration
+resource before the routes that reference it.
+
+The numeric `threshold` and `uncertainty` options accept property placeholders 
in all three
+DSLs. For example, Java accepts `.threshold("{{semantic.threshold:0.5}}")`, 
and XML accepts
+`threshold="{{semantic.threshold:0.5}}"`. Values are resolved and validated 
when declarations
+are registered; Java model exports preserve the placeholder text.
+
 Both Java and XML declarations require `camel-semantic` and use the same 
context-wide registry,
 validation, defaults and adapters as YAML. They do not require 
`camel-yaml-dsl`. Their questions
 can also be selected together using `refs:name1,name2`, as described below.
 
 === Exporting declarations
 
-Direct exports of a `RoutesDefinition` containing semantic declarations 
preserve them, as does
-XML-to-YAML conversion. Runtime route dumps such as 
`camel.main.dumpRoutes=yaml` contain only
+Direct exports of a `RoutesDefinition` containing semantic declarations 
preserve them. The MCP
+route transformation tool also preserves declarations in XML-to-YAML, 
YAML-to-XML, and
+Java-to-YAML/XML conversions. Conversions that load a Camel context export the 
registered
+questions with their effective numeric policies. Runtime route dumps such as 
`camel.main.dumpRoutes=yaml` contain only
 the routes and omit the question declarations. Supply the declarations 
separately before
 reloading those dumps.
 
diff --git 
a/components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/DefaultSemanticDefinitionConfigurer.java
 
b/components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/DefaultSemanticDefinitionConfigurer.java
index 871f378049fb..9971fe18596d 100644
--- 
a/components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/DefaultSemanticDefinitionConfigurer.java
+++ 
b/components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/DefaultSemanticDefinitionConfigurer.java
@@ -17,13 +17,14 @@
 package org.apache.camel.semantic;
 
 import java.util.LinkedHashMap;
+import java.util.Locale;
 import java.util.Map;
 
 import org.apache.camel.CamelContext;
 import org.apache.camel.model.PropertyDefinition;
 import org.apache.camel.model.app.SemanticDefinition;
-import org.apache.camel.model.app.SemanticDefinitionConfigurer;
 import org.apache.camel.model.app.SemanticQuestionDefinition;
+import org.apache.camel.model.spi.SemanticDefinitionConfigurer;
 import org.apache.camel.spi.Resource;
 import org.apache.camel.util.StringHelper;
 
@@ -42,7 +43,7 @@ public class DefaultSemanticDefinitionConfigurer implements 
SemanticDefinitionCo
                     throw new IllegalArgumentException("Duplicate semantic 
question: " + name);
                 }
                 try {
-                    questions.put(name, question(question));
+                    questions.put(name, question(context, question));
                 } catch (IllegalArgumentException e) {
                     throw new IllegalArgumentException("Invalid semantic 
question '" + name + "': " + e.getMessage(), e);
                 }
@@ -52,7 +53,29 @@ public class DefaultSemanticDefinitionConfigurer implements 
SemanticDefinitionCo
         SemanticQuestions.get(context).replace("model:" + source, resource, 
questions);
     }
 
-    private static SemanticQuestion question(SemanticQuestionDefinition 
definition) {
+    /** Export a snapshot of the registered questions and their effective 
decision policies. */
+    public static SemanticDefinition getDefinition(CamelContext context) {
+        SemanticQuestions questions = 
context.getCamelContextExtension().getContextPlugin(SemanticQuestions.class);
+        if (questions == null) {
+            return null;
+        }
+        SemanticDefinition definition = new SemanticDefinition();
+        
questions.snapshot().entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(entry
 -> {
+            SemanticQuestion question = entry.getValue();
+            SemanticQuestionDefinition target = 
definition.question(entry.getKey())
+                    .type(question.getType().name().toLowerCase(Locale.ROOT))
+                    
.instructions(question.getInstructions()).state(question.getState());
+            question.getCriteria().forEach(target::criterion);
+            question.getLevels().forEach(target::level);
+            if (question.getType() == SemanticQuestion.Type.BOOLEAN) {
+                
target.threshold(question.getThreshold()).uncertainty(question.getUncertainty())
+                        
.uncertaintyPolicy(question.getUncertaintyPolicy().name().toLowerCase(Locale.ROOT).replace('_',
 '-'));
+            }
+        });
+        return definition;
+    }
+
+    private static SemanticQuestion question(CamelContext context, 
SemanticQuestionDefinition definition) {
         if (definition.getType() == null) {
             throw new IllegalArgumentException("Question type is required");
         }
@@ -72,16 +95,16 @@ public class DefaultSemanticDefinitionConfigurer implements 
SemanticDefinitionCo
         return new SemanticQuestion(
                 type, definition.getInstructions(), definition.getState(), 
criteria,
                 definition.getLevels(),
-                definition.getThreshold() == null ? 0.5 : 
parseDouble(definition.getThreshold(), "threshold"),
-                definition.getUncertainty() == null ? 0 : 
parseDouble(definition.getUncertainty(), "uncertainty"),
+                definition.getThreshold() == null ? 0.5 : parseDouble(context, 
definition.getThreshold(), "threshold"),
+                definition.getUncertainty() == null ? 0 : parseDouble(context, 
definition.getUncertainty(), "uncertainty"),
                 definition.getUncertaintyPolicy() == null
                         ? SemanticQuestion.UncertaintyPolicy.FAIL
                         : enumeration(definition.getUncertaintyPolicy(), 
SemanticQuestion.UncertaintyPolicy.class));
     }
 
-    private static double parseDouble(String value, String field) {
+    private static double parseDouble(CamelContext context, String value, 
String field) {
         try {
-            return Double.parseDouble(value);
+            return 
Double.parseDouble(context.resolvePropertyPlaceholders(value));
         } catch (NumberFormatException e) {
             throw new IllegalArgumentException(field + " must be a valid 
number: " + value, e);
         }
diff --git 
a/components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/SemanticQuestions.java
 
b/components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/SemanticQuestions.java
index cccc22e48ae6..5114f0107972 100644
--- 
a/components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/SemanticQuestions.java
+++ 
b/components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/SemanticQuestions.java
@@ -90,6 +90,10 @@ public final class SemanticQuestions {
                 .forEach(location -> replace(location, Map.of()));
     }
 
+    Map<String, SemanticQuestion> snapshot() {
+        return questions;
+    }
+
     public SemanticQuestion get(String name) {
         SemanticQuestion question = questions.get(name);
         if (question == null) {
diff --git 
a/components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/yaml/SemanticDefinitionDeserializer.java
 
b/components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/yaml/SemanticDefinitionDeserializer.java
index b4f049f8aca5..f2cd3ef54248 100644
--- 
a/components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/yaml/SemanticDefinitionDeserializer.java
+++ 
b/components/camel-ai/camel-semantic/src/main/java/org/apache/camel/semantic/yaml/SemanticDefinitionDeserializer.java
@@ -57,7 +57,7 @@ public class SemanticDefinitionDeserializer extends 
YamlDeserializerSupport impl
 
     @Override
     public Object construct(Node node) {
-        read(node);
+        read(getDeserializationContext(node).getCamelContext(), node);
         // Registration happens once for the entire resource, including 
declarations after routes.
         return (CamelContextCustomizer) context -> {
         };
@@ -76,7 +76,7 @@ public class SemanticDefinitionDeserializer extends 
YamlDeserializerSupport impl
             }
             for (NodeTuple tuple : mapping.getValue()) {
                 if ("semantic".equals(asText(tuple.getKeyNode()))) {
-                    read(tuple.getValueNode()).forEach((name, question) -> {
+                    read(dc.getCamelContext(), 
tuple.getValueNode()).forEach((name, question) -> {
                         if (definitions.putIfAbsent(name, question) != null) {
                             throw new YamlDeserializationException(
                                     tuple.getValueNode(), "Duplicate semantic 
question: " + name);
@@ -98,7 +98,7 @@ public class SemanticDefinitionDeserializer extends 
YamlDeserializerSupport impl
         }
     }
 
-    private static Map<String, SemanticQuestion> read(Node node) {
+    private static Map<String, SemanticQuestion> read(CamelContext context, 
Node node) {
         Map<String, Node> semantic = fields(node, "semantic declaration");
         if (!semantic.keySet().equals(Set.of("question"))) {
             throw new YamlDeserializationException(node, "Semantic declaration 
requires only question");
@@ -109,7 +109,7 @@ public class SemanticDefinitionDeserializer extends 
YamlDeserializerSupport impl
                 throw new YamlDeserializationException(definition, "Semantic 
question requires a nonblank name");
             }
             try {
-                result.put(name, readQuestion(name, definition));
+                result.put(name, readQuestion(context, name, definition));
             } catch (IllegalArgumentException e) {
                 throw new YamlDeserializationException(
                         definition, "Invalid semantic question '" + name + "': 
" + e.getMessage(), e);
@@ -118,7 +118,7 @@ public class SemanticDefinitionDeserializer extends 
YamlDeserializerSupport impl
         return result;
     }
 
-    private static SemanticQuestion readQuestion(String name, Node definition) 
{
+    private static SemanticQuestion readQuestion(CamelContext context, String 
name, Node definition) {
         Map<String, Node> values = fields(definition, "semantic question '" + 
name + "'");
         values.forEach((field, value) -> {
             if (!FIELDS.contains(field)) {
@@ -152,7 +152,8 @@ public class SemanticDefinitionDeserializer extends 
YamlDeserializerSupport impl
                 : SemanticQuestion.UncertaintyPolicy.FAIL;
         return new SemanticQuestion(
                 type, asText(values.get("instructions")), 
asText(values.get("state")),
-                criteria, levels, number(values, name, "threshold", 0.5), 
number(values, name, "uncertainty", 0), policy);
+                criteria, levels, number(context, values, name, "threshold", 
0.5),
+                number(context, values, name, "uncertainty", 0), policy);
     }
 
     private static <T extends Enum<T>> T enumeration(Node node, String 
question, String field, Class<T> type) {
@@ -164,14 +165,15 @@ public class SemanticDefinitionDeserializer extends 
YamlDeserializerSupport impl
         }
     }
 
-    private static double number(Map<String, Node> values, String question, 
String name, double fallback) {
+    private static double number(
+            CamelContext context, Map<String, Node> values, String question, 
String name, double fallback) {
         if (!values.containsKey(name)) {
             return fallback;
         }
         Node node = values.get(name);
         String raw = asText(node);
         try {
-            return Double.parseDouble(raw);
+            return 
Double.parseDouble(context.resolvePropertyPlaceholders(raw));
         } catch (NumberFormatException e) {
             throw new YamlDeserializationException(
                     node,
diff --git 
a/components/camel-ai/camel-semantic/src/test/java/org/apache/camel/semantic/SemanticDeclarationDslTest.java
 
b/components/camel-ai/camel-semantic/src/test/java/org/apache/camel/semantic/SemanticDeclarationDslTest.java
index e2b3f13a7f24..6115b5a8c442 100644
--- 
a/components/camel-ai/camel-semantic/src/test/java/org/apache/camel/semantic/SemanticDeclarationDslTest.java
+++ 
b/components/camel-ai/camel-semantic/src/test/java/org/apache/camel/semantic/SemanticDeclarationDslTest.java
@@ -25,8 +25,11 @@ import java.util.stream.Stream;
 
 import org.apache.camel.Exchange;
 import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.dsl.support.RouteBuilderLoaderSupport;
+import org.apache.camel.dsl.xml.jaxb.JaxbXmlRoutesBuilderLoader;
 import org.apache.camel.impl.DefaultCamelContext;
 import org.apache.camel.language.semantic.SemanticLanguage;
+import org.apache.camel.model.SemanticDefinitionHelper;
 import org.apache.camel.model.app.SemanticDefinition;
 import org.apache.camel.spi.Resource;
 import org.apache.camel.support.DefaultExchange;
@@ -113,7 +116,7 @@ class SemanticDeclarationDslTest {
     }
 
     @ParameterizedTest
-    @ValueSource(strings = { "java", "camel", "routes" })
+    @ValueSource(strings = { "java", "camel", "routes", "jaxb" })
     void nativeDeclarationsEvaluateSingleQuestionsAndMixedBatches(String dsl) 
throws Exception {
         if (dsl.equals("java")) {
             context.addRoutes(new RouteBuilder() {
@@ -135,7 +138,16 @@ class SemanticDeclarationDslTest {
                       <setHeader 
name="department"><simple>${exchangeProperty.decision[department]}</simple></setHeader>
                     </route>
                     """;
-            load("questions.xml", document(dsl, route + xmlQuestions()));
+            if (dsl.equals("jaxb")) {
+                try (var loader = new JaxbXmlRoutesBuilderLoader()) {
+                    loader.setCamelContext(context);
+                    loader.start();
+                    context.addRoutes(loader.loadRoutesBuilder(
+                            ResourceHelper.fromString("questions.xml", 
document("routes", route + xmlQuestions()))));
+                }
+            } else {
+                load("questions.xml", document(dsl, route + xmlQuestions()));
+            }
         }
         assertThat(states).isEmpty();
         SemanticQuestion urgent = SemanticQuestions.get(context).get("urgent");
@@ -233,10 +245,10 @@ class SemanticDeclarationDslTest {
         Resource resource = trackResource ? 
ResourceHelper.fromString("questions.xml", "") : null;
         SemanticDefinition definition = new SemanticDefinition();
         definition.question("first").type("boolean").instructions("Valid?");
-        SemanticDefinition.configure(context, resource, "declared-source", 
definition);
+        SemanticDefinitionHelper.configure(context, resource, 
"declared-source", definition);
         SemanticDefinition replacement = new SemanticDefinition();
         
replacement.question("second").type("boolean").instructions("Updated?");
-        SemanticDefinition.configure(context, resource, "declared-source", 
replacement);
+        SemanticDefinitionHelper.configure(context, resource, 
"declared-source", replacement);
         SemanticQuestions questions = SemanticQuestions.get(context);
         assertThatThrownBy(() -> 
questions.get("first")).hasMessageContaining("Unknown");
         assertThat(questions.get("second")).isNotNull();
@@ -295,6 +307,120 @@ class SemanticDeclarationDslTest {
         assertThat(states).isEmpty();
     }
 
+    @ParameterizedTest
+    @ValueSource(strings = { "java", "xml" })
+    void numericPlaceholdersResolveBeforeValidation(String dsl) throws 
Exception {
+        if (dsl.equals("java")) {
+            context.addRoutes(new RouteBuilder() {
+                @Override
+                public void configure() {
+                    
semanticQuestions().question("urgent").type("boolean").instructions("Urgent?")
+                            
.threshold("{{threshold:0.8}}").uncertainty("{{uncertainty:0.1}}");
+                }
+            });
+        } else {
+            load("questions.xml", document("routes", xmlQuestions()
+                    .replace("threshold=\"0.8\"", 
"threshold=\"{{threshold:0.8}}\"")
+                    .replace("uncertainty=\"0.1\"", 
"uncertainty=\"{{uncertainty:0.1}}\"")));
+        }
+        
assertThat(SemanticQuestions.get(context).get("urgent").getThreshold()).isEqualTo(0.8);
+        
assertThat(SemanticQuestions.get(context).get("urgent").getUncertainty()).isEqualTo(0.1);
+        assertThatThrownBy(() -> load("invalid.xml", document("routes", """
+                <semantic><question name="invalid" type="boolean" 
threshold="{{threshold:abc}}">
+                  <instructions>Urgent?</instructions>
+                </question></semantic>
+                """)))
+                .hasMessageContaining("Invalid semantic question 'invalid': 
threshold must be a valid number")
+                .hasRootCauseInstanceOf(NumberFormatException.class);
+    }
+
+    @ParameterizedTest
+    @ValueSource(strings = { "routes", "camel" })
+    void repeatedUnchangedPreparsePreservesDeclarationsAndBeans(String root) 
throws Exception {
+        var loader = 
PluginHelper.getRoutesLoader(context).getRoutesLoader("xml");
+        Resource resource = ResourceHelper.fromString("questions.xml", 
document(root, xmlQuestions()
+                                                                               
       + (root.equals("camel")
+                                                                               
               ? "<bean name=\"counter\" 
type=\"java.util.concurrent.atomic.AtomicInteger\"/>"
+                                                                               
               : "")));
+        loader.preParseRoute(resource);
+        SemanticQuestion question = 
SemanticQuestions.get(context).get("urgent");
+        Object bean = context.getRegistry().lookupByName("counter");
+        loader.preParseRoute(resource);
+        
assertThat(SemanticQuestions.get(context).get("urgent")).isSameAs(question);
+        if (root.equals("camel")) {
+            assertThat(bean).isNotNull();
+            
assertThat(context.getRegistry().lookupByName("counter")).isSameAs(bean);
+        }
+    }
+
+    @ParameterizedTest
+    @MethodSource("externalFailures")
+    void retryAfterExternalFailureReadsChangedXml(String root, boolean 
failPreparse) throws Exception {
+        var failingLoader = new RouteBuilderLoaderSupport("fail") {
+            @Override
+            public void preParseRoute(Resource resource) {
+                if (failPreparse) {
+                    throw new IllegalStateException("foreign preparse 
failure");
+                }
+            }
+
+            @Override
+            public RouteBuilder doLoadRouteBuilder(Resource resource) {
+                return new RouteBuilder() {
+                    @Override
+                    public void configure() {
+                        throw new IllegalStateException("earlier builder 
failure");
+                    }
+                };
+            }
+        };
+        context.getRegistry().bind("routes-builder-loader-fail", 
failingLoader);
+        Path file = directory.resolve("questions.xml");
+        Files.writeString(file, document(root, xmlQuestions()));
+        Resource resource = ResourceHelper.resolveResource(context, 
file.toUri().toString());
+        Resource broken = ResourceHelper.fromString("broken.fail", "");
+        var loader = PluginHelper.getRoutesLoader(context);
+        assertThatThrownBy(() -> loader.loadRoutes(failPreparse
+                ? List.of(resource, broken) : List.of(broken, resource)))
+                .hasMessageContaining(failPreparse ? "foreign preparse 
failure" : "earlier builder failure");
+        
assertThat(SemanticQuestions.get(context).get("department").getState()).isEqualTo("${header.myState}");
+        Files.writeString(file, document(root, 
xmlQuestions().replace("header.myState", "header.updated") + """
+                <route id="updated"><from uri="direct:updated"/>
+                  <setBody><language 
language="semantic">ref:department</language></setBody>
+                </route>
+                """));
+        loader.loadRoutes(resource);
+        
assertThat(SemanticQuestions.get(context).get("department").getState()).isEqualTo("${header.updated}");
+        try (var template = context.createProducerTemplate()) {
+            assertThat(template.requestBodyAndHeader("direct:updated", "old", 
"updated", "new")).isEqualTo("billing");
+        }
+        assertThat(states).containsExactly("new");
+    }
+
+    @ParameterizedTest
+    @ValueSource(booleans = { false, true })
+    void deferredBeansKeepTheirOwningResourceWhenAnotherBuilderFails(boolean 
removeBean) throws Exception {
+        var loader = PluginHelper.getRoutesLoader(context);
+        Resource first = ResourceHelper.fromString("first.xml", 
document("camel", xmlQuestions()));
+        Resource second = ResourceHelper.fromString("second.xml", 
document("camel", """
+                <bean name="deferred" type="java.lang.String" 
factoryBean="factory" factoryMethod="create"/>
+                """));
+        assertThatThrownBy(() -> loader.loadRoutes(List.of(first, 
second))).hasMessageContaining("Error creating bean");
+        if (removeBean) {
+            loader.loadRoutes(ResourceHelper.fromString("second.xml", 
document("camel", "")));
+            
assertThat(context.getRegistry().lookupByName("deferred")).isNull();
+        } else {
+            context.getRegistry().bind("factory", new DeferredBeanFactory());
+            loader.loadRoutes(second);
+            
assertThat(context.getRegistry().lookupByName("deferred")).isEqualTo("created");
+        }
+    }
+
+    static Stream<Arguments> externalFailures() {
+        return Stream.of(Arguments.of("routes", true), Arguments.of("routes", 
false),
+                Arguments.of("camel", true), Arguments.of("camel", false));
+    }
+
     @Test
     void failedResourceBatchDoesNotReuseEarlierCachedDeclarationsOnRetry() 
throws Exception {
         String second = "<semantic><question name=\"second\" type=\"boolean\">"
@@ -378,6 +504,12 @@ class SemanticDeclarationDslTest {
         
PluginHelper.getRoutesLoader(context).loadRoutes(ResourceHelper.fromString(location,
 xml));
     }
 
+    public static class DeferredBeanFactory {
+        public static String create() {
+            return "created";
+        }
+    }
+
     private static class TestWatcher extends RouteWatcherReloadStrategy {
         void reload(Resource resource) {
             onRouteReload(List.of(resource), false);
diff --git 
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/app/semanticQuestionDefinition.json
 
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/app/semanticQuestionDefinition.json
index 12d4bbcbe256..a530faab4979 100644
--- 
a/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/app/semanticQuestionDefinition.json
+++ 
b/core/camel-core-model/src/generated/resources/META-INF/org/apache/camel/model/app/semanticQuestionDefinition.json
@@ -15,9 +15,9 @@
     "name": { "index": 0, "kind": "attribute", "displayName": "Name", "group": 
"common", "required": true, "type": "string", "javaType": "java.lang.String", 
"deprecated": false, "autowired": false, "secret": false, "description": "The 
context-wide question name." },
     "type": { "index": 1, "kind": "attribute", "displayName": "Type", "group": 
"common", "required": true, "type": "enum", "javaType": "java.lang.String", 
"enum": [ "boolean", "choice", "score" ], "deprecated": false, "autowired": 
false, "secret": false, "description": "The question type." },
     "state": { "index": 2, "kind": "attribute", "displayName": "State", 
"group": "common", "required": false, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "autowired": false, "secret": false, 
"description": "The Simple expression selecting the message state." },
-    "threshold": { "index": 3, "kind": "attribute", "displayName": 
"Threshold", "group": "common", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "description": "The boolean decision threshold." },
-    "uncertainty": { "index": 4, "kind": "attribute", "displayName": 
"Uncertainty", "group": "common", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "description": "The boolean uncertainty band." },
-    "uncertaintyPolicy": { "index": 5, "kind": "attribute", "displayName": 
"Uncertainty Policy", "group": "common", "required": false, "type": "enum", 
"javaType": "java.lang.String", "enum": [ "fail", "non-match" ], "deprecated": 
false, "autowired": false, "secret": false, "description": "The boolean 
uncertainty policy." },
+    "threshold": { "index": 3, "kind": "attribute", "displayName": 
"Threshold", "group": "common", "required": false, "type": "number", 
"javaType": "java.lang.Double", "deprecated": false, "autowired": false, 
"secret": false, "defaultValue": "0.5", "description": "The boolean decision 
threshold." },
+    "uncertainty": { "index": 4, "kind": "attribute", "displayName": 
"Uncertainty", "group": "common", "required": false, "type": "number", 
"javaType": "java.lang.Double", "deprecated": false, "autowired": false, 
"secret": false, "defaultValue": "0", "description": "The boolean uncertainty 
band." },
+    "uncertaintyPolicy": { "index": 5, "kind": "attribute", "displayName": 
"Uncertainty Policy", "group": "common", "required": false, "type": "enum", 
"javaType": "java.lang.String", "enum": [ "fail", "non-match" ], "deprecated": 
false, "autowired": false, "secret": false, "defaultValue": "fail", 
"description": "The boolean uncertainty policy." },
     "instructions": { "index": 6, "kind": "element", "displayName": 
"Instructions", "group": "common", "required": true, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": false, "description": "Instructions describing the judgment to make." 
},
     "criterion": { "index": 7, "kind": "element", "displayName": "Criterion", 
"group": "common", "required": false, "type": "array", "javaType": 
"java.util.List<org.apache.camel.model.PropertyDefinition>", "deprecated": 
false, "autowired": false, "secret": false, "description": "Named choice 
criteria, or optional true\/false boolean criteria." },
     "level": { "index": 8, "kind": "element", "displayName": "Level", "group": 
"common", "required": false, "type": "array", "javaType": 
"java.util.List<java.lang.String>", "deprecated": false, "autowired": false, 
"secret": false, "description": "Ordered descriptive levels for a score 
question." }
diff --git 
a/core/camel-core-model/src/main/java/org/apache/camel/builder/RouteBuilder.java
 
b/core/camel-core-model/src/main/java/org/apache/camel/builder/RouteBuilder.java
index 5897665f64f1..5e2e23be0065 100644
--- 
a/core/camel-core-model/src/main/java/org/apache/camel/builder/RouteBuilder.java
+++ 
b/core/camel-core-model/src/main/java/org/apache/camel/builder/RouteBuilder.java
@@ -46,6 +46,7 @@ import org.apache.camel.model.RouteDefinition;
 import org.apache.camel.model.RouteTemplateDefinition;
 import org.apache.camel.model.RouteTemplatesDefinition;
 import org.apache.camel.model.RoutesDefinition;
+import org.apache.camel.model.SemanticDefinitionHelper;
 import org.apache.camel.model.TemplatedRouteDefinition;
 import org.apache.camel.model.TemplatedRoutesDefinition;
 import org.apache.camel.model.app.SemanticDefinition;
@@ -202,6 +203,8 @@ public abstract class RouteBuilder extends BuilderSupport 
implements RoutesBuild
 
     /**
      * Declares named semantic questions for use with the semantic language. 
Requires camel-semantic at runtime.
+     *
+     * @since 4.23
      */
     public SemanticDefinition semanticQuestions() {
         if (getRouteCollection().getSemantic() == null) {
@@ -925,7 +928,7 @@ public abstract class RouteBuilder extends BuilderSupport 
implements RoutesBuild
                 String source = getResource() != null
                         ? getResource().getLocation()
                         : "java:" + 
camelContext.getUuidGenerator().generateUuid();
-                SemanticDefinition.configure(camelContext, getResource(), 
source, getRouteCollection().getSemantic());
+                SemanticDefinitionHelper.configure(camelContext, 
getResource(), source, getRouteCollection().getSemantic());
             }
 
             // remember the source resource
diff --git 
a/core/camel-core-model/src/main/java/org/apache/camel/model/RoutesDefinition.java
 
b/core/camel-core-model/src/main/java/org/apache/camel/model/RoutesDefinition.java
index f1ae6086fb01..b171aa164699 100644
--- 
a/core/camel-core-model/src/main/java/org/apache/camel/model/RoutesDefinition.java
+++ 
b/core/camel-core-model/src/main/java/org/apache/camel/model/RoutesDefinition.java
@@ -83,14 +83,6 @@ public class RoutesDefinition extends 
OptionalIdentifiedDefinition<RoutesDefinit
     @Metadata(description = "Named semantic question declarations.")
     private SemanticDefinition semantic;
 
-    public SemanticDefinition getSemantic() {
-        return semantic;
-    }
-
-    public void setSemantic(SemanticDefinition semantic) {
-        this.semantic = semantic;
-    }
-
     public RoutesDefinition() {
     }
 
@@ -121,6 +113,14 @@ public class RoutesDefinition extends 
OptionalIdentifiedDefinition<RoutesDefinit
         this.routes = routes;
     }
 
+    public SemanticDefinition getSemantic() {
+        return semantic;
+    }
+
+    public void setSemantic(SemanticDefinition semantic) {
+        this.semantic = semantic;
+    }
+
     public List<InterceptFromDefinition> getInterceptFroms() {
         return interceptFroms;
     }
diff --git 
a/core/camel-core-model/src/main/java/org/apache/camel/model/app/SemanticDefinition.java
 
b/core/camel-core-model/src/main/java/org/apache/camel/model/SemanticDefinitionHelper.java
similarity index 63%
copy from 
core/camel-core-model/src/main/java/org/apache/camel/model/app/SemanticDefinition.java
copy to 
core/camel-core-model/src/main/java/org/apache/camel/model/SemanticDefinitionHelper.java
index ac1f2f50c0dd..95f4a8b1c922 100644
--- 
a/core/camel-core-model/src/main/java/org/apache/camel/model/app/SemanticDefinition.java
+++ 
b/core/camel-core-model/src/main/java/org/apache/camel/model/SemanticDefinitionHelper.java
@@ -14,43 +14,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.camel.model.app;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import jakarta.xml.bind.annotation.XmlAccessType;
-import jakarta.xml.bind.annotation.XmlAccessorType;
-import jakarta.xml.bind.annotation.XmlElement;
-import jakarta.xml.bind.annotation.XmlType;
+package org.apache.camel.model;
 
 import org.apache.camel.CamelContext;
-import org.apache.camel.spi.Metadata;
+import org.apache.camel.model.app.SemanticDefinition;
+import org.apache.camel.model.spi.SemanticDefinitionConfigurer;
 import org.apache.camel.spi.Resource;
 
-/** Named, provider-independent semantic question declarations. */
-@Metadata(label = "configuration")
-@XmlType(name = "semanticDefinition")
-@XmlAccessorType(XmlAccessType.FIELD)
-public class SemanticDefinition {
-    @XmlElement(name = "question")
-    @Metadata(description = "Named semantic questions shared by routes in this 
Camel context.")
-    private List<SemanticQuestionDefinition> questions = new ArrayList<>();
-
-    public List<SemanticQuestionDefinition> getQuestions() {
-        return questions;
-    }
-
-    public void setQuestions(List<SemanticQuestionDefinition> questions) {
-        this.questions = questions;
-    }
-
-    /** Declare a named semantic question. */
-    public SemanticQuestionDefinition question(String name) {
-        SemanticQuestionDefinition question = new SemanticQuestionDefinition();
-        question.setName(name);
-        questions.add(question);
-        return question;
+/** Connects semantic declarations to the optional semantic language module. */
+public final class SemanticDefinitionHelper {
+    private SemanticDefinitionHelper() {
     }
 
     /** Register declarations before route initialization, without requiring 
semantic support for ordinary routes. */
diff --git 
a/core/camel-core-model/src/main/java/org/apache/camel/model/app/SemanticDefinition.java
 
b/core/camel-core-model/src/main/java/org/apache/camel/model/app/SemanticDefinition.java
index ac1f2f50c0dd..ae3fe8a55e8e 100644
--- 
a/core/camel-core-model/src/main/java/org/apache/camel/model/app/SemanticDefinition.java
+++ 
b/core/camel-core-model/src/main/java/org/apache/camel/model/app/SemanticDefinition.java
@@ -24,9 +24,7 @@ import jakarta.xml.bind.annotation.XmlAccessorType;
 import jakarta.xml.bind.annotation.XmlElement;
 import jakarta.xml.bind.annotation.XmlType;
 
-import org.apache.camel.CamelContext;
 import org.apache.camel.spi.Metadata;
-import org.apache.camel.spi.Resource;
 
 /** Named, provider-independent semantic question declarations. */
 @Metadata(label = "configuration")
@@ -52,24 +50,4 @@ public class SemanticDefinition {
         questions.add(question);
         return question;
     }
-
-    /** Register declarations before route initialization, without requiring 
semantic support for ordinary routes. */
-    public static void configure(
-            CamelContext context, Resource resource, String source, 
SemanticDefinition definition) {
-        SemanticDefinitionConfigurer configurer
-                = 
context.getCamelContextExtension().getContextPlugin(SemanticDefinitionConfigurer.class);
-        if (configurer == null) {
-            if (definition == null || definition.getQuestions().isEmpty()) {
-                return;
-            }
-            // Concurrent discovery may create equivalent instances of the 
stateless default configurer.
-            // The semantic module synchronizes access to shared question 
state.
-            configurer = 
context.getCamelContextExtension().getDefaultFactoryFinder()
-                    .newInstance("semantic-configurer", 
SemanticDefinitionConfigurer.class)
-                    .orElseThrow(() -> new IllegalArgumentException(
-                            "Semantic question declarations require 
camel-semantic on the classpath"));
-            
context.getCamelContextExtension().addContextPlugin(SemanticDefinitionConfigurer.class,
 configurer);
-        }
-        configurer.configure(context, resource, source, definition);
-    }
 }
diff --git 
a/core/camel-core-model/src/main/java/org/apache/camel/model/app/SemanticQuestionDefinition.java
 
b/core/camel-core-model/src/main/java/org/apache/camel/model/app/SemanticQuestionDefinition.java
index 1a5c70c833c3..15f4f6787c65 100644
--- 
a/core/camel-core-model/src/main/java/org/apache/camel/model/app/SemanticQuestionDefinition.java
+++ 
b/core/camel-core-model/src/main/java/org/apache/camel/model/app/SemanticQuestionDefinition.java
@@ -43,13 +43,13 @@ public class SemanticQuestionDefinition {
     @Metadata(description = "The Simple expression selecting the message 
state.")
     private String state;
     @XmlAttribute
-    @Metadata(description = "The boolean decision threshold.")
+    @Metadata(javaType = "java.lang.Double", defaultValue = "0.5", description 
= "The boolean decision threshold.")
     private String threshold;
     @XmlAttribute
-    @Metadata(description = "The boolean uncertainty band.")
+    @Metadata(javaType = "java.lang.Double", defaultValue = "0", description = 
"The boolean uncertainty band.")
     private String uncertainty;
     @XmlAttribute
-    @Metadata(enums = "fail,non-match", description = "The boolean uncertainty 
policy.")
+    @Metadata(defaultValue = "fail", enums = "fail,non-match", description = 
"The boolean uncertainty policy.")
     private String uncertaintyPolicy;
     @XmlElement(required = true)
     @Metadata(required = true, description = "Instructions describing the 
judgment to make.")
@@ -147,13 +147,23 @@ public class SemanticQuestionDefinition {
 
     /** The boolean decision threshold. */
     public SemanticQuestionDefinition threshold(double threshold) {
-        this.threshold = Double.toString(threshold);
+        return threshold(Double.toString(threshold));
+    }
+
+    /** The boolean decision threshold, allowing property placeholders. */
+    public SemanticQuestionDefinition threshold(String threshold) {
+        this.threshold = threshold;
         return this;
     }
 
     /** The boolean uncertainty band. */
     public SemanticQuestionDefinition uncertainty(double uncertainty) {
-        this.uncertainty = Double.toString(uncertainty);
+        return uncertainty(Double.toString(uncertainty));
+    }
+
+    /** The boolean uncertainty band, allowing property placeholders. */
+    public SemanticQuestionDefinition uncertainty(String uncertainty) {
+        this.uncertainty = uncertainty;
         return this;
     }
 
diff --git 
a/core/camel-core-model/src/main/java/org/apache/camel/model/app/SemanticDefinitionConfigurer.java
 
b/core/camel-core-model/src/main/java/org/apache/camel/model/spi/SemanticDefinitionConfigurer.java
similarity index 93%
rename from 
core/camel-core-model/src/main/java/org/apache/camel/model/app/SemanticDefinitionConfigurer.java
rename to 
core/camel-core-model/src/main/java/org/apache/camel/model/spi/SemanticDefinitionConfigurer.java
index 49977893df9d..06485ca98106 100644
--- 
a/core/camel-core-model/src/main/java/org/apache/camel/model/app/SemanticDefinitionConfigurer.java
+++ 
b/core/camel-core-model/src/main/java/org/apache/camel/model/spi/SemanticDefinitionConfigurer.java
@@ -14,9 +14,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.camel.model.app;
+package org.apache.camel.model.spi;
 
 import org.apache.camel.CamelContext;
+import org.apache.camel.model.app.SemanticDefinition;
 import org.apache.camel.spi.Resource;
 
 /** Installs semantic declarations using the optional semantic language 
module. */
diff --git 
a/core/camel-java-io/src/main/java/org/apache/camel/java/out/JavaDslModelWriterSupport.java
 
b/core/camel-java-io/src/main/java/org/apache/camel/java/out/JavaDslModelWriterSupport.java
index 67d18af71fc0..fa651e1963c8 100644
--- 
a/core/camel-java-io/src/main/java/org/apache/camel/java/out/JavaDslModelWriterSupport.java
+++ 
b/core/camel-java-io/src/main/java/org/apache/camel/java/out/JavaDslModelWriterSupport.java
@@ -1151,10 +1151,10 @@ public abstract class JavaDslModelWriterSupport {
                 
result.append(".state(").append(quote(question.getState())).append(")");
             }
             if (question.getThreshold() != null) {
-                
result.append(".threshold(").append(Double.parseDouble(question.getThreshold())).append(")");
+                
result.append(".threshold(").append(quote(question.getThreshold())).append(")");
             }
             if (question.getUncertainty() != null) {
-                
result.append(".uncertainty(").append(Double.parseDouble(question.getUncertainty())).append(")");
+                
result.append(".uncertainty(").append(quote(question.getUncertainty())).append(")");
             }
             if (question.getUncertaintyPolicy() != null) {
                 
result.append(".uncertaintyPolicy(").append(quote(question.getUncertaintyPolicy())).append(")");
diff --git 
a/core/camel-java-io/src/test/java/org/apache/camel/java/out/JavaDslCompileTest.java
 
b/core/camel-java-io/src/test/java/org/apache/camel/java/out/JavaDslCompileTest.java
index a8a462e986bb..ab3fc4fad7b8 100644
--- 
a/core/camel-java-io/src/test/java/org/apache/camel/java/out/JavaDslCompileTest.java
+++ 
b/core/camel-java-io/src/test/java/org/apache/camel/java/out/JavaDslCompileTest.java
@@ -139,12 +139,25 @@ public class JavaDslCompileTest {
             RoutesDefinition routes = new ModelParser(input, 
NAMESPACE).parseRoutesDefinition().orElseThrow();
             String java = new LwModelToJavaDumper().dumpModelAsJava(null, 
routes);
             
assertTrue(java.contains("semanticQuestions().question(\"department\")"));
-            assertTrue(java.contains(".threshold(0.8)"));
+            assertTrue(java.contains(".threshold(\"0.8\")"));
             List<String> errors = compile("SemanticRoutes", 
wrapInRouteBuilder("SemanticRoutes", List.of(java)));
             assertTrue(errors.isEmpty(), () -> errors + "\n" + java);
         }
     }
 
+    @Test
+    void semanticNumericPlaceholdersSurviveJavaExport() throws Exception {
+        try (var input = 
Files.newInputStream(XML_IO_RESOURCES.resolve("semantic-routes.xml"))) {
+            RoutesDefinition routes = new ModelParser(input, 
NAMESPACE).parseRoutesDefinition().orElseThrow();
+            
routes.getSemantic().getQuestions().get(0).threshold("{{threshold}}").uncertainty("{{uncertainty}}");
+            String java = new LwModelToJavaDumper().dumpModelAsJava(null, 
routes);
+            assertTrue(java.contains(".threshold(\"{{threshold}}\")"));
+            assertTrue(java.contains(".uncertainty(\"{{uncertainty}}\")"));
+            List<String> errors = compile("SemanticPlaceholders", 
wrapInRouteBuilder("SemanticPlaceholders", List.of(java)));
+            assertTrue(errors.isEmpty(), () -> errors + "\n" + java);
+        }
+    }
+
     @ParameterizedTest(name = "{0}")
     @MethodSource("xmlRouteFiles")
     void xmlToJavaDslCompiles(String xmlFile) throws Exception {
diff --git 
a/core/camel-xml-io/src/generated/java/org/apache/camel/xml/out/ModelWriter.java
 
b/core/camel-xml-io/src/generated/java/org/apache/camel/xml/out/ModelWriter.java
index d6a50e2a08bf..d976097922d7 100644
--- 
a/core/camel-xml-io/src/generated/java/org/apache/camel/xml/out/ModelWriter.java
+++ 
b/core/camel-xml-io/src/generated/java/org/apache/camel/xml/out/ModelWriter.java
@@ -2144,9 +2144,9 @@ public class ModelWriter extends BaseWriter {
         doWriteAttribute("name", def.getName(), null);
         doWriteAttribute("type", def.getType(), null);
         doWriteAttribute("state", def.getState(), null);
-        doWriteAttribute("threshold", def.getThreshold(), null);
-        doWriteAttribute("uncertainty", def.getUncertainty(), null);
-        doWriteAttribute("uncertaintyPolicy", def.getUncertaintyPolicy(), 
null);
+        doWriteAttribute("threshold", def.getThreshold(), "0.5");
+        doWriteAttribute("uncertainty", def.getUncertainty(), "0");
+        doWriteAttribute("uncertaintyPolicy", def.getUncertaintyPolicy(), 
"fail");
         doWriteElement("instructions", def.getInstructions(), 
this::doWriteString);
         doWriteList(null, "criterion", def.getCriteria(), 
this::doWritePropertyDefinition);
         doWriteList(null, "level", def.getLevels(), this::doWriteString);
diff --git a/dsl/camel-jbang/camel-jbang-mcp/pom.xml 
b/dsl/camel-jbang/camel-jbang-mcp/pom.xml
index 1cff39614136..227616abc2bb 100644
--- a/dsl/camel-jbang/camel-jbang-mcp/pom.xml
+++ b/dsl/camel-jbang/camel-jbang-mcp/pom.xml
@@ -116,6 +116,11 @@
             <artifactId>camel-java-joor-dsl</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-semantic</artifactId>
+        </dependency>
+
         <!-- Swagger/OpenAPI parser for contract-first OpenAPI tools -->
         <dependency>
             <groupId>io.swagger.core.v3</groupId>
@@ -147,6 +152,11 @@
         </dependency>
 
         <!-- test dependencies -->
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-xml-io-dsl</artifactId>
+            <scope>test</scope>
+        </dependency>
         <dependency>
             <groupId>io.quarkus</groupId>
             <artifactId>quarkus-junit</artifactId>
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/TransformTools.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/TransformTools.java
index 8666b7206d00..92cb87bb0fd2 100644
--- 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/TransformTools.java
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/TransformTools.java
@@ -30,8 +30,12 @@ import io.quarkiverse.mcp.server.Tool;
 import io.quarkiverse.mcp.server.ToolArg;
 import io.quarkiverse.mcp.server.ToolCallException;
 import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.model.ExpressionNode;
+import org.apache.camel.model.ProcessorDefinitionHelper;
 import org.apache.camel.model.RouteDefinition;
 import org.apache.camel.model.RoutesDefinition;
+import org.apache.camel.model.app.SemanticDefinition;
+import org.apache.camel.semantic.DefaultSemanticDefinitionConfigurer;
 import org.apache.camel.spi.Resource;
 import org.apache.camel.support.PluginHelper;
 import org.apache.camel.support.ResourceHelper;
@@ -154,6 +158,7 @@ public class TransformTools {
 
             RoutesDefinition rd = new RoutesDefinition();
             rd.setRoutes(routeDefs);
+            
rd.setSemantic(DefaultSemanticDefinitionConfigurer.getDefinition(ctx));
 
             StringWriter sw = new StringWriter();
             new 
org.apache.camel.xml.out.ModelWriter(sw).writeRoutesDefinition(rd);
@@ -179,9 +184,17 @@ public class TransformTools {
                         "Could not parse Java route. Ensure it contains a 
valid route definition.");
             }
 
+            // Java expression clauses are normally materialized when 
processors are created.
+            routeDefs.forEach(route -> 
ProcessorDefinitionHelper.filterTypeInOutputs(route.getOutputs(), 
ExpressionNode.class)
+                    .forEach(ExpressionNode::preCreateProcessor));
+
             if ("yaml".equals(targetFormat)) {
                 YamlModelWriter writer = new YamlModelWriter();
                 List<JsonObject> roots = new ArrayList<>();
+                SemanticDefinition semantic = 
DefaultSemanticDefinitionConfigurer.getDefinition(ctx);
+                if (semantic != null) {
+                    roots.add(writer.writeSemanticDefinition(semantic));
+                }
                 for (RouteDefinition route : routeDefs) {
                     roots.add(writer.writeRouteDefinition(route));
                 }
@@ -189,6 +202,7 @@ public class TransformTools {
             } else {
                 RoutesDefinition rd = new RoutesDefinition();
                 rd.setRoutes(routeDefs);
+                
rd.setSemantic(DefaultSemanticDefinitionConfigurer.getDefinition(ctx));
 
                 StringWriter sw = new StringWriter();
                 new 
org.apache.camel.xml.out.ModelWriter(sw).writeRoutesDefinition(rd);
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/TransformToolsTest.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/TransformToolsTest.java
index 54a613b56314..4c45f5b9eb78 100644
--- 
a/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/TransformToolsTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/TransformToolsTest.java
@@ -16,7 +16,13 @@
  */
 package org.apache.camel.dsl.jbang.core.commands.mcp;
 
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.semantic.SemanticQuestions;
+import org.apache.camel.support.PluginHelper;
+import org.apache.camel.support.ResourceHelper;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
@@ -59,6 +65,70 @@ class TransformToolsTest {
         assertThat(result.result).contains("semantic:", "question:", 
"urgent:", "Urgent?", "ref:urgent");
     }
 
+    @ParameterizedTest
+    @CsvSource({ "yaml,xml", "java,xml", "java,yaml" })
+    void semanticDeclarationsSurviveConversionAndReload(String source, String 
target) throws Exception {
+        String yaml = """
+                - semantic:
+                    question:
+                      urgent:
+                        type: boolean
+                        instructions: Urgent?
+                        threshold: 0.8
+                        uncertainty: 0.1
+                        uncertaintyPolicy: non-match
+                      department:
+                        type: choice
+                        state: ${header.selected}
+                        instructions: Which department?
+                        criteria:
+                          billing: Invoices
+                          technical: Bugs
+                      priority:
+                        type: score
+                        instructions: Priority?
+                        criteria: [Low, High]
+                - route:
+                    from:
+                      uri: direct:input
+                      steps:
+                        - setBody:
+                            expression:
+                              language:
+                                language: semantic
+                                expression: ref:department
+                """;
+        String java = """
+                import org.apache.camel.builder.RouteBuilder;
+                public class SemanticRoute extends RouteBuilder {
+                    public void configure() {
+                        
semanticQuestions().question("urgent").type("boolean").instructions("Urgent?")
+                            
.threshold(0.8).uncertainty(0.1).uncertaintyPolicy("non-match");
+                        
semanticQuestions().question("department").type("choice").instructions("Which 
department?")
+                            .state("${header.selected}").criterion("billing", 
"Invoices").criterion("technical", "Bugs");
+                        
semanticQuestions().question("priority").type("score").instructions("Priority?")
+                            .level("Low").level("High");
+                        from("direct:input").setBody().language("semantic", 
"ref:department");
+                    }
+                }
+                """;
+        var result = createTools().camel_transform_route(source.equals("java") 
? java : yaml, source, target);
+        assertThat(result.supported).isTrue();
+        assertThat(result.result).contains("ref:department");
+        try (var context = new DefaultCamelContext()) {
+            context.build();
+            
PluginHelper.getRoutesLoader(context).loadRoutes(ResourceHelper.fromString("result."
 + target, result.result));
+            var questions = SemanticQuestions.get(context);
+            assertThat(questions.get("urgent").getThreshold()).isEqualTo(0.8);
+            
assertThat(questions.get("urgent").getUncertainty()).isEqualTo(0.1);
+            
assertThat(questions.get("urgent").getUncertaintyPolicy().name()).isEqualTo("NON_MATCH");
+            
assertThat(questions.get("department").getCriteria()).containsEntry("billing", 
"Invoices")
+                    .containsEntry("technical", "Bugs");
+            
assertThat(questions.get("department").getState()).isEqualTo("${header.selected}");
+            
assertThat(questions.get("priority").getLevels()).containsExactly("Low", 
"High");
+        }
+    }
+
     @Test
     void transformYamlToXml() {
         TransformTools tools = createTools();
diff --git 
a/dsl/camel-xml-io-dsl/src/main/java/org/apache/camel/dsl/xml/io/XmlRoutesBuilderLoader.java
 
b/dsl/camel-xml-io-dsl/src/main/java/org/apache/camel/dsl/xml/io/XmlRoutesBuilderLoader.java
index a945d754496f..2319f7c35648 100644
--- 
a/dsl/camel-xml-io-dsl/src/main/java/org/apache/camel/dsl/xml/io/XmlRoutesBuilderLoader.java
+++ 
b/dsl/camel-xml-io-dsl/src/main/java/org/apache/camel/dsl/xml/io/XmlRoutesBuilderLoader.java
@@ -18,6 +18,7 @@ package org.apache.camel.dsl.xml.io;
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
@@ -43,6 +44,7 @@ import org.apache.camel.model.RouteDefinition;
 import org.apache.camel.model.RouteTemplateDefinition;
 import org.apache.camel.model.RouteTemplatesDefinition;
 import org.apache.camel.model.RoutesDefinition;
+import org.apache.camel.model.SemanticDefinitionHelper;
 import org.apache.camel.model.TemplatedRouteDefinition;
 import org.apache.camel.model.TemplatedRoutesDefinition;
 import org.apache.camel.model.app.BeansDefinition;
@@ -94,16 +96,25 @@ public class XmlRoutesBuilderLoader extends 
RouteBuilderLoaderSupport {
     public void preParseRoute(Resource resource) throws Exception {
         // preparsing is done at early stage, so we have a chance to load 
additional beans and populate
         // Camel registry
-        if (preparseDone.getOrDefault(resource.getLocation(), false)) {
-            return;
-        }
         try {
-            XmlStreamInfo xmlInfo = xmlInfo(resource);
+            // Main may preparse twice. Reuse unchanged input, but refresh 
edited resources after a failed batch.
+            Resource snapshot = new CachedResource(resource);
+            Resource previous = resourceCache.get(resource.getLocation());
+            if (preparseDone.getOrDefault(resource.getLocation(), false) && 
previous != null) {
+                try (var current = snapshot.getInputStream(); var cached = 
previous.getInputStream()) {
+                    if (Arrays.equals(current.readAllBytes(), 
cached.readAllBytes())) {
+                        return;
+                    }
+                }
+            }
+            clearCaches(resource.getLocation());
+            resourceCache.put(resource.getLocation(), snapshot);
+            XmlStreamInfo xmlInfo = xmlInfo(snapshot);
             if (xmlInfo.isValid()) {
                 String root = xmlInfo.getRootElementName();
                 SemanticDefinition semantic = null;
                 if ("beans".equals(root) || "blueprint".equals(root) || 
"camel".equals(root)) {
-                    new XmlModelParser(resource, 
xmlInfo.getRootElementNamespace())
+                    new XmlModelParser(snapshot, 
xmlInfo.getRootElementNamespace())
                             .parseBeansDefinition()
                             .ifPresent(bd -> {
                                 registerBeans(resource, bd);
@@ -114,14 +125,14 @@ public class XmlRoutesBuilderLoader extends 
RouteBuilderLoaderSupport {
                         semantic = app.getSemantic();
                     }
                 } else if ("routes".equals(root) || "route".equals(root)) {
-                    RoutesDefinition routes = new 
XmlModelParser(resource(resource), xmlInfo.getRootElementNamespace())
+                    RoutesDefinition routes = new XmlModelParser(snapshot, 
xmlInfo.getRootElementNamespace())
                             .parseRoutesDefinition().orElse(null);
                     if (routes != null) {
                         routesCache.put(resource.getLocation(), routes);
                         semantic = routes.getSemantic();
                     }
                 }
-                SemanticDefinition.configure(getCamelContext(), resource, 
resource.getLocation(), semantic);
+                SemanticDefinitionHelper.configure(getCamelContext(), 
resource, resource.getLocation(), semantic);
             }
             preparseDone.put(resource.getLocation(), true);
         } catch (Exception e) {
@@ -131,6 +142,7 @@ public class XmlRoutesBuilderLoader extends 
RouteBuilderLoaderSupport {
             camelAppCache.clear();
             routesCache.clear();
             preparseDone.clear();
+            delayedRegistrations.clear();
             throw e;
         }
     }
@@ -141,6 +153,7 @@ public class XmlRoutesBuilderLoader extends 
RouteBuilderLoaderSupport {
         camelAppCache.remove(location);
         routesCache.remove(location);
         preparseDone.remove(location);
+        delayedRegistrations.removeIf(def -> def.getResource() != null && 
location.equals(def.getResource().getLocation()));
     }
 
     @Override
@@ -237,7 +250,6 @@ public class XmlRoutesBuilderLoader extends 
RouteBuilderLoaderSupport {
                 if (!delayedRegistrations.isEmpty()) {
                     // some of the beans were not available yet, so we have to 
try register them now
                     for (BeanFactoryDefinition<?> def : delayedRegistrations) {
-                        def.setResource(getResource());
                         registerBeanDefinition(def, false);
                     }
                     delayedRegistrations.clear();
diff --git 
a/dsl/camel-xml-jaxb-dsl/src/main/java/org/apache/camel/dsl/xml/jaxb/JaxbXmlRoutesBuilderLoader.java
 
b/dsl/camel-xml-jaxb-dsl/src/main/java/org/apache/camel/dsl/xml/jaxb/JaxbXmlRoutesBuilderLoader.java
index 639a8ff0ed82..b27786fa4220 100644
--- 
a/dsl/camel-xml-jaxb-dsl/src/main/java/org/apache/camel/dsl/xml/jaxb/JaxbXmlRoutesBuilderLoader.java
+++ 
b/dsl/camel-xml-jaxb-dsl/src/main/java/org/apache/camel/dsl/xml/jaxb/JaxbXmlRoutesBuilderLoader.java
@@ -84,6 +84,7 @@ public class JaxbXmlRoutesBuilderLoader extends 
RouteBuilderLoaderSupport {
                 try (InputStream is = resourceInputStream(resource)) {
                     RoutesDefinition routes = 
loadRoutesDefinition(getCamelContext(), is);
                     if (routes != null) {
+                        getRouteCollection().setSemantic(routes.getSemantic());
                         // xml routes must be prepared in the same way 
java-dsl (via RoutesDefinition)
                         // so create a copy and use the fluent builder to add 
the route
                         for (RouteDefinition route : routes.getRoutes()) {
diff --git 
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/java/org/apache/camel/dsl/yaml/SemanticQuestionTest.java
 
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/java/org/apache/camel/dsl/yaml/SemanticQuestionTest.java
index 0a4414f55232..71974099b4d5 100644
--- 
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/java/org/apache/camel/dsl/yaml/SemanticQuestionTest.java
+++ 
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/java/org/apache/camel/dsl/yaml/SemanticQuestionTest.java
@@ -341,6 +341,21 @@ class SemanticQuestionTest extends YamlTestSupport {
                 .hasStackTraceContaining("Node type map is invalid, expected 
array");
     }
 
+    @Test
+    void numericPlaceholdersResolveBeforeValidation() throws Exception {
+        loadRoutesNoValidate("""
+                - semantic:
+                    question:
+                      urgent:
+                        type: boolean
+                        instructions: Urgent?
+                        threshold: "{{threshold:0.8}}"
+                        uncertainty: "{{uncertainty:0.1}}"
+                """);
+        
assertThat(SemanticQuestions.get(context).get("urgent").getThreshold()).isEqualTo(0.8);
+        
assertThat(SemanticQuestions.get(context).get("urgent").getUncertainty()).isEqualTo(0.1);
+    }
+
     @ParameterizedTest
     @ValueSource(strings = { "threshold", "uncertainty" })
     void invalidNumericValuesIdentifyQuestionFieldAndLocation(String field) {

Reply via email to