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 2024/01/31 10:46:52 UTC

(camel) branch main updated: Var headers (#12960)

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

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


The following commit(s) were added to refs/heads/main by this push:
     new e3582740a93 Var headers (#12960)
e3582740a93 is described below

commit e3582740a931ffb4e8a0938c45814706cb411ef7
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Wed Jan 31 11:46:46 2024 +0100

    Var headers (#12960)
    
    CAMEL-19749: variables - Should also copy message headers into variable when using EIP variables
---
 .../apache/camel/catalog/languages/hl7terser.json  |   9 +-
 .../org/apache/camel/catalog/languages/jq.json     |   9 +-
 .../apache/camel/catalog/languages/jsonpath.json   |   9 +-
 .../org/apache/camel/catalog/models/hl7terser.json |   9 +-
 .../org/apache/camel/catalog/models/jq.json        |   9 +-
 .../org/apache/camel/catalog/models/jsonpath.json  |   9 +-
 .../apache/camel/catalog/schemas/camel-spring.xsd  |   9 +
 .../org/apache/camel/component/hl7/hl7terser.json  |   9 +-
 .../resources/org/apache/camel/language/jq/jq.json |   9 +-
 .../org/apache/camel/language/jq/JqExpression.java |  24 +-
 .../org/apache/camel/language/jq/JqFunctions.java  |   2 +-
 .../org/apache/camel/language/jq/JqLanguage.java   |   6 +-
 .../language/jq/JqSimpleTransformVariableTest.java |  42 ++--
 .../org/apache/camel/jsonpath/jsonpath.json        |   9 +-
 .../apache/camel/jsonpath/JsonPathExpression.java  |  12 +
 .../apache/camel/jsonpath/JsonPathLanguage.java    |   8 +-
 .../camel/spring/processor/FromVariableTest.xml    |   2 +
 .../src/main/java/org/apache/camel/Exchange.java   |   2 +-
 .../VariableRepository.java => VariableAware.java} |  25 +-
 .../apache/camel/spi/StreamCachingStrategy.java    |   8 +
 .../org/apache/camel/spi/VariableRepository.java   |  11 +-
 .../impl/engine/DefaultStreamCachingStrategy.java  |  18 +-
 .../modules/languages/pages/simple-language.adoc   |  14 ++
 .../simple/ast/SimpleFunctionExpression.java       |  30 +++
 .../camel/language/tokenizer/TokenizeLanguage.java |   3 +-
 .../org/apache/camel/model/language/hl7terser.json |   9 +-
 .../org/apache/camel/model/language/jq.json        |   9 +-
 .../org/apache/camel/model/language/jsonpath.json  |   9 +-
 .../java/org/apache/camel/builder/Builder.java     |   2 +-
 .../org/apache/camel/builder/DataFormatClause.java |   5 +-
 .../camel/builder/ExpressionClauseSupport.java     |   2 +-
 .../camel/builder/LanguageBuilderFactory.java      |   2 +-
 .../camel/model/dataformat/BeanioDataFormat.java   |  11 +-
 .../SingleInputTypedExpressionDefinition.java      |  27 ++
 .../camel/model/language/WasmExpression.java       |   2 +-
 .../java/org/apache/camel/processor/Enricher.java  |  20 +-
 .../org/apache/camel/processor/PollEnricher.java   |  17 +-
 .../camel/processor/SendDynamicProcessor.java      |  55 ++--
 .../org/apache/camel/processor/SendProcessor.java  |  32 ++-
 .../org/apache/camel/reifier/RouteReifier.java     |   7 +-
 .../dataformat/BeanioDataFormatReifier.java        |   4 +-
 .../language/JsonPathExpressionReifier.java        |   3 +-
 .../SingleInputTypedExpressionReifier.java         |   7 +-
 .../org/apache/camel/language/VariableTest.java    |  26 ++
 .../apache/camel/language/simple/SimpleTest.java   |   9 +
 .../camel/processor/EnrichVariableHeadersTest.java |  94 +++++++
 .../apache/camel/processor/FromVariableTest.java   |  26 +-
 ...est.java => PollEnrichVariableHeadersTest.java} |  24 +-
 .../processor/ToDynamicVariableHeadersTest.java    |  93 +++++++
 .../camel/processor/ToVariableHeadersTest.java     |  94 +++++++
 .../camel/converter/stream/CachedOutputStream.java |  38 ++-
 .../org/apache/camel/support/AbstractExchange.java |   9 +-
 ...sitory.java => AbstractVariableRepository.java} |  61 ++++-
 .../org/apache/camel/support/ExchangeHelper.java   |  66 ++++-
 .../camel/support/ExchangeVariableRepository.java  | 106 ++++----
 .../camel/support/GlobalVariableRepository.java    |  60 +----
 .../camel/support/HeaderVariableRepository.java    |  68 +++++
 .../camel/support/SingleInputLanguageSupport.java  |  18 +-
 .../support/SingleInputTypedLanguageSupport.java   |  27 +-
 .../camel/support/builder/ExpressionBuilder.java   | 104 +++++++-
 .../camel/support/processor/MarshalProcessor.java  |   1 -
 .../support/processor/UnmarshalProcessor.java      |   1 -
 .../java/org/apache/camel/xml/in/ModelParser.java  |   1 +
 .../java/org/apache/camel/xml/out/ModelWriter.java |   1 +
 .../org/apache/camel/yaml/out/ModelWriter.java     |   1 +
 docs/user-manual/modules/ROOT/pages/variables.adoc | 279 ++++-----------------
 .../dsl/yaml/deserializers/ModelDeserializers.java |  22 +-
 .../generated/resources/schema/camelYamlDsl.json   |  15 ++
 .../apache/camel/dsl/yaml/FromVariableTest.groovy  |  17 +-
 69 files changed, 1218 insertions(+), 563 deletions(-)

diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/hl7terser.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/hl7terser.json
index 3865481202b..58cd42e820c 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/hl7terser.json
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/hl7terser.json
@@ -18,9 +18,10 @@
   "properties": {
     "id": { "index": 0, "kind": "attribute", "displayName": "Id", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the id of this node" },
     "expression": { "index": 1, "kind": "value", "displayName": "Expression", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The expression value in your chosen language syntax" },
-    "headerName": { "index": 2, "kind": "attribute", "displayName": "Header Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of header to use as input, instead of the message body It has as higher precedent than the propertyName if both are set." },
-    "propertyName": { "index": 3, "kind": "attribute", "displayName": "Property Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of property to use as input, instead of the message body. It has a lower precedent than the headerName if both are set." },
-    "resultType": { "index": 4, "kind": "attribute", "displayName": "Result Type", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the class of the result type (type from output)" },
-    "trim": { "index": 5, "kind": "attribute", "displayName": "Trim", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to trim the value to remove leading and trailing whitespaces and line breaks" }
+    "variableName": { "index": 2, "kind": "attribute", "displayName": "Variable Name", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of variable to use as input, instead of the message body It has as higher precedent if other are set." },
+    "headerName": { "index": 3, "kind": "attribute", "displayName": "Header Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of header to use as input, instead of the message body It has as higher precedent than the propertyName if both are set." },
+    "propertyName": { "index": 4, "kind": "attribute", "displayName": "Property Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of property to use as input, instead of the message body. It has a lower precedent than the headerName if both are set." },
+    "resultType": { "index": 5, "kind": "attribute", "displayName": "Result Type", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the class of the result type (type from output)" },
+    "trim": { "index": 6, "kind": "attribute", "displayName": "Trim", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to trim the value to remove leading and trailing whitespaces and line breaks" }
   }
 }
diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/jq.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/jq.json
index 01859b055b7..2414f6e84ef 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/jq.json
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/jq.json
@@ -18,9 +18,10 @@
   "properties": {
     "id": { "index": 0, "kind": "attribute", "displayName": "Id", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the id of this node" },
     "expression": { "index": 1, "kind": "value", "displayName": "Expression", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The expression value in your chosen language syntax" },
-    "headerName": { "index": 2, "kind": "attribute", "displayName": "Header Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of header to use as input, instead of the message body It has as higher precedent than the propertyName if both are set." },
-    "propertyName": { "index": 3, "kind": "attribute", "displayName": "Property Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of property to use as input, instead of the message body. It has a lower precedent than the headerName if both are set." },
-    "resultType": { "index": 4, "kind": "attribute", "displayName": "Result Type", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the class of the result type (type from output)" },
-    "trim": { "index": 5, "kind": "attribute", "displayName": "Trim", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to trim the value to remove leading and trailing whitespaces and line breaks" }
+    "variableName": { "index": 2, "kind": "attribute", "displayName": "Variable Name", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of variable to use as input, instead of the message body It has as higher precedent if other are set." },
+    "headerName": { "index": 3, "kind": "attribute", "displayName": "Header Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of header to use as input, instead of the message body It has as higher precedent than the propertyName if both are set." },
+    "propertyName": { "index": 4, "kind": "attribute", "displayName": "Property Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of property to use as input, instead of the message body. It has a lower precedent than the headerName if both are set." },
+    "resultType": { "index": 5, "kind": "attribute", "displayName": "Result Type", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the class of the result type (type from output)" },
+    "trim": { "index": 6, "kind": "attribute", "displayName": "Trim", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to trim the value to remove leading and trailing whitespaces and line breaks" }
   }
 }
diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/jsonpath.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/jsonpath.json
index 1f60d86297e..ec518053441 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/jsonpath.json
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/jsonpath.json
@@ -24,9 +24,10 @@
     "writeAsString": { "index": 5, "kind": "attribute", "displayName": "Write As String", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to write the output of each row\/element as a JSON String value instead of a Map\/POJO value." },
     "unpackArray": { "index": 6, "kind": "attribute", "displayName": "Unpack Array", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to unpack a single element json-array into an object." },
     "option": { "index": 7, "kind": "attribute", "displayName": "Option", "label": "advanced", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "DEFAULT_PATH_LEAF_TO_NULL", "ALWAYS_RETURN_LIST", "AS_PATH_LIST", "SUPPRESS_EXCEPTIONS", "REQUIRE_PROPERTIES" ], "deprecated": false, "autowired": false, "secret": false, "description": "To configure additional options on JSONPath. Multiple values can be separated by comma." },
-    "headerName": { "index": 8, "kind": "attribute", "displayName": "Header Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of header to use as input, instead of the message body It has as higher precedent than the propertyName if both are set." },
-    "propertyName": { "index": 9, "kind": "attribute", "displayName": "Property Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of property to use as input, instead of the message body. It has a lower precedent than the headerName if both are set." },
-    "resultType": { "index": 10, "kind": "attribute", "displayName": "Result Type", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the class of the result type (type from output)" },
-    "trim": { "index": 11, "kind": "attribute", "displayName": "Trim", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to trim the value to remove leading and trailing whitespaces and line breaks" }
+    "variableName": { "index": 8, "kind": "attribute", "displayName": "Variable Name", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of variable to use as input, instead of the message body It has as higher precedent if other are set." },
+    "headerName": { "index": 9, "kind": "attribute", "displayName": "Header Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of header to use as input, instead of the message body It has as higher precedent than the propertyName if both are set." },
+    "propertyName": { "index": 10, "kind": "attribute", "displayName": "Property Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of property to use as input, instead of the message body. It has a lower precedent than the headerName if both are set." },
+    "resultType": { "index": 11, "kind": "attribute", "displayName": "Result Type", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the class of the result type (type from output)" },
+    "trim": { "index": 12, "kind": "attribute", "displayName": "Trim", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to trim the value to remove leading and trailing whitespaces and line breaks" }
   }
 }
diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/hl7terser.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/hl7terser.json
index c21ace421f3..175a11f5b72 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/hl7terser.json
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/hl7terser.json
@@ -15,9 +15,10 @@
   "properties": {
     "id": { "index": 0, "kind": "attribute", "displayName": "Id", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the id of this node" },
     "expression": { "index": 1, "kind": "value", "displayName": "Expression", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The expression value in your chosen language syntax" },
-    "headerName": { "index": 2, "kind": "attribute", "displayName": "Header Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of header to use as input, instead of the message body It has as higher precedent than the propertyName if both are set." },
-    "propertyName": { "index": 3, "kind": "attribute", "displayName": "Property Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of property to use as input, instead of the message body. It has a lower precedent than the headerName if both are set." },
-    "resultType": { "index": 4, "kind": "attribute", "displayName": "Result Type", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the class of the result type (type from output)" },
-    "trim": { "index": 5, "kind": "attribute", "displayName": "Trim", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to trim the value to remove leading and trailing whitespaces and line breaks" }
+    "variableName": { "index": 2, "kind": "attribute", "displayName": "Variable Name", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of variable to use as input, instead of the message body It has as higher precedent if other are set." },
+    "headerName": { "index": 3, "kind": "attribute", "displayName": "Header Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of header to use as input, instead of the message body It has as higher precedent than the propertyName if both are set." },
+    "propertyName": { "index": 4, "kind": "attribute", "displayName": "Property Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of property to use as input, instead of the message body. It has a lower precedent than the headerName if both are set." },
+    "resultType": { "index": 5, "kind": "attribute", "displayName": "Result Type", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the class of the result type (type from output)" },
+    "trim": { "index": 6, "kind": "attribute", "displayName": "Trim", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to trim the value to remove leading and trailing whitespaces and line breaks" }
   }
 }
diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/jq.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/jq.json
index c0d966613c3..04fb7ca5648 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/jq.json
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/jq.json
@@ -15,9 +15,10 @@
   "properties": {
     "id": { "index": 0, "kind": "attribute", "displayName": "Id", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the id of this node" },
     "expression": { "index": 1, "kind": "value", "displayName": "Expression", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The expression value in your chosen language syntax" },
-    "headerName": { "index": 2, "kind": "attribute", "displayName": "Header Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of header to use as input, instead of the message body It has as higher precedent than the propertyName if both are set." },
-    "propertyName": { "index": 3, "kind": "attribute", "displayName": "Property Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of property to use as input, instead of the message body. It has a lower precedent than the headerName if both are set." },
-    "resultType": { "index": 4, "kind": "attribute", "displayName": "Result Type", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the class of the result type (type from output)" },
-    "trim": { "index": 5, "kind": "attribute", "displayName": "Trim", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to trim the value to remove leading and trailing whitespaces and line breaks" }
+    "variableName": { "index": 2, "kind": "attribute", "displayName": "Variable Name", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of variable to use as input, instead of the message body It has as higher precedent if other are set." },
+    "headerName": { "index": 3, "kind": "attribute", "displayName": "Header Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of header to use as input, instead of the message body It has as higher precedent than the propertyName if both are set." },
+    "propertyName": { "index": 4, "kind": "attribute", "displayName": "Property Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of property to use as input, instead of the message body. It has a lower precedent than the headerName if both are set." },
+    "resultType": { "index": 5, "kind": "attribute", "displayName": "Result Type", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the class of the result type (type from output)" },
+    "trim": { "index": 6, "kind": "attribute", "displayName": "Trim", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to trim the value to remove leading and trailing whitespaces and line breaks" }
   }
 }
diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/jsonpath.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/jsonpath.json
index 18ebc270c0c..7a139068350 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/jsonpath.json
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/jsonpath.json
@@ -21,9 +21,10 @@
     "writeAsString": { "index": 5, "kind": "attribute", "displayName": "Write As String", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to write the output of each row\/element as a JSON String value instead of a Map\/POJO value." },
     "unpackArray": { "index": 6, "kind": "attribute", "displayName": "Unpack Array", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to unpack a single element json-array into an object." },
     "option": { "index": 7, "kind": "attribute", "displayName": "Option", "label": "advanced", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "DEFAULT_PATH_LEAF_TO_NULL", "ALWAYS_RETURN_LIST", "AS_PATH_LIST", "SUPPRESS_EXCEPTIONS", "REQUIRE_PROPERTIES" ], "deprecated": false, "autowired": false, "secret": false, "description": "To configure additional options on JSONPath. Multiple values can be separated by comma." },
-    "headerName": { "index": 8, "kind": "attribute", "displayName": "Header Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of header to use as input, instead of the message body It has as higher precedent than the propertyName if both are set." },
-    "propertyName": { "index": 9, "kind": "attribute", "displayName": "Property Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of property to use as input, instead of the message body. It has a lower precedent than the headerName if both are set." },
-    "resultType": { "index": 10, "kind": "attribute", "displayName": "Result Type", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the class of the result type (type from output)" },
-    "trim": { "index": 11, "kind": "attribute", "displayName": "Trim", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to trim the value to remove leading and trailing whitespaces and line breaks" }
+    "variableName": { "index": 8, "kind": "attribute", "displayName": "Variable Name", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of variable to use as input, instead of the message body It has as higher precedent if other are set." },
+    "headerName": { "index": 9, "kind": "attribute", "displayName": "Header Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of header to use as input, instead of the message body It has as higher precedent than the propertyName if both are set." },
+    "propertyName": { "index": 10, "kind": "attribute", "displayName": "Property Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of property to use as input, instead of the message body. It has a lower precedent than the headerName if both are set." },
+    "resultType": { "index": 11, "kind": "attribute", "displayName": "Result Type", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the class of the result type (type from output)" },
+    "trim": { "index": 12, "kind": "attribute", "displayName": "Trim", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to trim the value to remove leading and trailing whitespaces and line breaks" }
   }
 }
diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd
index a9f51331f7f..8db2e5a49fb 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd
@@ -15242,6 +15242,15 @@ The String representation of the MediaType to output.
   <xs:complexType abstract="true" name="singleInputTypedExpressionDefinition">
     <xs:simpleContent>
       <xs:extension base="tns:typedExpressionDefinition">
+        <xs:attribute name="variableName" type="xs:string">
+          <xs:annotation>
+            <xs:documentation xml:lang="en">
+<![CDATA[
+Name of variable to use as input, instead of the message body It has as higher precedent if other are set.
+]]>
+            </xs:documentation>
+          </xs:annotation>
+        </xs:attribute>
         <xs:attribute name="headerName" type="xs:string">
           <xs:annotation>
             <xs:documentation xml:lang="en">
diff --git a/components/camel-hl7/src/generated/resources/org/apache/camel/component/hl7/hl7terser.json b/components/camel-hl7/src/generated/resources/org/apache/camel/component/hl7/hl7terser.json
index 3865481202b..58cd42e820c 100644
--- a/components/camel-hl7/src/generated/resources/org/apache/camel/component/hl7/hl7terser.json
+++ b/components/camel-hl7/src/generated/resources/org/apache/camel/component/hl7/hl7terser.json
@@ -18,9 +18,10 @@
   "properties": {
     "id": { "index": 0, "kind": "attribute", "displayName": "Id", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the id of this node" },
     "expression": { "index": 1, "kind": "value", "displayName": "Expression", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The expression value in your chosen language syntax" },
-    "headerName": { "index": 2, "kind": "attribute", "displayName": "Header Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of header to use as input, instead of the message body It has as higher precedent than the propertyName if both are set." },
-    "propertyName": { "index": 3, "kind": "attribute", "displayName": "Property Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of property to use as input, instead of the message body. It has a lower precedent than the headerName if both are set." },
-    "resultType": { "index": 4, "kind": "attribute", "displayName": "Result Type", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the class of the result type (type from output)" },
-    "trim": { "index": 5, "kind": "attribute", "displayName": "Trim", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to trim the value to remove leading and trailing whitespaces and line breaks" }
+    "variableName": { "index": 2, "kind": "attribute", "displayName": "Variable Name", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of variable to use as input, instead of the message body It has as higher precedent if other are set." },
+    "headerName": { "index": 3, "kind": "attribute", "displayName": "Header Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of header to use as input, instead of the message body It has as higher precedent than the propertyName if both are set." },
+    "propertyName": { "index": 4, "kind": "attribute", "displayName": "Property Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of property to use as input, instead of the message body. It has a lower precedent than the headerName if both are set." },
+    "resultType": { "index": 5, "kind": "attribute", "displayName": "Result Type", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the class of the result type (type from output)" },
+    "trim": { "index": 6, "kind": "attribute", "displayName": "Trim", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to trim the value to remove leading and trailing whitespaces and line breaks" }
   }
 }
diff --git a/components/camel-jq/src/generated/resources/org/apache/camel/language/jq/jq.json b/components/camel-jq/src/generated/resources/org/apache/camel/language/jq/jq.json
index 01859b055b7..2414f6e84ef 100644
--- a/components/camel-jq/src/generated/resources/org/apache/camel/language/jq/jq.json
+++ b/components/camel-jq/src/generated/resources/org/apache/camel/language/jq/jq.json
@@ -18,9 +18,10 @@
   "properties": {
     "id": { "index": 0, "kind": "attribute", "displayName": "Id", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the id of this node" },
     "expression": { "index": 1, "kind": "value", "displayName": "Expression", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The expression value in your chosen language syntax" },
-    "headerName": { "index": 2, "kind": "attribute", "displayName": "Header Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of header to use as input, instead of the message body It has as higher precedent than the propertyName if both are set." },
-    "propertyName": { "index": 3, "kind": "attribute", "displayName": "Property Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of property to use as input, instead of the message body. It has a lower precedent than the headerName if both are set." },
-    "resultType": { "index": 4, "kind": "attribute", "displayName": "Result Type", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the class of the result type (type from output)" },
-    "trim": { "index": 5, "kind": "attribute", "displayName": "Trim", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to trim the value to remove leading and trailing whitespaces and line breaks" }
+    "variableName": { "index": 2, "kind": "attribute", "displayName": "Variable Name", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of variable to use as input, instead of the message body It has as higher precedent if other are set." },
+    "headerName": { "index": 3, "kind": "attribute", "displayName": "Header Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of header to use as input, instead of the message body It has as higher precedent than the propertyName if both are set." },
+    "propertyName": { "index": 4, "kind": "attribute", "displayName": "Property Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of property to use as input, instead of the message body. It has a lower precedent than the headerName if both are set." },
+    "resultType": { "index": 5, "kind": "attribute", "displayName": "Result Type", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the class of the result type (type from output)" },
+    "trim": { "index": 6, "kind": "attribute", "displayName": "Trim", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to trim the value to remove leading and trailing whitespaces and line breaks" }
   }
 }
diff --git a/components/camel-jq/src/main/java/org/apache/camel/language/jq/JqExpression.java b/components/camel-jq/src/main/java/org/apache/camel/language/jq/JqExpression.java
index f66c0f8f1ed..a72cc5f90a3 100644
--- a/components/camel-jq/src/main/java/org/apache/camel/language/jq/JqExpression.java
+++ b/components/camel-jq/src/main/java/org/apache/camel/language/jq/JqExpression.java
@@ -32,9 +32,11 @@ import org.apache.camel.Exchange;
 import org.apache.camel.ExpressionIllegalSyntaxException;
 import org.apache.camel.InvalidPayloadException;
 import org.apache.camel.NoSuchHeaderOrPropertyException;
+import org.apache.camel.NoSuchVariableException;
 import org.apache.camel.RuntimeCamelException;
 import org.apache.camel.TypeConverter;
 import org.apache.camel.spi.ExpressionResultTypeAware;
+import org.apache.camel.support.ExchangeHelper;
 import org.apache.camel.support.ExpressionAdapter;
 import org.apache.camel.support.MessageHelper;
 
@@ -48,6 +50,7 @@ public class JqExpression extends ExpressionAdapter implements ExpressionResultT
     private JsonQuery query;
     private TypeConverter typeConverter;
 
+    private String variableName;
     private String headerName;
     private String propertyName;
 
@@ -117,6 +120,17 @@ public class JqExpression extends ExpressionAdapter implements ExpressionResultT
         this.resultTypeName = resultTypeName;
     }
 
+    public String getVariableName() {
+        return variableName;
+    }
+
+    /**
+     * Name of the variable to use as input instead of the message body.
+     */
+    public void setVariableName(String variableName) {
+        this.variableName = variableName;
+    }
+
     public String getHeaderName() {
         return headerName;
     }
@@ -206,7 +220,7 @@ public class JqExpression extends ExpressionAdapter implements ExpressionResultT
     private JsonNode getPayload(Exchange exchange) throws Exception {
         JsonNode payload = null;
 
-        if (headerName == null && propertyName == null) {
+        if (variableName == null && headerName == null && propertyName == null) {
             payload = exchange.getMessage().getBody(JsonNode.class);
             if (payload == null) {
                 throw new InvalidPayloadException(exchange, JsonNode.class);
@@ -214,7 +228,13 @@ public class JqExpression extends ExpressionAdapter implements ExpressionResultT
             // if body is stream cached then reset, so we can re-read it again
             MessageHelper.resetStreamCache(exchange.getMessage());
         } else {
-            if (headerName != null) {
+            if (variableName != null) {
+                payload = ExchangeHelper.getVariable(exchange, variableName, JsonNode.class);
+                if (payload == null) {
+                    throw new NoSuchVariableException(exchange, variableName, JsonNode.class);
+                }
+            }
+            if (payload == null && headerName != null) {
                 payload = exchange.getMessage().getHeader(headerName, JsonNode.class);
             }
             if (payload == null && propertyName != null) {
diff --git a/components/camel-jq/src/main/java/org/apache/camel/language/jq/JqFunctions.java b/components/camel-jq/src/main/java/org/apache/camel/language/jq/JqFunctions.java
index 82d5ce182fa..8589ed7eeeb 100644
--- a/components/camel-jq/src/main/java/org/apache/camel/language/jq/JqFunctions.java
+++ b/components/camel-jq/src/main/java/org/apache/camel/language/jq/JqFunctions.java
@@ -181,7 +181,7 @@ public final class JqFunctions {
      *
      * <pre>
      * {@code
-     * .name = proeprty(\"CommitterName\")"
+     * .name = property(\"CommitterName\")"
      * }
      * </pre>
      *
diff --git a/components/camel-jq/src/main/java/org/apache/camel/language/jq/JqLanguage.java b/components/camel-jq/src/main/java/org/apache/camel/language/jq/JqLanguage.java
index c5ef627cf27..151d4dcb242 100644
--- a/components/camel-jq/src/main/java/org/apache/camel/language/jq/JqLanguage.java
+++ b/components/camel-jq/src/main/java/org/apache/camel/language/jq/JqLanguage.java
@@ -71,6 +71,7 @@ public class JqLanguage extends SingleInputTypedLanguageSupport implements Stati
     public Expression createExpression(String expression) {
         JqExpression answer = new JqExpression(Scope.newChildScope(rootScope), expression);
         answer.setResultType(getResultType());
+        answer.setVariableName(getVariableName());
         answer.setHeaderName(getHeaderName());
         answer.setPropertyName(getPropertyName());
         answer.init(getCamelContext());
@@ -81,8 +82,9 @@ public class JqLanguage extends SingleInputTypedLanguageSupport implements Stati
     public Expression createExpression(String expression, Object[] properties) {
         JqExpression answer = new JqExpression(Scope.newChildScope(rootScope), expression);
         answer.setResultType(property(Class.class, properties, 0, getResultType()));
-        answer.setHeaderName(property(String.class, properties, 1, getHeaderName()));
-        answer.setPropertyName(property(String.class, properties, 2, getPropertyName()));
+        answer.setVariableName(property(String.class, properties, 1, getVariableName()));
+        answer.setHeaderName(property(String.class, properties, 2, getHeaderName()));
+        answer.setPropertyName(property(String.class, properties, 3, getPropertyName()));
         answer.init(getCamelContext());
         return answer;
     }
diff --git a/core/camel-core/src/test/java/org/apache/camel/processor/FromVariableTest.java b/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqSimpleTransformVariableTest.java
similarity index 52%
copy from core/camel-core/src/test/java/org/apache/camel/processor/FromVariableTest.java
copy to components/camel-jq/src/test/java/org/apache/camel/language/jq/JqSimpleTransformVariableTest.java
index 3dfc5800ee8..5e62dbad94f 100644
--- a/core/camel-core/src/test/java/org/apache/camel/processor/FromVariableTest.java
+++ b/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqSimpleTransformVariableTest.java
@@ -14,35 +14,41 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.camel.processor;
+package org.apache.camel.language.jq;
 
-import org.apache.camel.ContextTestSupport;
 import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
 import org.junit.jupiter.api.Test;
 
-public class FromVariableTest extends ContextTestSupport {
+public class JqSimpleTransformVariableTest extends JqTestSupport {
 
-    @Test
-    public void testOriginalBody() throws Exception {
-        getMockEndpoint("mock:foo").expectedBodiesReceived("Bye World");
-        getMockEndpoint("mock:result").expectedBodiesReceived("World");
-
-        template.sendBody("direct:start", "World");
-
-        assertMockEndpointsSatisfied();
-    }
+    private static String EXPECTED = """
+            {
+              "country": "se",
+            }""";
 
     @Override
-    protected RouteBuilder createRouteBuilder() throws Exception {
+    protected RouteBuilder createRouteBuilder() {
         return new RouteBuilder() {
             @Override
-            public void configure() throws Exception {
-                fromV("direct:start", "myKey")
-                        .transform().simple("Bye ${body}")
-                        .to("mock:foo")
-                        .setBody(simple("${variable:myKey}"))
+            public void configure() {
+                from("direct:start")
+                        .setVariable("place", constant("{ \"name\": \"sweden\", \"iso\": \"se\" }"))
+                        .transform().simple("""
+                                {
+                                  "country": "${jq(variable:place,.iso)}",
+                                }""")
                         .to("mock:result");
             }
         };
     }
+
+    @Test
+    public void testTransform() throws Exception {
+        getMockEndpoint("mock:result").expectedBodiesReceived(EXPECTED);
+
+        template.sendBody("direct:start", "{\"id\": 123, \"age\": 42, \"name\": \"scott\"}");
+
+        MockEndpoint.assertIsSatisfied(context);
+    }
 }
diff --git a/components/camel-jsonpath/src/generated/resources/org/apache/camel/jsonpath/jsonpath.json b/components/camel-jsonpath/src/generated/resources/org/apache/camel/jsonpath/jsonpath.json
index 1f60d86297e..ec518053441 100644
--- a/components/camel-jsonpath/src/generated/resources/org/apache/camel/jsonpath/jsonpath.json
+++ b/components/camel-jsonpath/src/generated/resources/org/apache/camel/jsonpath/jsonpath.json
@@ -24,9 +24,10 @@
     "writeAsString": { "index": 5, "kind": "attribute", "displayName": "Write As String", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to write the output of each row\/element as a JSON String value instead of a Map\/POJO value." },
     "unpackArray": { "index": 6, "kind": "attribute", "displayName": "Unpack Array", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to unpack a single element json-array into an object." },
     "option": { "index": 7, "kind": "attribute", "displayName": "Option", "label": "advanced", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "DEFAULT_PATH_LEAF_TO_NULL", "ALWAYS_RETURN_LIST", "AS_PATH_LIST", "SUPPRESS_EXCEPTIONS", "REQUIRE_PROPERTIES" ], "deprecated": false, "autowired": false, "secret": false, "description": "To configure additional options on JSONPath. Multiple values can be separated by comma." },
-    "headerName": { "index": 8, "kind": "attribute", "displayName": "Header Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of header to use as input, instead of the message body It has as higher precedent than the propertyName if both are set." },
-    "propertyName": { "index": 9, "kind": "attribute", "displayName": "Property Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of property to use as input, instead of the message body. It has a lower precedent than the headerName if both are set." },
-    "resultType": { "index": 10, "kind": "attribute", "displayName": "Result Type", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the class of the result type (type from output)" },
-    "trim": { "index": 11, "kind": "attribute", "displayName": "Trim", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to trim the value to remove leading and trailing whitespaces and line breaks" }
+    "variableName": { "index": 8, "kind": "attribute", "displayName": "Variable Name", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of variable to use as input, instead of the message body It has as higher precedent if other are set." },
+    "headerName": { "index": 9, "kind": "attribute", "displayName": "Header Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of header to use as input, instead of the message body It has as higher precedent than the propertyName if both are set." },
+    "propertyName": { "index": 10, "kind": "attribute", "displayName": "Property Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of property to use as input, instead of the message body. It has a lower precedent than the headerName if both are set." },
+    "resultType": { "index": 11, "kind": "attribute", "displayName": "Result Type", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the class of the result type (type from output)" },
+    "trim": { "index": 12, "kind": "attribute", "displayName": "Trim", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to trim the value to remove leading and trailing whitespaces and line breaks" }
   }
 }
diff --git a/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathExpression.java b/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathExpression.java
index 1c8b9cccc29..f0c27d7cd4a 100644
--- a/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathExpression.java
+++ b/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathExpression.java
@@ -43,6 +43,7 @@ public class JsonPathExpression extends ExpressionAdapter {
     private boolean allowEasyPredicate = true;
     private boolean writeAsString;
     private boolean unpackArray;
+    private String variableName;
     private String headerName;
     private String propertyName;
     private Option[] options;
@@ -129,6 +130,17 @@ public class JsonPathExpression extends ExpressionAdapter {
         this.unpackArray = unpackArray;
     }
 
+    public String getVariableName() {
+        return variableName;
+    }
+
+    /**
+     * Name of variable to use as input, instead of the message body
+     */
+    public void setVariableName(String variableName) {
+        this.variableName = variableName;
+    }
+
     public String getHeaderName() {
         return headerName;
     }
diff --git a/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathLanguage.java b/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathLanguage.java
index 1dc16711993..b8879cfcbec 100644
--- a/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathLanguage.java
+++ b/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathLanguage.java
@@ -102,9 +102,10 @@ public class JsonPathLanguage extends SingleInputTypedLanguageSupport implements
         answer.setSuppressExceptions(suppressExceptions);
         answer.setAllowSimple(allowSimple);
         answer.setAllowEasyPredicate(allowEasyPredicate);
-        answer.setHeaderName(getHeaderName());
         answer.setWriteAsString(writeAsString);
         answer.setUnpackArray(unpackArray);
+        answer.setVariableName(getVariableName());
+        answer.setHeaderName(getHeaderName());
         answer.setPropertyName(getPropertyName());
         answer.setOptions(options);
         answer.init(getCamelContext());
@@ -137,6 +138,7 @@ public class JsonPathLanguage extends SingleInputTypedLanguageSupport implements
             answer.setOptions(list.toArray(new Option[0]));
         }
         answer.setPropertyName(property(String.class, properties, 8, getPropertyName()));
+        answer.setVariableName(property(String.class, properties, 9, getVariableName()));
         answer.init(getCamelContext());
         return answer;
     }
@@ -178,6 +180,10 @@ public class JsonPathLanguage extends SingleInputTypedLanguageSupport implements
             case "allowEasyPredicate":
                 setAllowEasyPredicate(PropertyConfigurerSupport.property(camelContext, boolean.class, value));
                 return true;
+            case "variablename":
+            case "variableName":
+                setVariableName(PropertyConfigurerSupport.property(camelContext, String.class, value));
+                return true;
             case "headername":
             case "headerName":
                 setHeaderName(PropertyConfigurerSupport.property(camelContext, String.class, value));
diff --git a/components/camel-spring-xml/src/test/resources/org/apache/camel/spring/processor/FromVariableTest.xml b/components/camel-spring-xml/src/test/resources/org/apache/camel/spring/processor/FromVariableTest.xml
index 1f578ec457d..595d382242e 100644
--- a/components/camel-spring-xml/src/test/resources/org/apache/camel/spring/processor/FromVariableTest.xml
+++ b/components/camel-spring-xml/src/test/resources/org/apache/camel/spring/processor/FromVariableTest.xml
@@ -29,6 +29,8 @@
     <jmxAgent id="jmx" disabled="true"/>
     <route>
       <from uri="direct:start" variableReceive="myKey"/>
+      <setHeader name="foo"><constant>456</constant></setHeader>
+      <setHeader name="bar"><constant>Murphy</constant></setHeader>
       <transform><simple>Bye ${body}</simple></transform>
       <to uri="mock:foo"/>
       <setBody>
diff --git a/core/camel-api/src/main/java/org/apache/camel/Exchange.java b/core/camel-api/src/main/java/org/apache/camel/Exchange.java
index 3aa5175e076..3495a865391 100644
--- a/core/camel-api/src/main/java/org/apache/camel/Exchange.java
+++ b/core/camel-api/src/main/java/org/apache/camel/Exchange.java
@@ -64,7 +64,7 @@ import org.apache.camel.spi.annotations.ConstantProvider;
  * details.
  */
 @ConstantProvider("org.apache.camel.ExchangeConstantProvider")
-public interface Exchange {
+public interface Exchange extends VariableAware {
 
     String AUTHENTICATION = "CamelAuthentication";
     String AUTHENTICATION_FAILURE_POLICY_ID = "CamelAuthenticationFailurePolicyId";
diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/VariableRepository.java b/core/camel-api/src/main/java/org/apache/camel/VariableAware.java
similarity index 61%
copy from core/camel-api/src/main/java/org/apache/camel/spi/VariableRepository.java
copy to core/camel-api/src/main/java/org/apache/camel/VariableAware.java
index 153b3502135..ab82303117c 100644
--- a/core/camel-api/src/main/java/org/apache/camel/spi/VariableRepository.java
+++ b/core/camel-api/src/main/java/org/apache/camel/VariableAware.java
@@ -14,27 +14,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.camel.spi;
-
-import org.apache.camel.StaticService;
+package org.apache.camel;
 
 /**
- * Repository for storing and accessing variables.
+ * An interface to represent an object that supports variables.
  */
-public interface VariableRepository extends StaticService {
-
-    /**
-     * The id of this repository.
-     */
-    String getId();
+public interface VariableAware {
 
     /**
      * Returns a variable by name.
      *
-     * If the variable is of type {@link org.apache.camel.StreamCache} then the repository should ensure to reset the
-     * stream cache before returning the value, to ensure the content can be read by the Camel end user and would be
-     * re-readable next time.
-     *
      * @param  name the name of the variable
      * @return      the value of the given variable or <tt>null</tt> if there is no variable for the given name
      */
@@ -48,12 +37,4 @@ public interface VariableRepository extends StaticService {
      */
     void setVariable(String name, Object value);
 
-    /**
-     * Removes the given variable
-     *
-     * @param  name of the variable
-     * @return      the old value of the variable, or <tt>null</tt> if there was no variable for the given name
-     */
-    Object removeVariable(String name);
-
 }
diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/StreamCachingStrategy.java b/core/camel-api/src/main/java/org/apache/camel/spi/StreamCachingStrategy.java
index 68de83dd718..267949c9ba6 100644
--- a/core/camel-api/src/main/java/org/apache/camel/spi/StreamCachingStrategy.java
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/StreamCachingStrategy.java
@@ -282,4 +282,12 @@ public interface StreamCachingStrategy extends StaticService {
      */
     StreamCache cache(Message message);
 
+    /**
+     * Caches the value aas a {@link StreamCache}.
+     *
+     * @param  value the value
+     * @return       the value cached as a {@link StreamCache}, or <tt>null</tt> if not possible or no need to cache
+     */
+    StreamCache cache(Object value);
+
 }
diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/VariableRepository.java b/core/camel-api/src/main/java/org/apache/camel/spi/VariableRepository.java
index 153b3502135..fba280fcda1 100644
--- a/core/camel-api/src/main/java/org/apache/camel/spi/VariableRepository.java
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/VariableRepository.java
@@ -17,11 +17,12 @@
 package org.apache.camel.spi;
 
 import org.apache.camel.StaticService;
+import org.apache.camel.VariableAware;
 
 /**
  * Repository for storing and accessing variables.
  */
-public interface VariableRepository extends StaticService {
+public interface VariableRepository extends StaticService, VariableAware {
 
     /**
      * The id of this repository.
@@ -40,14 +41,6 @@ public interface VariableRepository extends StaticService {
      */
     Object getVariable(String name);
 
-    /**
-     * Sets a variable
-     *
-     * @param name  of the variable
-     * @param value the value of the variable
-     */
-    void setVariable(String name, Object value);
-
     /**
      * Removes the given variable
      *
diff --git a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultStreamCachingStrategy.java b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultStreamCachingStrategy.java
index 4c0919f7b7e..92df0417bd8 100644
--- a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultStreamCachingStrategy.java
+++ b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultStreamCachingStrategy.java
@@ -249,21 +249,33 @@ public class DefaultStreamCachingStrategy extends ServiceSupport implements Came
 
     @Override
     public StreamCache cache(Exchange exchange) {
-        return cache(exchange.getMessage());
+        return doCache(exchange.getMessage().getBody(), exchange);
     }
 
     @Override
     public StreamCache cache(Message message) {
+        return doCache(message.getBody(), message.getExchange());
+    }
+
+    @Override
+    public StreamCache cache(Object body) {
+        return doCache(body, null);
+    }
+
+    private StreamCache doCache(Object body, Exchange exchange) {
         StreamCache cache = null;
         // try convert to stream cache
-        Object body = message.getBody();
         if (body != null) {
             boolean allowed = allowClasses == null && denyClasses == null;
             if (!allowed) {
                 allowed = checkAllowDenyList(body);
             }
             if (allowed) {
-                cache = camelContext.getTypeConverter().convertTo(StreamCache.class, message.getExchange(), body);
+                if (exchange != null) {
+                    cache = camelContext.getTypeConverter().convertTo(StreamCache.class, exchange, body);
+                } else {
+                    cache = camelContext.getTypeConverter().convertTo(StreamCache.class, body);
+                }
             }
         }
         if (cache != null) {
diff --git a/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-language.adoc b/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-language.adoc
index 9a663faf367..acb01a71d40 100644
--- a/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-language.adoc
+++ b/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-language.adoc
@@ -274,12 +274,26 @@ The algorithm can be SHA-256 (default) or SHA3-256.
 |jsonpath(exp) | Object | When working with JSon data, then this allows to use the JsonPath language
 for example to extract data from the message body (in JSon format). This requires having camel-jsonpath JAR on the classpath.
 
+|jsonpath(input,exp) | Object | When working with JSon data, then this allows to use the JsonPath language
+for example to extract data from the message body (in JSon format). This requires having camel-jsonpath JAR on the classpath.
+For _input_ you can choose `header:key`, `exchangeProperty:key` or `variable:key` to use as input for the JSon payload instead of the message body.
+
 |jq(exp) | Object | When working with JSon data, then this allows to use the JQ language
 for example to extract data from the message body (in JSon format). This requires having camel-jq JAR on the classpath.
 
+|jq(input,exp) | Object | When working with JSon data, then this allows to use the JQ language
+for example to extract data from the message body (in JSon format). This requires having camel-jq JAR on the classpath.
+For _input_ you can choose `header:key`, `exchangeProperty:key` or `variable:key` to use as input for the JSon payload instead of the message body.
+
 |xpath(exp) | Object | When working with XML data, then this allows to use the XPath language
 for example to extract data from the message body (in XML format). This requires having camel-xpath JAR on the classpath.
 
+|xpath(input,exp) | Object | When working with XML data, then this allows to use the XPath language
+for example to extract data from the message body (in XML format). This requires having camel-xpath JAR on the classpath.
+For _input_ you can choose `header:key`, `exchangeProperty:key` or `variable:key` to use as input for the JSon payload instead of the message body.
+
+|pretty(exp) | String | Converts the inlined expression to a String, and attempts to pretty print if JSon or XML, otherwise the expression is returned as the String value.
+
 |=======================================================================
 
 == OGNL expression support
diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionExpression.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionExpression.java
index 20b07a3d12b..a9276250d2e 100644
--- a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionExpression.java
+++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionExpression.java
@@ -175,6 +175,18 @@ public class SimpleFunctionExpression extends LiteralExpression {
             return SimpleExpressionBuilder.exchangeOgnlExpression(remainder);
         }
 
+        // pretty
+        remainder = ifStartsWithReturnRemainder("pretty(", function);
+        if (remainder != null) {
+            String exp = StringHelper.beforeLast(remainder, ")");
+            if (exp == null) {
+                throw new SimpleParserException("Valid syntax: ${pretty(exp)} was: " + function, token.getIndex());
+            }
+            exp = StringHelper.removeQuotes(exp);
+            Expression inlined = camelContext.resolveLanguage("simple").createExpression(exp);
+            return ExpressionBuilder.prettyExpression(inlined);
+        }
+
         // file: prefix
         remainder = ifStartsWithReturnRemainder("file:", function);
         if (remainder != null) {
@@ -522,6 +534,12 @@ public class SimpleFunctionExpression extends LiteralExpression {
                 throw new SimpleParserException("Valid syntax: ${jq(exp)} was: " + function, token.getIndex());
             }
             exp = StringHelper.removeQuotes(exp);
+            if (exp.startsWith("header:") || exp.startsWith("property:") || exp.startsWith("exchangeProperty:")
+                    || exp.startsWith("variable:")) {
+                String input = StringHelper.before(exp, ",");
+                exp = StringHelper.after(exp, ",");
+                return ExpressionBuilder.singleInputLanguageExpression("jq", exp, input);
+            }
             return ExpressionBuilder.languageExpression("jq", exp);
         }
         // jsonpath
@@ -532,6 +550,12 @@ public class SimpleFunctionExpression extends LiteralExpression {
                 throw new SimpleParserException("Valid syntax: ${jsonpath(exp)} was: " + function, token.getIndex());
             }
             exp = StringHelper.removeQuotes(exp);
+            if (exp.startsWith("header:") || exp.startsWith("property:") || exp.startsWith("exchangeProperty:")
+                    || exp.startsWith("variable:")) {
+                String input = StringHelper.before(exp, ",");
+                exp = StringHelper.after(exp, ",");
+                return ExpressionBuilder.singleInputLanguageExpression("jq", exp, input);
+            }
             return ExpressionBuilder.languageExpression("jsonpath", exp);
         }
         remainder = ifStartsWithReturnRemainder("xpath(", function);
@@ -541,6 +565,12 @@ public class SimpleFunctionExpression extends LiteralExpression {
                 throw new SimpleParserException("Valid syntax: ${xpath(exp)} was: " + function, token.getIndex());
             }
             exp = StringHelper.removeQuotes(exp);
+            if (exp.startsWith("header:") || exp.startsWith("property:") || exp.startsWith("exchangeProperty:")
+                    || exp.startsWith("variable:")) {
+                String input = StringHelper.before(exp, ",");
+                exp = StringHelper.after(exp, ",");
+                return ExpressionBuilder.singleInputLanguageExpression("jq", exp, input);
+            }
             return ExpressionBuilder.languageExpression("xpath", exp);
         }
 
diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/tokenizer/TokenizeLanguage.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/tokenizer/TokenizeLanguage.java
index 0d9b0da25f8..3662e1b0284 100644
--- a/core/camel-core-languages/src/main/java/org/apache/camel/language/tokenizer/TokenizeLanguage.java
+++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/tokenizer/TokenizeLanguage.java
@@ -133,7 +133,8 @@ public class TokenizeLanguage extends SingleInputLanguageSupport implements Prop
 
         if (answer == null) {
             // use the regular tokenizer
-            final Expression exp = ExpressionBuilder.singleInputExpression(getHeaderName(), getPropertyName());
+            final Expression exp
+                    = ExpressionBuilder.singleInputExpression(getVariableName(), getHeaderName(), getPropertyName());
             if (regex) {
                 answer = ExpressionBuilder.regexTokenizeExpression(exp, token);
             } else {
diff --git a/core/camel-core-model/src/generated/resources/org/apache/camel/model/language/hl7terser.json b/core/camel-core-model/src/generated/resources/org/apache/camel/model/language/hl7terser.json
index c21ace421f3..175a11f5b72 100644
--- a/core/camel-core-model/src/generated/resources/org/apache/camel/model/language/hl7terser.json
+++ b/core/camel-core-model/src/generated/resources/org/apache/camel/model/language/hl7terser.json
@@ -15,9 +15,10 @@
   "properties": {
     "id": { "index": 0, "kind": "attribute", "displayName": "Id", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the id of this node" },
     "expression": { "index": 1, "kind": "value", "displayName": "Expression", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The expression value in your chosen language syntax" },
-    "headerName": { "index": 2, "kind": "attribute", "displayName": "Header Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of header to use as input, instead of the message body It has as higher precedent than the propertyName if both are set." },
-    "propertyName": { "index": 3, "kind": "attribute", "displayName": "Property Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of property to use as input, instead of the message body. It has a lower precedent than the headerName if both are set." },
-    "resultType": { "index": 4, "kind": "attribute", "displayName": "Result Type", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the class of the result type (type from output)" },
-    "trim": { "index": 5, "kind": "attribute", "displayName": "Trim", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to trim the value to remove leading and trailing whitespaces and line breaks" }
+    "variableName": { "index": 2, "kind": "attribute", "displayName": "Variable Name", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of variable to use as input, instead of the message body It has as higher precedent if other are set." },
+    "headerName": { "index": 3, "kind": "attribute", "displayName": "Header Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of header to use as input, instead of the message body It has as higher precedent than the propertyName if both are set." },
+    "propertyName": { "index": 4, "kind": "attribute", "displayName": "Property Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of property to use as input, instead of the message body. It has a lower precedent than the headerName if both are set." },
+    "resultType": { "index": 5, "kind": "attribute", "displayName": "Result Type", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the class of the result type (type from output)" },
+    "trim": { "index": 6, "kind": "attribute", "displayName": "Trim", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to trim the value to remove leading and trailing whitespaces and line breaks" }
   }
 }
diff --git a/core/camel-core-model/src/generated/resources/org/apache/camel/model/language/jq.json b/core/camel-core-model/src/generated/resources/org/apache/camel/model/language/jq.json
index c0d966613c3..04fb7ca5648 100644
--- a/core/camel-core-model/src/generated/resources/org/apache/camel/model/language/jq.json
+++ b/core/camel-core-model/src/generated/resources/org/apache/camel/model/language/jq.json
@@ -15,9 +15,10 @@
   "properties": {
     "id": { "index": 0, "kind": "attribute", "displayName": "Id", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the id of this node" },
     "expression": { "index": 1, "kind": "value", "displayName": "Expression", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The expression value in your chosen language syntax" },
-    "headerName": { "index": 2, "kind": "attribute", "displayName": "Header Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of header to use as input, instead of the message body It has as higher precedent than the propertyName if both are set." },
-    "propertyName": { "index": 3, "kind": "attribute", "displayName": "Property Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of property to use as input, instead of the message body. It has a lower precedent than the headerName if both are set." },
-    "resultType": { "index": 4, "kind": "attribute", "displayName": "Result Type", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the class of the result type (type from output)" },
-    "trim": { "index": 5, "kind": "attribute", "displayName": "Trim", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to trim the value to remove leading and trailing whitespaces and line breaks" }
+    "variableName": { "index": 2, "kind": "attribute", "displayName": "Variable Name", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of variable to use as input, instead of the message body It has as higher precedent if other are set." },
+    "headerName": { "index": 3, "kind": "attribute", "displayName": "Header Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of header to use as input, instead of the message body It has as higher precedent than the propertyName if both are set." },
+    "propertyName": { "index": 4, "kind": "attribute", "displayName": "Property Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of property to use as input, instead of the message body. It has a lower precedent than the headerName if both are set." },
+    "resultType": { "index": 5, "kind": "attribute", "displayName": "Result Type", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the class of the result type (type from output)" },
+    "trim": { "index": 6, "kind": "attribute", "displayName": "Trim", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to trim the value to remove leading and trailing whitespaces and line breaks" }
   }
 }
diff --git a/core/camel-core-model/src/generated/resources/org/apache/camel/model/language/jsonpath.json b/core/camel-core-model/src/generated/resources/org/apache/camel/model/language/jsonpath.json
index 18ebc270c0c..7a139068350 100644
--- a/core/camel-core-model/src/generated/resources/org/apache/camel/model/language/jsonpath.json
+++ b/core/camel-core-model/src/generated/resources/org/apache/camel/model/language/jsonpath.json
@@ -21,9 +21,10 @@
     "writeAsString": { "index": 5, "kind": "attribute", "displayName": "Write As String", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to write the output of each row\/element as a JSON String value instead of a Map\/POJO value." },
     "unpackArray": { "index": 6, "kind": "attribute", "displayName": "Unpack Array", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to unpack a single element json-array into an object." },
     "option": { "index": 7, "kind": "attribute", "displayName": "Option", "label": "advanced", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "DEFAULT_PATH_LEAF_TO_NULL", "ALWAYS_RETURN_LIST", "AS_PATH_LIST", "SUPPRESS_EXCEPTIONS", "REQUIRE_PROPERTIES" ], "deprecated": false, "autowired": false, "secret": false, "description": "To configure additional options on JSONPath. Multiple values can be separated by comma." },
-    "headerName": { "index": 8, "kind": "attribute", "displayName": "Header Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of header to use as input, instead of the message body It has as higher precedent than the propertyName if both are set." },
-    "propertyName": { "index": 9, "kind": "attribute", "displayName": "Property Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of property to use as input, instead of the message body. It has a lower precedent than the headerName if both are set." },
-    "resultType": { "index": 10, "kind": "attribute", "displayName": "Result Type", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the class of the result type (type from output)" },
-    "trim": { "index": 11, "kind": "attribute", "displayName": "Trim", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to trim the value to remove leading and trailing whitespaces and line breaks" }
+    "variableName": { "index": 8, "kind": "attribute", "displayName": "Variable Name", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of variable to use as input, instead of the message body It has as higher precedent if other are set." },
+    "headerName": { "index": 9, "kind": "attribute", "displayName": "Header Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of header to use as input, instead of the message body It has as higher precedent than the propertyName if both are set." },
+    "propertyName": { "index": 10, "kind": "attribute", "displayName": "Property Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of property to use as input, instead of the message body. It has a lower precedent than the headerName if both are set." },
+    "resultType": { "index": 11, "kind": "attribute", "displayName": "Result Type", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the class of the result type (type from output)" },
+    "trim": { "index": 12, "kind": "attribute", "displayName": "Trim", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to trim the value to remove leading and trailing whitespaces and line breaks" }
   }
 }
diff --git a/core/camel-core-model/src/main/java/org/apache/camel/builder/Builder.java b/core/camel-core-model/src/main/java/org/apache/camel/builder/Builder.java
index 042812ab99c..1b0ffec14c8 100644
--- a/core/camel-core-model/src/main/java/org/apache/camel/builder/Builder.java
+++ b/core/camel-core-model/src/main/java/org/apache/camel/builder/Builder.java
@@ -28,8 +28,8 @@ import org.apache.camel.model.language.JsonPathExpression;
 import org.apache.camel.model.language.LanguageExpression;
 import org.apache.camel.model.language.MethodCallExpression;
 import org.apache.camel.model.language.SimpleExpression;
-import org.apache.camel.model.language.WasmExpression;
 import org.apache.camel.model.language.VariableExpression;
+import org.apache.camel.model.language.WasmExpression;
 import org.apache.camel.util.ObjectHelper;
 
 /**
diff --git a/core/camel-core-model/src/main/java/org/apache/camel/builder/DataFormatClause.java b/core/camel-core-model/src/main/java/org/apache/camel/builder/DataFormatClause.java
index 63f314a77b9..4855d3502ff 100644
--- a/core/camel-core-model/src/main/java/org/apache/camel/builder/DataFormatClause.java
+++ b/core/camel-core-model/src/main/java/org/apache/camel/builder/DataFormatClause.java
@@ -186,8 +186,9 @@ public class DataFormatClause<T extends ProcessorDefinition<?>> {
     /**
      * Uses the beanio data format
      */
-    public T beanio(String mapping, String streamName, String encoding, boolean ignoreUnidentifiedRecords,
-                    boolean ignoreUnexpectedRecords, boolean ignoreInvalidRecords) {
+    public T beanio(
+            String mapping, String streamName, String encoding, boolean ignoreUnidentifiedRecords,
+            boolean ignoreUnexpectedRecords, boolean ignoreInvalidRecords) {
         BeanioDataFormat dataFormat = new BeanioDataFormat();
         dataFormat.setMapping(mapping);
         dataFormat.setStreamName(streamName);
diff --git a/core/camel-core-model/src/main/java/org/apache/camel/builder/ExpressionClauseSupport.java b/core/camel-core-model/src/main/java/org/apache/camel/builder/ExpressionClauseSupport.java
index 1efd4b30997..fd3e07ad584 100644
--- a/core/camel-core-model/src/main/java/org/apache/camel/builder/ExpressionClauseSupport.java
+++ b/core/camel-core-model/src/main/java/org/apache/camel/builder/ExpressionClauseSupport.java
@@ -44,8 +44,8 @@ import org.apache.camel.model.language.RefExpression;
 import org.apache.camel.model.language.SimpleExpression;
 import org.apache.camel.model.language.SpELExpression;
 import org.apache.camel.model.language.TokenizerExpression;
-import org.apache.camel.model.language.WasmExpression;
 import org.apache.camel.model.language.VariableExpression;
+import org.apache.camel.model.language.WasmExpression;
 import org.apache.camel.model.language.XMLTokenizerExpression;
 import org.apache.camel.model.language.XPathExpression;
 import org.apache.camel.model.language.XQueryExpression;
diff --git a/core/camel-core-model/src/main/java/org/apache/camel/builder/LanguageBuilderFactory.java b/core/camel-core-model/src/main/java/org/apache/camel/builder/LanguageBuilderFactory.java
index 4b225993b8a..d3cd60dc6cd 100644
--- a/core/camel-core-model/src/main/java/org/apache/camel/builder/LanguageBuilderFactory.java
+++ b/core/camel-core-model/src/main/java/org/apache/camel/builder/LanguageBuilderFactory.java
@@ -37,8 +37,8 @@ import org.apache.camel.model.language.RefExpression;
 import org.apache.camel.model.language.SimpleExpression;
 import org.apache.camel.model.language.SpELExpression;
 import org.apache.camel.model.language.TokenizerExpression;
-import org.apache.camel.model.language.WasmExpression;
 import org.apache.camel.model.language.VariableExpression;
+import org.apache.camel.model.language.WasmExpression;
 import org.apache.camel.model.language.XMLTokenizerExpression;
 import org.apache.camel.model.language.XPathExpression;
 import org.apache.camel.model.language.XQueryExpression;
diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/dataformat/BeanioDataFormat.java b/core/camel-core-model/src/main/java/org/apache/camel/model/dataformat/BeanioDataFormat.java
index 744b647454b..0b175a8b50f 100644
--- a/core/camel-core-model/src/main/java/org/apache/camel/model/dataformat/BeanioDataFormat.java
+++ b/core/camel-core-model/src/main/java/org/apache/camel/model/dataformat/BeanioDataFormat.java
@@ -21,6 +21,7 @@ import jakarta.xml.bind.annotation.XmlAccessorType;
 import jakarta.xml.bind.annotation.XmlAttribute;
 import jakarta.xml.bind.annotation.XmlRootElement;
 import jakarta.xml.bind.annotation.XmlTransient;
+
 import org.apache.camel.builder.DataFormatBuilder;
 import org.apache.camel.model.DataFormatDefinition;
 import org.apache.camel.spi.Metadata;
@@ -183,8 +184,8 @@ public class BeanioDataFormat extends DataFormatDefinition {
         private String unmarshalSingleObject;
 
         /**
-         * The BeanIO mapping file. Is by default loaded from the classpath. You can prefix with file:, http:, or classpath:
-         * to denote from where to load the mapping file.
+         * The BeanIO mapping file. Is by default loaded from the classpath. You can prefix with file:, http:, or
+         * classpath: to denote from where to load the mapping file.
          */
         public BeanioDataFormat.Builder mapping(String mapping) {
             this.mapping = mapping;
@@ -258,8 +259,8 @@ public class BeanioDataFormat extends DataFormatDefinition {
         }
 
         /**
-         * To use a custom org.apache.camel.dataformat.beanio.BeanIOErrorHandler as error handler while parsing. Configure
-         * the fully qualified class name of the error handler. Notice the options ignoreUnidentifiedRecords,
+         * To use a custom org.apache.camel.dataformat.beanio.BeanIOErrorHandler as error handler while parsing.
+         * Configure the fully qualified class name of the error handler. Notice the options ignoreUnidentifiedRecords,
          * ignoreUnexpectedRecords, and ignoreInvalidRecords may not be in use when you use a custom error handler.
          */
         public BeanioDataFormat.Builder beanReaderErrorHandlerType(String beanReaderErrorHandlerType) {
@@ -293,4 +294,4 @@ public class BeanioDataFormat extends DataFormatDefinition {
         }
     }
 
-}
\ No newline at end of file
+}
diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/language/SingleInputTypedExpressionDefinition.java b/core/camel-core-model/src/main/java/org/apache/camel/model/language/SingleInputTypedExpressionDefinition.java
index 8f4e30204da..148d26a6e3b 100644
--- a/core/camel-core-model/src/main/java/org/apache/camel/model/language/SingleInputTypedExpressionDefinition.java
+++ b/core/camel-core-model/src/main/java/org/apache/camel/model/language/SingleInputTypedExpressionDefinition.java
@@ -27,6 +27,8 @@ import org.apache.camel.spi.Metadata;
  */
 public abstract class SingleInputTypedExpressionDefinition extends TypedExpressionDefinition {
 
+    @XmlAttribute
+    private String variableName;
     @XmlAttribute
     @Metadata(label = "advanced")
     private String headerName;
@@ -47,10 +49,24 @@ public abstract class SingleInputTypedExpressionDefinition extends TypedExpressi
 
     protected SingleInputTypedExpressionDefinition(AbstractBuilder<?, ?> builder) {
         super(builder);
+        this.variableName = builder.variableName;
         this.headerName = builder.headerName;
         this.propertyName = builder.propertyName;
     }
 
+    public String getVariableName() {
+        return variableName;
+    }
+
+    /**
+     * Name of variable to use as input, instead of the message body
+     * </p>
+     * It has as higher precedent if other are set.
+     */
+    public void setVariableName(String variableName) {
+        this.variableName = variableName;
+    }
+
     public String getHeaderName() {
         return headerName;
     }
@@ -86,9 +102,20 @@ public abstract class SingleInputTypedExpressionDefinition extends TypedExpressi
             T extends AbstractBuilder<T, E>, E extends SingleInputTypedExpressionDefinition>
             extends TypedExpressionDefinition.AbstractBuilder<T, E> {
 
+        private String variableName;
         private String headerName;
         private String propertyName;
 
+        /**
+         * Name of variable to use as input, instead of the message body
+         * </p>
+         * It has as higher precedent if other are set.
+         */
+        public T variableName(String variableName) {
+            this.variableName = variableName;
+            return (T) this;
+        }
+
         /**
          * Name of header to use as input, instead of the message body
          * </p>
diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/language/WasmExpression.java b/core/camel-core-model/src/main/java/org/apache/camel/model/language/WasmExpression.java
index 65c608cb5f5..f1b898477f7 100644
--- a/core/camel-core-model/src/main/java/org/apache/camel/model/language/WasmExpression.java
+++ b/core/camel-core-model/src/main/java/org/apache/camel/model/language/WasmExpression.java
@@ -21,6 +21,7 @@ import jakarta.xml.bind.annotation.XmlAccessorType;
 import jakarta.xml.bind.annotation.XmlAttribute;
 import jakarta.xml.bind.annotation.XmlRootElement;
 import jakarta.xml.bind.annotation.XmlTransient;
+
 import org.apache.camel.spi.Metadata;
 
 /**
@@ -42,7 +43,6 @@ public class WasmExpression extends TypedExpressionDefinition {
         super(expression);
     }
 
-
     public WasmExpression(String expression, String module) {
         super(expression);
 
diff --git a/core/camel-core-processor/src/main/java/org/apache/camel/processor/Enricher.java b/core/camel-core-processor/src/main/java/org/apache/camel/processor/Enricher.java
index bb8350971ec..5dcabc96fac 100644
--- a/core/camel-core-processor/src/main/java/org/apache/camel/processor/Enricher.java
+++ b/core/camel-core-processor/src/main/java/org/apache/camel/processor/Enricher.java
@@ -16,6 +16,8 @@
  */
 package org.apache.camel.processor;
 
+import java.util.Map;
+
 import org.apache.camel.AggregationStrategy;
 import org.apache.camel.AsyncCallback;
 import org.apache.camel.CamelContext;
@@ -26,6 +28,7 @@ import org.apache.camel.ExchangePattern;
 import org.apache.camel.ExchangePropertyKey;
 import org.apache.camel.Expression;
 import org.apache.camel.spi.EndpointUtilizationStatistics;
+import org.apache.camel.spi.HeadersMapFactory;
 import org.apache.camel.spi.IdAware;
 import org.apache.camel.spi.ProcessorExchangeFactory;
 import org.apache.camel.spi.RouteIdAware;
@@ -63,6 +66,7 @@ public class Enricher extends AsyncProcessorSupport implements IdAware, RouteIdA
     private boolean ignoreInvalidEndpoint;
     private boolean allowOptimisedComponents = true;
     private boolean autoStartupComponents = true;
+    private HeadersMapFactory headersMapFactory;
     private ProcessorExchangeFactory processorExchangeFactory;
     private SendDynamicProcessor sendDynamicProcessor;
 
@@ -188,9 +192,12 @@ public class Enricher extends AsyncProcessorSupport implements IdAware, RouteIdA
         // if we should store the received message body in a variable,
         // then we need to preserve the original message body
         Object body = null;
+        Map<String, Object> headers = null;
         if (variableReceive != null) {
             try {
                 body = exchange.getMessage().getBody();
+                // do a defensive copy of the headers
+                headers = headersMapFactory.newMap(exchange.getMessage().getHeaders());
             } catch (Exception throwable) {
                 exchange.setException(throwable);
                 callback.done(true);
@@ -198,6 +205,7 @@ public class Enricher extends AsyncProcessorSupport implements IdAware, RouteIdA
             }
         }
         final Object originalBody = body;
+        final Map<String, Object> originalHeaders = headers;
 
         return sendDynamicProcessor.process(resourceExchange, new AsyncCallback() {
             @Override
@@ -216,9 +224,10 @@ public class Enricher extends AsyncProcessorSupport implements IdAware, RouteIdA
                         if (aggregatedExchange != null) {
                             if (variableReceive != null) {
                                 // result should be stored in variable instead of message body
-                                Object value = aggregatedExchange.getMessage().getBody();
-                                ExchangeHelper.setVariable(exchange, variableReceive, value);
-                                aggregatedExchange.getMessage().setBody(originalBody);
+                                ExchangeHelper.setVariableFromMessageBodyAndHeaders(exchange, variableReceive,
+                                        exchange.getMessage());
+                                exchange.getMessage().setBody(originalBody);
+                                exchange.getMessage().setHeaders(originalHeaders);
                             }
                             // copy aggregation result onto original exchange (preserving pattern)
                             copyResultsWithoutCorrelationId(exchange, aggregatedExchange);
@@ -302,6 +311,11 @@ public class Enricher extends AsyncProcessorSupport implements IdAware, RouteIdA
         ServiceHelper.buildService(processorExchangeFactory, sendDynamicProcessor);
     }
 
+    @Override
+    protected void doInit() throws Exception {
+        headersMapFactory = camelContext.getCamelContextExtension().getHeadersMapFactory();
+    }
+
     @Override
     protected void doStart() throws Exception {
         ServiceHelper.startService(processorExchangeFactory, aggregationStrategy, sendDynamicProcessor);
diff --git a/core/camel-core-processor/src/main/java/org/apache/camel/processor/PollEnricher.java b/core/camel-core-processor/src/main/java/org/apache/camel/processor/PollEnricher.java
index 21759c73f24..d939963dd26 100644
--- a/core/camel-core-processor/src/main/java/org/apache/camel/processor/PollEnricher.java
+++ b/core/camel-core-processor/src/main/java/org/apache/camel/processor/PollEnricher.java
@@ -16,6 +16,8 @@
  */
 package org.apache.camel.processor;
 
+import java.util.Map;
+
 import org.apache.camel.AggregationStrategy;
 import org.apache.camel.AsyncCallback;
 import org.apache.camel.CamelContext;
@@ -32,6 +34,7 @@ import org.apache.camel.PollingConsumer;
 import org.apache.camel.spi.ConsumerCache;
 import org.apache.camel.spi.EndpointUtilizationStatistics;
 import org.apache.camel.spi.ExceptionHandler;
+import org.apache.camel.spi.HeadersMapFactory;
 import org.apache.camel.spi.IdAware;
 import org.apache.camel.spi.NormalizedEndpointUri;
 import org.apache.camel.spi.RouteIdAware;
@@ -64,7 +67,8 @@ public class PollEnricher extends AsyncProcessorSupport implements IdAware, Rout
 
     private CamelContext camelContext;
     private ConsumerCache consumerCache;
-    protected volatile String scheme;
+    private HeadersMapFactory headersMapFactory;
+    private volatile String scheme;
     private String id;
     private String routeId;
     private String variableReceive;
@@ -320,9 +324,12 @@ public class PollEnricher extends AsyncProcessorSupport implements IdAware, Rout
         // if we should store the received message body in a variable,
         // then we need to preserve the original message body
         Object originalBody = null;
+        Map<String, Object> originalHeaders = null;
         if (variableReceive != null) {
             try {
                 originalBody = exchange.getMessage().getBody();
+                // do a defensive copy of the headers
+                originalHeaders = headersMapFactory.newMap(exchange.getMessage().getHeaders());
             } catch (Exception throwable) {
                 exchange.setException(throwable);
                 callback.done(true);
@@ -345,9 +352,9 @@ public class PollEnricher extends AsyncProcessorSupport implements IdAware, Rout
                 if (aggregatedExchange != null) {
                     if (variableReceive != null) {
                         // result should be stored in variable instead of message body
-                        Object value = aggregatedExchange.getMessage().getBody();
-                        ExchangeHelper.setVariable(exchange, variableReceive, value);
-                        aggregatedExchange.getMessage().setBody(originalBody);
+                        ExchangeHelper.setVariableFromMessageBodyAndHeaders(exchange, variableReceive, exchange.getMessage());
+                        exchange.getMessage().setBody(originalBody);
+                        exchange.getMessage().setHeaders(originalHeaders);
                     }
                     // copy aggregation result onto original exchange (preserving pattern)
                     copyResultsPreservePattern(exchange, aggregatedExchange);
@@ -485,6 +492,8 @@ public class PollEnricher extends AsyncProcessorSupport implements IdAware, Rout
             scheme = ExchangeHelper.resolveScheme(u);
         }
 
+        headersMapFactory = camelContext.getCamelContextExtension().getHeadersMapFactory();
+
         ServiceHelper.initService(consumerCache, aggregationStrategy);
     }
 
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 13620cc6ce6..d877f180684 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
@@ -16,6 +16,8 @@
  */
 package org.apache.camel.processor;
 
+import java.util.Map;
+
 import org.apache.camel.AsyncCallback;
 import org.apache.camel.CamelContext;
 import org.apache.camel.CamelContextAware;
@@ -29,6 +31,7 @@ import org.apache.camel.NoTypeConversionAvailableException;
 import org.apache.camel.Processor;
 import org.apache.camel.ResolveEndpointFailedException;
 import org.apache.camel.spi.EndpointUtilizationStatistics;
+import org.apache.camel.spi.HeadersMapFactory;
 import org.apache.camel.spi.IdAware;
 import org.apache.camel.spi.NormalizedEndpointUri;
 import org.apache.camel.spi.ProducerCache;
@@ -62,6 +65,7 @@ public class SendDynamicProcessor extends AsyncProcessorSupport implements IdAwa
     protected String variableReceive;
     protected ExchangePattern pattern;
     protected ProducerCache producerCache;
+    protected HeadersMapFactory headersMapFactory;
     protected String id;
     protected String routeId;
     protected boolean ignoreInvalidEndpoint;
@@ -101,8 +105,6 @@ public class SendDynamicProcessor extends AsyncProcessorSupport implements IdAwa
 
     @Override
     public boolean process(Exchange exchange, final AsyncCallback callback) {
-        // TODO: variables
-
         if (!isStarted()) {
             exchange.setException(new IllegalStateException("SendProcessor has not been started: " + this));
             callback.done(true);
@@ -179,9 +181,12 @@ public class SendDynamicProcessor extends AsyncProcessorSupport implements IdAwa
         // if we should store the received message body in a variable,
         // then we need to preserve the original message body
         Object body = null;
+        Map<String, Object> headers = null;
         if (variableReceive != null) {
             try {
                 body = exchange.getMessage().getBody();
+                // do a defensive copy of the headers
+                headers = headersMapFactory.newMap(exchange.getMessage().getHeaders());
             } catch (Exception throwable) {
                 exchange.setException(throwable);
                 callback.done(true);
@@ -189,6 +194,7 @@ public class SendDynamicProcessor extends AsyncProcessorSupport implements IdAwa
             }
         }
         final Object originalBody = body;
+        final Map<String, Object> originalHeaders = headers;
 
         // send the exchange to the destination using the producer cache
         final Processor preProcessor = preAwareProcessor;
@@ -204,7 +210,6 @@ public class SendDynamicProcessor extends AsyncProcessorSupport implements IdAwa
                 }
                 // replace message body with variable
                 if (variableSend != null) {
-                    // it may be a global variable
                     Object value = ExchangeHelper.getVariable(exchange, variableSend);
                     exchange.getMessage().setBody(value);
                 }
@@ -217,30 +222,28 @@ public class SendDynamicProcessor extends AsyncProcessorSupport implements IdAwa
             }
 
             LOG.debug(">>>> {} {}", endpoint, e);
-            return p.process(target, new AsyncCallback() {
-                public void done(boolean doneSync) {
-                    // restore previous MEP
-                    target.setPattern(existingPattern);
-                    try {
-                        if (postProcessor != null) {
-                            postProcessor.process(target);
-                        }
-                    } catch (Exception e) {
-                        target.setException(e);
-                    }
-                    // stop endpoint if prototype as it was only used once
-                    if (stopEndpoint) {
-                        ServiceHelper.stopAndShutdownService(endpoint);
-                    }
-                    // result should be stored in variable instead of message body
-                    if (variableReceive != null) {
-                        Object value = exchange.getMessage().getBody();
-                        ExchangeHelper.setVariable(exchange, variableReceive, value);
-                        exchange.getMessage().setBody(originalBody);
+            return p.process(target, doneSync -> {
+                // restore previous MEP
+                target.setPattern(existingPattern);
+                try {
+                    if (postProcessor != null) {
+                        postProcessor.process(target);
                     }
-                    // signal we are done
-                    c.done(doneSync);
+                } catch (Exception e1) {
+                    target.setException(e1);
+                }
+                // stop endpoint if prototype as it was only used once
+                if (stopEndpoint) {
+                    ServiceHelper.stopAndShutdownService(endpoint);
                 }
+                // result should be stored in variable instead of message body
+                if (variableReceive != null) {
+                    ExchangeHelper.setVariableFromMessageBodyAndHeaders(exchange, variableReceive, exchange.getMessage());
+                    exchange.getMessage().setBody(originalBody);
+                    exchange.getMessage().setHeaders(originalHeaders);
+                }
+                // signal we are done
+                c.done(doneSync);
             });
         });
     }
@@ -379,6 +382,8 @@ public class SendDynamicProcessor extends AsyncProcessorSupport implements IdAwa
             }
         }
         ServiceHelper.initService(dynamicAware);
+
+        headersMapFactory = camelContext.getCamelContextExtension().getHeadersMapFactory();
     }
 
     @Override
diff --git a/core/camel-core-processor/src/main/java/org/apache/camel/processor/SendProcessor.java b/core/camel-core-processor/src/main/java/org/apache/camel/processor/SendProcessor.java
index 960450d8d44..b04e75a88fb 100644
--- a/core/camel-core-processor/src/main/java/org/apache/camel/processor/SendProcessor.java
+++ b/core/camel-core-processor/src/main/java/org/apache/camel/processor/SendProcessor.java
@@ -16,6 +16,7 @@
  */
 package org.apache.camel.processor;
 
+import java.util.Map;
 import java.util.concurrent.atomic.AtomicLong;
 
 import org.apache.camel.AsyncCallback;
@@ -27,6 +28,7 @@ import org.apache.camel.Exchange;
 import org.apache.camel.ExchangePattern;
 import org.apache.camel.ExchangePropertyKey;
 import org.apache.camel.Traceable;
+import org.apache.camel.spi.HeadersMapFactory;
 import org.apache.camel.spi.IdAware;
 import org.apache.camel.spi.ProducerCache;
 import org.apache.camel.spi.RouteIdAware;
@@ -56,6 +58,7 @@ public class SendProcessor extends AsyncProcessorSupport implements Traceable, E
     protected final ExchangePattern pattern;
     protected ProducerCache producerCache;
     protected AsyncProducer producer;
+    protected HeadersMapFactory headersMapFactory;
     protected final Endpoint destination;
     protected String variableSend;
     protected String variableReceive;
@@ -128,12 +131,14 @@ public class SendProcessor extends AsyncProcessorSupport implements Traceable, E
         // if you want to permanently to change the MEP then use .setExchangePattern in the DSL
         final ExchangePattern existingPattern = exchange.getPattern();
 
-        // if we should store the received message body in a variable,
-        // then we need to preserve the original message body
+        // when using variables then we need to remember original data
         Object body = null;
-        if (variableReceive != null) {
+        Map<String, Object> headers = null;
+        if (variableSend != null || variableReceive != null) {
             try {
                 body = exchange.getMessage().getBody();
+                // do a defensive copy of the headers
+                headers = headersMapFactory.newMap(exchange.getMessage().getHeaders());
             } catch (Exception throwable) {
                 exchange.setException(throwable);
                 callback.done(true);
@@ -141,6 +146,7 @@ public class SendProcessor extends AsyncProcessorSupport implements Traceable, E
             }
         }
         final Object originalBody = body;
+        final Map<String, Object> originalHeaders = headers;
 
         if (extendedStatistics) {
             counter.incrementAndGet();
@@ -172,11 +178,12 @@ public class SendProcessor extends AsyncProcessorSupport implements Traceable, E
             if (newCallback) {
                 ac = doneSync -> {
                     try {
-                        // result should be stored in variable instead of message body
+                        // result should be stored in variable instead of message body/headers
                         if (variableReceive != null) {
-                            Object value = exchange.getMessage().getBody();
-                            ExchangeHelper.setVariable(exchange, variableReceive, value);
+                            ExchangeHelper.setVariableFromMessageBodyAndHeaders(exchange, variableReceive,
+                                    exchange.getMessage());
                             exchange.getMessage().setBody(originalBody);
+                            exchange.getMessage().setHeaders(originalHeaders);
                         }
                         // restore previous MEP
                         target.setPattern(existingPattern);
@@ -193,9 +200,10 @@ public class SendProcessor extends AsyncProcessorSupport implements Traceable, E
             try {
                 // replace message body with variable
                 if (variableSend != null) {
-                    // it may be a global variable
                     Object value = ExchangeHelper.getVariable(exchange, variableSend);
                     exchange.getMessage().setBody(value);
+                    // TODO: empty headers or
+
                 }
 
                 LOG.debug(">>>> {} {}", destination, exchange);
@@ -220,7 +228,6 @@ public class SendProcessor extends AsyncProcessorSupport implements Traceable, E
 
             // replace message body with variable
             if (variableSend != null) {
-                // it may be a global variable
                 Object value = ExchangeHelper.getVariable(exchange, variableSend);
                 exchange.getMessage().setBody(value);
             }
@@ -232,11 +239,12 @@ public class SendProcessor extends AsyncProcessorSupport implements Traceable, E
                     (producer, ex, cb) -> producer.process(ex, doneSync -> {
                         // restore previous MEP
                         exchange.setPattern(existingPattern);
-                        // result should be stored in variable instead of message body
+                        // result should be stored in variable instead of message body/headers
                         if (variableReceive != null) {
-                            Object value = exchange.getMessage().getBody();
-                            ExchangeHelper.setVariable(exchange, variableReceive, value);
+                            ExchangeHelper.setVariableFromMessageBodyAndHeaders(exchange, variableReceive,
+                                    exchange.getMessage());
                             exchange.getMessage().setBody(originalBody);
+                            exchange.getMessage().setHeaders(originalHeaders);
                         }
                         // signal we are done
                         cb.done(doneSync);
@@ -292,6 +300,8 @@ public class SendProcessor extends AsyncProcessorSupport implements Traceable, E
             producerCache = new DefaultProducerCache(this, camelContext, 0);
             // do not add as service as we do not want to manage the producer cache
         }
+
+        headersMapFactory = camelContext.getCamelContextExtension().getHeadersMapFactory();
     }
 
     @Override
diff --git a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/RouteReifier.java b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/RouteReifier.java
index d53870207a7..31a92c2582e 100644
--- a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/RouteReifier.java
+++ b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/RouteReifier.java
@@ -422,7 +422,7 @@ public class RouteReifier extends ProcessorReifier<RouteDefinition> {
     }
 
     /**
-     * Advice for copying the message body into a variable
+     * Advice for moving message body into a variable when using variableReceive mode
      */
     private static class VariableAdvice implements CamelInternalProcessorAdvice<Object> {
 
@@ -434,8 +434,9 @@ public class RouteReifier extends ProcessorReifier<RouteDefinition> {
 
         @Override
         public Object before(Exchange exchange) throws Exception {
-            Object body = exchange.getMessage().getBody();
-            ExchangeHelper.setVariable(exchange, name, body);
+            // move body to variable
+            ExchangeHelper.setVariableFromMessageBodyAndHeaders(exchange, name, exchange.getMessage());
+            exchange.getMessage().setBody(null);
             return null;
         }
 
diff --git a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/dataformat/BeanioDataFormatReifier.java b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/dataformat/BeanioDataFormatReifier.java
index 1e49560407a..bd9708e3612 100644
--- a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/dataformat/BeanioDataFormatReifier.java
+++ b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/dataformat/BeanioDataFormatReifier.java
@@ -16,12 +16,12 @@
  */
 package org.apache.camel.reifier.dataformat;
 
+import java.util.Map;
+
 import org.apache.camel.CamelContext;
 import org.apache.camel.model.DataFormatDefinition;
 import org.apache.camel.model.dataformat.BeanioDataFormat;
 
-import java.util.Map;
-
 public class BeanioDataFormatReifier extends DataFormatReifier<BeanioDataFormat> {
 
     public BeanioDataFormatReifier(CamelContext camelContext, DataFormatDefinition definition) {
diff --git a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/language/JsonPathExpressionReifier.java b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/language/JsonPathExpressionReifier.java
index 71e3562bb8b..270c7364273 100644
--- a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/language/JsonPathExpressionReifier.java
+++ b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/language/JsonPathExpressionReifier.java
@@ -28,7 +28,7 @@ public class JsonPathExpressionReifier extends SingleInputTypedExpressionReifier
 
     @Override
     protected Object[] createProperties() {
-        Object[] properties = new Object[9];
+        Object[] properties = new Object[10];
         properties[0] = definition.getResultType();
         properties[1] = parseBoolean(definition.getSuppressExceptions());
         properties[2] = parseBoolean(definition.getAllowSimple());
@@ -38,6 +38,7 @@ public class JsonPathExpressionReifier extends SingleInputTypedExpressionReifier
         properties[6] = parseString(definition.getHeaderName());
         properties[7] = parseString(definition.getOption());
         properties[8] = parseString(definition.getPropertyName());
+        properties[9] = parseString(definition.getVariableName());
         return properties;
     }
 
diff --git a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/language/SingleInputTypedExpressionReifier.java b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/language/SingleInputTypedExpressionReifier.java
index 3eab302ec6b..623d774a473 100644
--- a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/language/SingleInputTypedExpressionReifier.java
+++ b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/language/SingleInputTypedExpressionReifier.java
@@ -35,10 +35,11 @@ class SingleInputTypedExpressionReifier<T extends SingleInputTypedExpressionDefi
 
     @Override
     protected Object[] createProperties() {
-        Object[] properties = new Object[3];
+        Object[] properties = new Object[4];
         properties[0] = definition.getResultType();
-        properties[1] = parseString(definition.getHeaderName());
-        properties[2] = parseString(definition.getPropertyName());
+        properties[1] = parseString(definition.getVariableName());
+        properties[2] = parseString(definition.getHeaderName());
+        properties[3] = parseString(definition.getPropertyName());
         return properties;
     }
 }
diff --git a/core/camel-core/src/test/java/org/apache/camel/language/VariableTest.java b/core/camel-core/src/test/java/org/apache/camel/language/VariableTest.java
index 95486d2a77d..8a0de284b32 100644
--- a/core/camel-core/src/test/java/org/apache/camel/language/VariableTest.java
+++ b/core/camel-core/src/test/java/org/apache/camel/language/VariableTest.java
@@ -16,10 +16,15 @@
  */
 package org.apache.camel.language;
 
+import java.util.Map;
+
 import org.apache.camel.LanguageTestSupport;
 import org.apache.camel.language.variable.VariableLanguage;
+import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 public class VariableTest extends LanguageTestSupport {
@@ -39,6 +44,27 @@ public class VariableTest extends LanguageTestSupport {
         assertPredicate("varLocal", false);
     }
 
+    @Test
+    public void testVariableHeaders() throws Exception {
+        exchange.setVariable("header:myKey.foo", "abc");
+        exchange.setVariable("header:myKey.bar", 123);
+        exchange.setVariable("myOtherKey", "Hello Again");
+
+        assertEquals("abc", exchange.getVariable("header:myKey.foo"));
+        assertEquals(123, exchange.getVariable("header:myKey.bar"));
+
+        Map map = exchange.getVariable("header:myKey", Map.class);
+        assertNotNull(map);
+        assertEquals(2, map.size());
+        assertEquals("abc", map.get("foo"));
+        assertEquals(123, map.get("bar"));
+    }
+
+    @Test
+    public void testInvalidHeaderKey() {
+        Assertions.assertThrows(IllegalArgumentException.class, () -> exchange.getVariable("header:"));
+    }
+
     @Test
     public void testSingleton() {
         VariableLanguage prop = new VariableLanguage();
diff --git a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleTest.java b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleTest.java
index 7d3dd1f7bb3..fdcf759ffd7 100644
--- a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleTest.java
+++ b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleTest.java
@@ -2328,6 +2328,15 @@ public class SimpleTest extends LanguageTestSupport {
         assertThrows(IllegalArgumentException.class, () -> evaluateExpression("${empty(unknownType)}", null));
     }
 
+    @Test
+    public void testPretty() {
+        assertExpression(exchange, "${pretty('Hello')}", "Hello");
+        assertExpression(exchange, "${pretty(${body})}", "<hello id=\"m123\">\n</hello>");
+
+        exchange.getMessage().setBody("{\"name\": \"Jack\", \"id\": 123}");
+        assertExpression(exchange, "${pretty(${body})}", "{\n\t\"name\": \"Jack\",\n\t\"id\": 123\n}\n");
+    }
+
     private void assertExpressionCreateNewEmpty(
             String type, Class<?> expectedClass, java.util.function.Predicate<Object> isEmptyAssertion) {
         Object value = evaluateExpression("${empty(%s)}".formatted(type), null);
diff --git a/core/camel-core/src/test/java/org/apache/camel/processor/EnrichVariableHeadersTest.java b/core/camel-core/src/test/java/org/apache/camel/processor/EnrichVariableHeadersTest.java
new file mode 100644
index 00000000000..229e5c86d4e
--- /dev/null
+++ b/core/camel-core/src/test/java/org/apache/camel/processor/EnrichVariableHeadersTest.java
@@ -0,0 +1,94 @@
+/*
+ * 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.processor;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.builder.RouteBuilder;
+import org.junit.jupiter.api.Test;
+
+public class EnrichVariableHeadersTest extends ContextTestSupport {
+
+    @Test
+    public void testSend() throws Exception {
+        getMockEndpoint("mock:before").expectedBodiesReceived("World");
+        getMockEndpoint("mock:before").expectedVariableReceived("hello", "Camel");
+        getMockEndpoint("mock:result").expectedBodiesReceived("Bye Camel");
+        getMockEndpoint("mock:result").expectedVariableReceived("hello", "Camel");
+        getMockEndpoint("mock:result").message(0).header("echo").isEqualTo("CamelCamel");
+
+        template.sendBody("direct:send", "World");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Test
+    public void testReceive() throws Exception {
+        getMockEndpoint("mock:after").expectedBodiesReceived("World");
+        getMockEndpoint("mock:after").expectedVariableReceived("bye", "Bye World");
+        getMockEndpoint("mock:after").message(0).header("echo").isNull();
+        getMockEndpoint("mock:result").expectedBodiesReceived("Bye World");
+        getMockEndpoint("mock:result").expectedVariableReceived("bye", "Bye World");
+        getMockEndpoint("mock:result").message(0).header("echo").isNull();
+
+        template.sendBody("direct:receive", "World");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Test
+    public void testSendAndReceive() throws Exception {
+        getMockEndpoint("mock:before").expectedBodiesReceived("World");
+        getMockEndpoint("mock:before").expectedVariableReceived("hello", "Camel");
+        getMockEndpoint("mock:result").expectedBodiesReceived("World");
+        getMockEndpoint("mock:result").expectedVariableReceived("bye", "Bye Camel");
+        getMockEndpoint("mock:result").message(0).header("echo").isNull();
+
+        template.sendBody("direct:sendAndReceive", "World");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() throws Exception {
+        return new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from("direct:send")
+                        .setVariable("hello", simple("Camel"))
+                        .to("mock:before")
+                        .enrich().constant("direct:foo").variableSend("hello")
+                        .to("mock:result");
+
+                from("direct:receive")
+                        .enrich().constant("direct:foo").variableReceive("bye")
+                        .to("mock:after")
+                        .setBody(simple("${variable:bye}"))
+                        .to("mock:result");
+
+                from("direct:sendAndReceive")
+                        .setVariable("hello", simple("Camel"))
+                        .to("mock:before")
+                        .enrich().constant("direct:foo").variableSend("hello").variableReceive("bye")
+                        .to("mock:result");
+
+                from("direct:foo")
+                        .setHeader("echo", simple("${body}${body}"))
+                        .transform().simple("Bye ${body}");
+            }
+        };
+    }
+}
diff --git a/core/camel-core/src/test/java/org/apache/camel/processor/FromVariableTest.java b/core/camel-core/src/test/java/org/apache/camel/processor/FromVariableTest.java
index 3dfc5800ee8..1d06aeabecb 100644
--- a/core/camel-core/src/test/java/org/apache/camel/processor/FromVariableTest.java
+++ b/core/camel-core/src/test/java/org/apache/camel/processor/FromVariableTest.java
@@ -16,15 +16,18 @@
  */
 package org.apache.camel.processor;
 
+import java.util.Map;
+
 import org.apache.camel.ContextTestSupport;
 import org.apache.camel.builder.RouteBuilder;
+import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
 public class FromVariableTest extends ContextTestSupport {
 
     @Test
     public void testOriginalBody() throws Exception {
-        getMockEndpoint("mock:foo").expectedBodiesReceived("Bye World");
+        getMockEndpoint("mock:foo").expectedBodiesReceived("Bye ");
         getMockEndpoint("mock:result").expectedBodiesReceived("World");
 
         template.sendBody("direct:start", "World");
@@ -32,12 +35,33 @@ public class FromVariableTest extends ContextTestSupport {
         assertMockEndpointsSatisfied();
     }
 
+    @Test
+    public void testOriginalHeaders() throws Exception {
+        getMockEndpoint("mock:foo").expectedBodiesReceived("Bye ");
+        getMockEndpoint("mock:foo").expectedHeaderReceived("foo", 456);
+        getMockEndpoint("mock:foo").whenAnyExchangeReceived(e -> {
+            Map m = e.getVariable("header:myKey", Map.class);
+            Assertions.assertNotNull(m);
+            Assertions.assertEquals(1, m.size());
+            Assertions.assertEquals(123, m.get("foo"));
+        });
+
+        getMockEndpoint("mock:result").expectedBodiesReceived("World");
+        getMockEndpoint("mock:result").expectedHeaderReceived("foo", 456);
+
+        template.sendBodyAndHeader("direct:start", "World", "foo", 123);
+
+        assertMockEndpointsSatisfied();
+    }
+
     @Override
     protected RouteBuilder createRouteBuilder() throws Exception {
         return new RouteBuilder() {
             @Override
             public void configure() throws Exception {
                 fromV("direct:start", "myKey")
+                        .setHeader("foo", constant(456))
+                        .setHeader("bar", constant("Murphy"))
                         .transform().simple("Bye ${body}")
                         .to("mock:foo")
                         .setBody(simple("${variable:myKey}"))
diff --git a/core/camel-core/src/test/java/org/apache/camel/processor/FromVariableTest.java b/core/camel-core/src/test/java/org/apache/camel/processor/PollEnrichVariableHeadersTest.java
similarity index 56%
copy from core/camel-core/src/test/java/org/apache/camel/processor/FromVariableTest.java
copy to core/camel-core/src/test/java/org/apache/camel/processor/PollEnrichVariableHeadersTest.java
index 3dfc5800ee8..cadd647e286 100644
--- a/core/camel-core/src/test/java/org/apache/camel/processor/FromVariableTest.java
+++ b/core/camel-core/src/test/java/org/apache/camel/processor/PollEnrichVariableHeadersTest.java
@@ -20,14 +20,20 @@ import org.apache.camel.ContextTestSupport;
 import org.apache.camel.builder.RouteBuilder;
 import org.junit.jupiter.api.Test;
 
-public class FromVariableTest extends ContextTestSupport {
+public class PollEnrichVariableHeadersTest extends ContextTestSupport {
 
     @Test
-    public void testOriginalBody() throws Exception {
-        getMockEndpoint("mock:foo").expectedBodiesReceived("Bye World");
-        getMockEndpoint("mock:result").expectedBodiesReceived("World");
+    public void testReceive() throws Exception {
+        template.sendBodyAndHeader("seda:foo", "Bye World", "echo", "CamelCamel");
 
-        template.sendBody("direct:start", "World");
+        getMockEndpoint("mock:after").expectedBodiesReceived("World");
+        getMockEndpoint("mock:after").expectedVariableReceived("bye", "Bye World");
+        getMockEndpoint("mock:result").expectedBodiesReceived("Bye World");
+        getMockEndpoint("mock:result").expectedVariableReceived("bye", "Bye World");
+        getMockEndpoint("mock:result").expectedVariableReceived("header:bye.echo", "CamelCamel");
+        getMockEndpoint("mock:result").message(0).header("echo").isNull();
+
+        template.sendBody("direct:receive", "World");
 
         assertMockEndpointsSatisfied();
     }
@@ -37,10 +43,10 @@ public class FromVariableTest extends ContextTestSupport {
         return new RouteBuilder() {
             @Override
             public void configure() throws Exception {
-                fromV("direct:start", "myKey")
-                        .transform().simple("Bye ${body}")
-                        .to("mock:foo")
-                        .setBody(simple("${variable:myKey}"))
+                from("direct:receive")
+                        .pollEnrich().constant("seda:foo").timeout(1000).variableReceive("bye")
+                        .to("mock:after")
+                        .setBody(simple("${variable:bye}"))
                         .to("mock:result");
             }
         };
diff --git a/core/camel-core/src/test/java/org/apache/camel/processor/ToDynamicVariableHeadersTest.java b/core/camel-core/src/test/java/org/apache/camel/processor/ToDynamicVariableHeadersTest.java
new file mode 100644
index 00000000000..12e4c1cafc4
--- /dev/null
+++ b/core/camel-core/src/test/java/org/apache/camel/processor/ToDynamicVariableHeadersTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.processor;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.builder.RouteBuilder;
+import org.junit.jupiter.api.Test;
+
+public class ToDynamicVariableHeadersTest extends ContextTestSupport {
+
+    @Test
+    public void testSend() throws Exception {
+        getMockEndpoint("mock:before").expectedBodiesReceived("World");
+        getMockEndpoint("mock:before").expectedVariableReceived("hello", "Camel");
+        getMockEndpoint("mock:result").expectedBodiesReceived("Bye Camel");
+        getMockEndpoint("mock:result").expectedVariableReceived("hello", "Camel");
+        getMockEndpoint("mock:result").message(0).header("echo").isEqualTo("CamelCamel");
+
+        template.sendBodyAndHeader("direct:send", "World", "where", "foo");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Test
+    public void testReceive() throws Exception {
+        getMockEndpoint("mock:after").expectedBodiesReceived("World");
+        getMockEndpoint("mock:after").expectedVariableReceived("bye", "Bye World");
+        getMockEndpoint("mock:result").expectedBodiesReceived("Bye World");
+        getMockEndpoint("mock:result").expectedVariableReceived("bye", "Bye World");
+        getMockEndpoint("mock:result").message(0).header("echo").isNull();
+
+        template.sendBodyAndHeader("direct:receive", "World", "where", "foo");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Test
+    public void testSendAndReceive() throws Exception {
+        getMockEndpoint("mock:before").expectedBodiesReceived("World");
+        getMockEndpoint("mock:before").expectedVariableReceived("hello", "Camel");
+        getMockEndpoint("mock:result").expectedBodiesReceived("World");
+        getMockEndpoint("mock:result").expectedVariableReceived("bye", "Bye Camel");
+        getMockEndpoint("mock:result").message(0).header("echo").isNull();
+
+        template.sendBodyAndHeader("direct:sendAndReceive", "World", "where", "foo");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() throws Exception {
+        return new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from("direct:send")
+                        .setVariable("hello", simple("Camel"))
+                        .to("mock:before")
+                        .toD("direct:${header.where}", "hello", null)
+                        .to("mock:result");
+
+                from("direct:receive")
+                        .toD("direct:${header.where}", null, "bye")
+                        .to("mock:after")
+                        .setBody(simple("${variable:bye}"))
+                        .to("mock:result");
+
+                from("direct:sendAndReceive")
+                        .setVariable("hello", simple("Camel"))
+                        .to("mock:before")
+                        .toD("direct:${header.where}", "hello", "bye")
+                        .to("mock:result");
+
+                from("direct:foo")
+                        .setHeader("echo", simple("${body}${body}"))
+                        .transform().simple("Bye ${body}");
+            }
+        };
+    }
+}
diff --git a/core/camel-core/src/test/java/org/apache/camel/processor/ToVariableHeadersTest.java b/core/camel-core/src/test/java/org/apache/camel/processor/ToVariableHeadersTest.java
new file mode 100644
index 00000000000..dbadd6732e7
--- /dev/null
+++ b/core/camel-core/src/test/java/org/apache/camel/processor/ToVariableHeadersTest.java
@@ -0,0 +1,94 @@
+/*
+ * 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.processor;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.builder.RouteBuilder;
+import org.junit.jupiter.api.Test;
+
+public class ToVariableHeadersTest extends ContextTestSupport {
+
+    @Test
+    public void testSend() throws Exception {
+        getMockEndpoint("mock:before").expectedBodiesReceived("World");
+        getMockEndpoint("mock:before").expectedVariableReceived("hello", "Camel");
+        getMockEndpoint("mock:result").expectedBodiesReceived("Bye Camel");
+        getMockEndpoint("mock:result").expectedVariableReceived("hello", "Camel");
+        getMockEndpoint("mock:result").message(0).header("echo").isEqualTo("CamelCamel");
+
+        template.sendBody("direct:send", "World");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Test
+    public void testReceive() throws Exception {
+        getMockEndpoint("mock:after").expectedBodiesReceived("World");
+        getMockEndpoint("mock:after").expectedVariableReceived("bye", "Bye World");
+        getMockEndpoint("mock:after").message(0).header("echo").isNull();
+        getMockEndpoint("mock:result").expectedBodiesReceived("Bye World");
+        getMockEndpoint("mock:result").expectedVariableReceived("bye", "Bye World");
+        getMockEndpoint("mock:result").message(0).header("echo").isNull();
+
+        template.sendBody("direct:receive", "World");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Test
+    public void testSendAndReceive() throws Exception {
+        getMockEndpoint("mock:before").expectedBodiesReceived("World");
+        getMockEndpoint("mock:before").expectedVariableReceived("hello", "Camel");
+        getMockEndpoint("mock:result").expectedBodiesReceived("World");
+        getMockEndpoint("mock:result").expectedVariableReceived("bye", "Bye Camel");
+        getMockEndpoint("mock:result").message(0).header("echo").isNull();
+
+        template.sendBody("direct:sendAndReceive", "World");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() throws Exception {
+        return new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from("direct:send")
+                        .setVariable("hello", simple("Camel"))
+                        .to("mock:before")
+                        .toV("direct:foo", "hello", null)
+                        .to("mock:result");
+
+                from("direct:receive")
+                        .toV("direct:foo", null, "bye")
+                        .to("mock:after")
+                        .setBody(variable("bye"))
+                        .to("mock:result");
+
+                from("direct:sendAndReceive")
+                        .setVariable("hello", simple("Camel"))
+                        .to("mock:before")
+                        .toV("direct:foo", "hello", "bye")
+                        .to("mock:result");
+
+                from("direct:foo")
+                        .setHeader("echo", simple("${body}${body}"))
+                        .transform().simple("Bye ${body}");
+            }
+        };
+    }
+}
diff --git a/core/camel-support/src/main/java/org/apache/camel/converter/stream/CachedOutputStream.java b/core/camel-support/src/main/java/org/apache/camel/converter/stream/CachedOutputStream.java
index 865f0f2a240..49a8c9d7f0e 100644
--- a/core/camel-support/src/main/java/org/apache/camel/converter/stream/CachedOutputStream.java
+++ b/core/camel-support/src/main/java/org/apache/camel/converter/stream/CachedOutputStream.java
@@ -25,6 +25,7 @@ import org.apache.camel.Exchange;
 import org.apache.camel.StreamCache;
 import org.apache.camel.converter.stream.FileInputStreamCache.TempFileManager;
 import org.apache.camel.spi.StreamCachingStrategy;
+import org.apache.camel.util.IOHelper;
 
 /**
  * This output stream will store the content into a File if the stream context size is exceed the THRESHOLD value. The
@@ -165,9 +166,10 @@ public class CachedOutputStream extends OutputStream {
     }
 
     // This class will close the CachedOutputStream when it is closed
-    private static class WrappedInputStream extends InputStream {
+    private static class WrappedInputStream extends InputStream implements StreamCache {
         private final CachedOutputStream cachedOutputStream;
         private final InputStream inputStream;
+        private long pos;
 
         WrappedInputStream(CachedOutputStream cos, InputStream is) {
             cachedOutputStream = cos;
@@ -176,6 +178,7 @@ public class CachedOutputStream extends OutputStream {
 
         @Override
         public int read() throws IOException {
+            pos++;
             return inputStream.read();
         }
 
@@ -185,8 +188,37 @@ public class CachedOutputStream extends OutputStream {
         }
 
         @Override
-        public synchronized void reset() throws IOException {
-            inputStream.reset();
+        public synchronized void reset() {
+            try {
+                inputStream.reset();
+            } catch (IOException e) {
+                // ignore
+            }
+        }
+
+        @Override
+        public void writeTo(OutputStream os) throws IOException {
+            IOHelper.copy(this, os);
+        }
+
+        @Override
+        public StreamCache copy(Exchange exchange) throws IOException {
+            return cachedOutputStream.newStreamCache();
+        }
+
+        @Override
+        public boolean inMemory() {
+            return cachedOutputStream.inMemory;
+        }
+
+        @Override
+        public long length() {
+            return cachedOutputStream.totalLength;
+        }
+
+        @Override
+        public long position() {
+            return pos;
         }
 
         @Override
diff --git a/core/camel-support/src/main/java/org/apache/camel/support/AbstractExchange.java b/core/camel-support/src/main/java/org/apache/camel/support/AbstractExchange.java
index c1aece49c88..a0367cd5400 100644
--- a/core/camel-support/src/main/java/org/apache/camel/support/AbstractExchange.java
+++ b/core/camel-support/src/main/java/org/apache/camel/support/AbstractExchange.java
@@ -126,10 +126,9 @@ abstract class AbstractExchange implements Exchange {
 
         if (parent.hasVariables()) {
             if (this.variableRepository == null) {
-                this.variableRepository = new ExchangeVariableRepository();
+                this.variableRepository = new ExchangeVariableRepository(getContext());
             }
-            this.variableRepository.setVariables(parent.getVariables());
-
+            this.variableRepository.copyFrom(parent.variableRepository);
         }
         if (parent.hasProperties()) {
             this.properties = safeCopyProperties(parent.properties);
@@ -403,7 +402,7 @@ abstract class AbstractExchange implements Exchange {
     @Override
     public void setVariable(String name, Object value) {
         if (variableRepository == null) {
-            variableRepository = new ExchangeVariableRepository();
+            variableRepository = new ExchangeVariableRepository(getContext());
         }
         variableRepository.setVariable(name, value);
     }
@@ -424,7 +423,7 @@ abstract class AbstractExchange implements Exchange {
     public Map<String, Object> getVariables() {
         if (variableRepository == null) {
             // force creating variables
-            variableRepository = new ExchangeVariableRepository();
+            variableRepository = new ExchangeVariableRepository(getContext());
         }
         return variableRepository.getVariables();
     }
diff --git a/core/camel-support/src/main/java/org/apache/camel/support/ExchangeVariableRepository.java b/core/camel-support/src/main/java/org/apache/camel/support/AbstractVariableRepository.java
similarity index 56%
copy from core/camel-support/src/main/java/org/apache/camel/support/ExchangeVariableRepository.java
copy to core/camel-support/src/main/java/org/apache/camel/support/AbstractVariableRepository.java
index 14f74be0760..ba6655b0541 100644
--- a/core/camel-support/src/main/java/org/apache/camel/support/ExchangeVariableRepository.java
+++ b/core/camel-support/src/main/java/org/apache/camel/support/AbstractVariableRepository.java
@@ -20,23 +20,32 @@ import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.stream.Stream;
 
-import org.apache.camel.Exchange;
-import org.apache.camel.NonManagedService;
+import org.apache.camel.CamelContext;
+import org.apache.camel.CamelContextAware;
 import org.apache.camel.StreamCache;
+import org.apache.camel.StreamCacheException;
 import org.apache.camel.spi.BrowsableVariableRepository;
-import org.apache.camel.spi.VariableRepository;
+import org.apache.camel.spi.StreamCachingStrategy;
 import org.apache.camel.support.service.ServiceSupport;
 
 /**
- * {@link VariableRepository} which is local per {@link Exchange} to hold request-scoped variables.
+ * Base class for {@link org.apache.camel.spi.VariableRepository} implementations that store variables in memory.
  */
-class ExchangeVariableRepository extends ServiceSupport implements BrowsableVariableRepository, NonManagedService {
+public abstract class AbstractVariableRepository extends ServiceSupport
+        implements BrowsableVariableRepository, CamelContextAware {
 
     private final Map<String, Object> variables = new ConcurrentHashMap<>(8);
+    private CamelContext camelContext;
+    private StreamCachingStrategy strategy;
 
     @Override
-    public String getId() {
-        return "exchange";
+    public CamelContext getCamelContext() {
+        return camelContext;
+    }
+
+    @Override
+    public void setCamelContext(CamelContext camelContext) {
+        this.camelContext = camelContext;
     }
 
     @Override
@@ -51,6 +60,12 @@ class ExchangeVariableRepository extends ServiceSupport implements BrowsableVari
 
     @Override
     public void setVariable(String name, Object value) {
+        if (value != null && strategy != null) {
+            StreamCache sc = convertToStreamCache(value);
+            if (sc != null) {
+                value = sc;
+            }
+        }
         if (value != null) {
             // avoid the NullPointException
             variables.put(name, value);
@@ -91,4 +106,36 @@ class ExchangeVariableRepository extends ServiceSupport implements BrowsableVari
         }
         return variables.remove(name);
     }
+
+    @Override
+    protected void doInit() throws Exception {
+        super.doInit();
+
+        if (camelContext != null && camelContext.isStreamCaching()) {
+            strategy = camelContext.getStreamCachingStrategy();
+        }
+    }
+
+    protected StreamCache convertToStreamCache(Object body) {
+        // check if body is already cached
+        if (body == null) {
+            return null;
+        } else if (body instanceof StreamCache) {
+            StreamCache sc = (StreamCache) body;
+            // reset so the cache is ready to be used before processing
+            sc.reset();
+            return sc;
+        }
+        return tryStreamCache(body);
+    }
+
+    protected StreamCache tryStreamCache(Object body) {
+        try {
+            // cache the body and if we could do that replace it as the new body
+            return strategy.cache(body);
+        } catch (Exception e) {
+            throw new StreamCacheException(body, e);
+        }
+    }
+
 }
diff --git a/core/camel-support/src/main/java/org/apache/camel/support/ExchangeHelper.java b/core/camel-support/src/main/java/org/apache/camel/support/ExchangeHelper.java
index d55f4e1ce57..7dd9f30b93e 100644
--- a/core/camel-support/src/main/java/org/apache/camel/support/ExchangeHelper.java
+++ b/core/camel-support/src/main/java/org/apache/camel/support/ExchangeHelper.java
@@ -47,6 +47,7 @@ import org.apache.camel.NoTypeConversionAvailableException;
 import org.apache.camel.Route;
 import org.apache.camel.RuntimeCamelException;
 import org.apache.camel.TypeConversionException;
+import org.apache.camel.VariableAware;
 import org.apache.camel.WrappedFile;
 import org.apache.camel.spi.NormalizedEndpointUri;
 import org.apache.camel.spi.UnitOfWork;
@@ -1085,20 +1086,59 @@ public final class ExchangeHelper {
      * @param value    the value of the variable
      */
     public static void setVariable(Exchange exchange, String name, Object value) {
+        VariableRepository repo = null;
         String id = StringHelper.before(name, ":");
+        // header and exchange is reserved
+        if ("header".equals(id) || "exchange".equals(id)) {
+            id = null;
+        }
         if (id != null) {
             VariableRepositoryFactory factory
                     = exchange.getContext().getCamelContextExtension().getContextPlugin(VariableRepositoryFactory.class);
-            VariableRepository repo = factory.getVariableRepository(id);
+            repo = factory.getVariableRepository(id);
             if (repo != null) {
                 name = StringHelper.after(name, ":");
-                repo.setVariable(name, value);
             } else {
-                exchange.setException(
-                        new IllegalArgumentException("VariableRepository with id: " + id + " does not exist"));
+                throw new IllegalArgumentException("VariableRepository with id: " + id + " does not exist");
             }
-        } else {
-            exchange.setVariable(name, value);
+        }
+        VariableAware va = repo != null ? repo : exchange;
+        va.setVariable(name, value);
+    }
+
+    /**
+     * Sets the variable from the given message body and headers
+     *
+     * @param exchange the exchange
+     * @param name     the variable name. Can be prefixed with repo-id:name to lookup the variable from a specific
+     *                 repository. If no repo-id is provided, then the variable is set on the exchange
+     * @param message  the message with the body and headers as source values
+     */
+    public static void setVariableFromMessageBodyAndHeaders(Exchange exchange, String name, Message message) {
+        VariableRepository repo = null;
+        String id = StringHelper.before(name, ":");
+        // header and exchange is reserved
+        if ("header".equals(id) || "exchange".equals(id)) {
+            id = null;
+        }
+        if (id != null) {
+            VariableRepositoryFactory factory
+                    = exchange.getContext().getCamelContextExtension().getContextPlugin(VariableRepositoryFactory.class);
+            repo = factory.getVariableRepository(id);
+            if (repo == null) {
+                throw new IllegalArgumentException("VariableRepository with id: " + id + " does not exist");
+            }
+            name = StringHelper.after(name, ":");
+        }
+        VariableAware va = repo != null ? repo : exchange;
+
+        // set body and headers as variables
+        Object body = message.getBody();
+        va.setVariable(name, body);
+        for (Map.Entry<String, Object> header : message.getHeaders().entrySet()) {
+            String key = "header:" + name + "." + header.getKey();
+            Object value = header.getValue();
+            va.setVariable(key, value);
         }
     }
 
@@ -1111,22 +1151,24 @@ public final class ExchangeHelper {
      * @return          the variable
      */
     public static Object getVariable(Exchange exchange, String name) {
-        Object answer;
+        VariableRepository repo = null;
         String id = StringHelper.before(name, ":");
+        // header and exchange is reserved
+        if ("header".equals(id) || "exchange".equals(id)) {
+            id = null;
+        }
         if (id != null) {
             VariableRepositoryFactory factory
                     = exchange.getContext().getCamelContextExtension().getContextPlugin(VariableRepositoryFactory.class);
-            VariableRepository repo = factory.getVariableRepository(id);
+            repo = factory.getVariableRepository(id);
             if (repo != null) {
                 name = StringHelper.after(name, ":");
-                answer = repo.getVariable(name);
             } else {
                 throw new IllegalArgumentException("VariableRepository with id: " + id + " does not exist");
             }
-        } else {
-            answer = exchange.getVariable(name);
         }
-        return answer;
+        VariableAware va = repo != null ? repo : exchange;
+        return va.getVariable(name);
     }
 
     /**
diff --git a/core/camel-support/src/main/java/org/apache/camel/support/ExchangeVariableRepository.java b/core/camel-support/src/main/java/org/apache/camel/support/ExchangeVariableRepository.java
index 14f74be0760..0e144ce3627 100644
--- a/core/camel-support/src/main/java/org/apache/camel/support/ExchangeVariableRepository.java
+++ b/core/camel-support/src/main/java/org/apache/camel/support/ExchangeVariableRepository.java
@@ -18,77 +18,95 @@ package org.apache.camel.support;
 
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
-import java.util.stream.Stream;
 
+import org.apache.camel.CamelContext;
 import org.apache.camel.Exchange;
-import org.apache.camel.NonManagedService;
-import org.apache.camel.StreamCache;
-import org.apache.camel.spi.BrowsableVariableRepository;
 import org.apache.camel.spi.VariableRepository;
-import org.apache.camel.support.service.ServiceSupport;
+import org.apache.camel.support.service.ServiceHelper;
+import org.apache.camel.util.CaseInsensitiveMap;
+import org.apache.camel.util.StringHelper;
 
 /**
  * {@link VariableRepository} which is local per {@link Exchange} to hold request-scoped variables.
  */
-class ExchangeVariableRepository extends ServiceSupport implements BrowsableVariableRepository, NonManagedService {
+final class ExchangeVariableRepository extends AbstractVariableRepository {
 
-    private final Map<String, Object> variables = new ConcurrentHashMap<>(8);
+    private final Map<String, Object> headers = new ConcurrentHashMap<>(8);
 
-    @Override
-    public String getId() {
-        return "exchange";
+    public ExchangeVariableRepository(CamelContext camelContext) {
+        setCamelContext(camelContext);
+        // ensure its started
+        ServiceHelper.startService(this);
+    }
+
+    void copyFrom(ExchangeVariableRepository source) {
+        setVariables(source.getVariables());
+        this.headers.putAll(source.headers);
     }
 
     @Override
     public Object getVariable(String name) {
-        Object answer = variables.get(name);
-        if (answer instanceof StreamCache sc) {
-            // reset so the cache is ready to be used as a variable
-            sc.reset();
+        String id = StringHelper.before(name, ":");
+        if ("header".equals(id)) {
+            String prefix = StringHelper.after(name, ":");
+            if (prefix == null || prefix.isBlank()) {
+                throw new IllegalArgumentException("Variable " + name + " must have header key");
+            }
+            if (!prefix.contains(".")) {
+                prefix = prefix + ".";
+                // we want all headers for a given variable
+                Map<String, Object> map = new CaseInsensitiveMap();
+                for (Map.Entry<String, Object> entry : headers.entrySet()) {
+                    String key = entry.getKey();
+                    if (key.startsWith(prefix)) {
+                        key = StringHelper.after(key, prefix);
+                        map.put(key, entry.getValue());
+                    }
+                }
+                return map;
+            } else {
+                return headers.get(prefix);
+            }
         }
-        return answer;
+        return super.getVariable(name);
     }
 
     @Override
     public void setVariable(String name, Object value) {
-        if (value != null) {
-            // avoid the NullPointException
-            variables.put(name, value);
+        String id = StringHelper.before(name, ":");
+        if ("header".equals(id)) {
+            name = StringHelper.after(name, ":");
+            if (value != null) {
+                // avoid the NullPointException
+                headers.put(name, value);
+            } else {
+                // if the value is null, we just remove the key from the map
+                headers.remove(name);
+            }
         } else {
-            // if the value is null, we just remove the key from the map
-            variables.remove(name);
+            super.setVariable(name, value);
         }
     }
 
-    public boolean hasVariables() {
-        return !variables.isEmpty();
-    }
-
-    public int size() {
-        return variables.size();
-    }
-
-    public Stream<String> names() {
-        return variables.keySet().stream();
-    }
-
-    public Map<String, Object> getVariables() {
-        return variables;
-    }
-
-    public void setVariables(Map<String, Object> map) {
-        variables.putAll(map);
+    @Override
+    public Object removeVariable(String name) {
+        String id = StringHelper.before(name, ":");
+        if ("header".equals(id)) {
+            name = StringHelper.after(name, ":");
+            return headers.remove(name);
+        }
+        return super.removeVariable(name);
     }
 
+    @Override
     public void clear() {
-        variables.clear();
+        super.clear();
+        headers.clear();
     }
 
     @Override
-    public Object removeVariable(String name) {
-        if (!hasVariables()) {
-            return null;
-        }
-        return variables.remove(name);
+    public String getId() {
+        return "exchange";
     }
+
 }
diff --git a/core/camel-support/src/main/java/org/apache/camel/support/GlobalVariableRepository.java b/core/camel-support/src/main/java/org/apache/camel/support/GlobalVariableRepository.java
index 09f1e7d7270..cf7abcd1fd8 100644
--- a/core/camel-support/src/main/java/org/apache/camel/support/GlobalVariableRepository.java
+++ b/core/camel-support/src/main/java/org/apache/camel/support/GlobalVariableRepository.java
@@ -17,75 +17,17 @@
 package org.apache.camel.support;
 
 import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.stream.Stream;
 
-import org.apache.camel.StreamCache;
-import org.apache.camel.spi.BrowsableVariableRepository;
 import org.apache.camel.spi.VariableRepository;
-import org.apache.camel.support.service.ServiceSupport;
 
 /**
  * Global {@link VariableRepository} which stores variables in-memory in a {@link Map}.
  */
-public final class GlobalVariableRepository extends ServiceSupport implements BrowsableVariableRepository {
-
-    private final ConcurrentMap<String, Object> variables = new ConcurrentHashMap<>();
+public final class GlobalVariableRepository extends AbstractVariableRepository {
 
     @Override
     public String getId() {
         return "global";
     }
 
-    @Override
-    public Object getVariable(String name) {
-        Object answer = variables.get(name);
-        if (answer instanceof StreamCache sc) {
-            // reset so the cache is ready to be used as a variable
-            sc.reset();
-        }
-        return answer;
-    }
-
-    @Override
-    public void setVariable(String name, Object value) {
-        if (value != null) {
-            // avoid the NullPointException
-            variables.put(name, value);
-        } else {
-            // if the value is null, we just remove the key from the map
-            variables.remove(name);
-        }
-    }
-
-    @Override
-    public Object removeVariable(String name) {
-        return variables.remove(name);
-    }
-
-    @Override
-    public boolean hasVariables() {
-        return !variables.isEmpty();
-    }
-
-    @Override
-    public int size() {
-        return variables.size();
-    }
-
-    @Override
-    public Stream<String> names() {
-        return variables.keySet().stream();
-    }
-
-    @Override
-    public Map<String, Object> getVariables() {
-        return variables;
-    }
-
-    @Override
-    public void clear() {
-        variables.clear();
-    }
 }
diff --git a/core/camel-support/src/main/java/org/apache/camel/support/HeaderVariableRepository.java b/core/camel-support/src/main/java/org/apache/camel/support/HeaderVariableRepository.java
new file mode 100644
index 00000000000..21a691db9b7
--- /dev/null
+++ b/core/camel-support/src/main/java/org/apache/camel/support/HeaderVariableRepository.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.support;
+
+import java.util.Map;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Exchange;
+import org.apache.camel.StreamCache;
+import org.apache.camel.spi.VariableRepository;
+import org.apache.camel.support.service.ServiceHelper;
+import org.apache.camel.util.CaseInsensitiveMap;
+import org.apache.camel.util.StringHelper;
+
+/**
+ * {@link VariableRepository} which is local per {@link Exchange} to hold request-scoped variables for message headers.
+ */
+final class HeaderVariableRepository extends AbstractVariableRepository {
+
+    public HeaderVariableRepository(CamelContext camelContext) {
+        setCamelContext(camelContext);
+        // ensure its started
+        ServiceHelper.startService(this);
+    }
+
+    @Override
+    public String getId() {
+        return "header";
+    }
+
+    @Override
+    public Object getVariable(String name) {
+        Object answer = super.getVariable(name);
+        if (answer == null && !name.contains(".")) {
+            String prefix = name + ".";
+            // we want all headers for a given variable
+            Map<String, Object> map = new CaseInsensitiveMap();
+            for (Map.Entry<String, Object> entry : getVariables().entrySet()) {
+                String key = entry.getKey();
+                if (key.startsWith(prefix)) {
+                    key = StringHelper.after(key, prefix);
+                    map.put(key, entry.getValue());
+                }
+            }
+            return map;
+        }
+        if (answer instanceof StreamCache sc) {
+            // reset so the cache is ready to be used as a variable
+            sc.reset();
+        }
+        return answer;
+    }
+
+}
diff --git a/core/camel-support/src/main/java/org/apache/camel/support/SingleInputLanguageSupport.java b/core/camel-support/src/main/java/org/apache/camel/support/SingleInputLanguageSupport.java
index 86703cea8f5..eabffb5a5a7 100644
--- a/core/camel-support/src/main/java/org/apache/camel/support/SingleInputLanguageSupport.java
+++ b/core/camel-support/src/main/java/org/apache/camel/support/SingleInputLanguageSupport.java
@@ -23,16 +23,20 @@ import org.apache.camel.spi.Language;
  */
 public abstract class SingleInputLanguageSupport extends LanguageSupport {
 
-    /**
-     * Name of header to use as input, instead of the message body
-     */
+    private String variableName;
     private String headerName;
+    private String propertyName;
+
+    public String getVariableName() {
+        return variableName;
+    }
+
     /**
-     * Name of property to use as input, instead of the message body.
-     * <p>
-     * It has a lower precedent than the name of header if both are set.
+     * Name of variable to use as input, instead of the message body
      */
-    private String propertyName;
+    public void setVariableName(String variableName) {
+        this.variableName = variableName;
+    }
 
     public String getHeaderName() {
         return headerName;
diff --git a/core/camel-support/src/main/java/org/apache/camel/support/SingleInputTypedLanguageSupport.java b/core/camel-support/src/main/java/org/apache/camel/support/SingleInputTypedLanguageSupport.java
index 3965287b485..9e1f98a8805 100644
--- a/core/camel-support/src/main/java/org/apache/camel/support/SingleInputTypedLanguageSupport.java
+++ b/core/camel-support/src/main/java/org/apache/camel/support/SingleInputTypedLanguageSupport.java
@@ -25,16 +25,22 @@ import org.apache.camel.support.builder.ExpressionBuilder;
  */
 public abstract class SingleInputTypedLanguageSupport extends TypedLanguageSupport {
 
-    /**
-     * Name of header to use as input, instead of the message body
-     */
+    private String variableName;
     private String headerName;
+    private String propertyName;
+
+    public String getVariableName() {
+        return variableName;
+    }
+
     /**
-     * Name of property to use as input, instead of the message body.
-     * <p>
-     * It has a lower precedent than the name of header if both are set.
+     * Name of variable to use as input, instead of the message body
+     * </p>
+     * It has as higher precedent if other are set.
      */
-    private String propertyName;
+    public void setVariableName(String variableName) {
+        this.variableName = variableName;
+    }
 
     public String getHeaderName() {
         return headerName;
@@ -63,9 +69,10 @@ public abstract class SingleInputTypedLanguageSupport extends TypedLanguageSuppo
     @Override
     public Expression createExpression(String expression, Object[] properties) {
         Class<?> type = property(Class.class, properties, 0, getResultType());
-        String header = property(String.class, properties, 1, getHeaderName());
-        String property = property(String.class, properties, 2, getPropertyName());
-        Expression source = ExpressionBuilder.singleInputExpression(header, property);
+        String variable = property(String.class, properties, 1, getVariableName());
+        String header = property(String.class, properties, 2, getHeaderName());
+        String property = property(String.class, properties, 3, getPropertyName());
+        Expression source = ExpressionBuilder.singleInputExpression(variable, header, property);
         if (type == null || type == Object.class) {
             return createExpression(source, expression, properties);
         }
diff --git a/core/camel-support/src/main/java/org/apache/camel/support/builder/ExpressionBuilder.java b/core/camel-support/src/main/java/org/apache/camel/support/builder/ExpressionBuilder.java
index 12271bde384..0f3bb477721 100644
--- a/core/camel-support/src/main/java/org/apache/camel/support/builder/ExpressionBuilder.java
+++ b/core/camel-support/src/main/java/org/apache/camel/support/builder/ExpressionBuilder.java
@@ -53,6 +53,7 @@ import org.apache.camel.support.GroupIterator;
 import org.apache.camel.support.GroupTokenIterator;
 import org.apache.camel.support.LanguageHelper;
 import org.apache.camel.support.LanguageSupport;
+import org.apache.camel.support.SingleInputTypedLanguageSupport;
 import org.apache.camel.util.InetAddressUtil;
 import org.apache.camel.util.ObjectHelper;
 import org.apache.camel.util.StringHelper;
@@ -957,6 +958,70 @@ public class ExpressionBuilder {
         };
     }
 
+    /**
+     * Returns an expression for evaluating the expression/predicate using the given language
+     *
+     * @param  expression the expression or predicate
+     * @param  input      input to use instead of message body
+     * @return            an expression object which will evaluate the expression/predicate using the given language
+     */
+    public static Expression singleInputLanguageExpression(final String language, final String expression, final String input) {
+        return new ExpressionAdapter() {
+            private Expression expr;
+            private Predicate pred;
+
+            @Override
+            public Object evaluate(Exchange exchange) {
+                return expr.evaluate(exchange, Object.class);
+            }
+
+            @Override
+            public boolean matches(Exchange exchange) {
+                return pred.matches(exchange);
+            }
+
+            @Override
+            public void init(CamelContext context) {
+                super.init(context);
+                Language lan = context.resolveLanguage(language);
+                if (lan != null) {
+                    if (input != null && lan instanceof SingleInputTypedLanguageSupport sil) {
+                        String prefix = StringHelper.before(input, ":");
+                        String source = StringHelper.after(input, ":");
+                        if (prefix != null) {
+                            prefix = prefix.trim();
+                        }
+                        if (source != null) {
+                            source = source.trim();
+                        }
+                        if ("header".equals(prefix)) {
+                            sil.setHeaderName(source);
+                        } else if ("property".equals(prefix) || "exchangeProperty".equals(prefix)) {
+                            sil.setPropertyName(source);
+                        } else if ("variable".equals(prefix)) {
+                            sil.setVariableName(source);
+                        } else {
+                            throw new IllegalArgumentException(
+                                    "Invalid input source for language. Should either be header:key, exchangeProperty:key, or variable:key, was: "
+                                                               + input);
+                        }
+                    }
+                    pred = lan.createPredicate(expression);
+                    pred.init(context);
+                    expr = lan.createExpression(expression);
+                    expr.init(context);
+                } else {
+                    throw new NoSuchLanguageException(language);
+                }
+            }
+
+            @Override
+            public String toString() {
+                return "language[" + language + ":" + expression + "]";
+            }
+        };
+    }
+
     /**
      * Returns the expression for the exchanges inbound message body
      */
@@ -1142,15 +1207,19 @@ public class ExpressionBuilder {
     }
 
     /**
+     * @param  variableName the name of the variable from which the input data must be extracted if not empty.
      * @param  headerName   the name of the header from which the input data must be extracted if not empty.
      * @param  propertyName the name of the property from which the input data must be extracted if not empty and
      *                      {@code headerName} is empty.
-     * @return              a header expression if {@code headerName} is not empty, otherwise a property expression if
-     *                      {@code propertyName} is not empty or finally a body expression.
+     * @return              a variable expression if {@code variableName} is not empty, a header expression if
+     *                      {@code headerName} is not empty, otherwise a property expression if {@code propertyName} is
+     *                      not empty or finally a body expression.
      */
-    public static Expression singleInputExpression(String headerName, String propertyName) {
+    public static Expression singleInputExpression(String variableName, String headerName, String propertyName) {
         final Expression exp;
-        if (ObjectHelper.isNotEmpty(headerName)) {
+        if (ObjectHelper.isNotEmpty(variableName)) {
+            exp = variableExpression(variableName);
+        } else if (ObjectHelper.isNotEmpty(headerName)) {
             exp = headerExpression(headerName);
         } else if (ObjectHelper.isNotEmpty(propertyName)) {
             exp = exchangePropertyExpression(propertyName);
@@ -1163,7 +1232,6 @@ public class ExpressionBuilder {
     /**
      * Returns the expression for the current thread id
      */
-
     public static Expression threadIdExpression() {
         return new ExpressionAdapter() {
             @Override
@@ -2306,6 +2374,32 @@ public class ExpressionBuilder {
         };
     }
 
+    /**
+     * Returns the expression as pretty formatted string
+     */
+    public static Expression prettyExpression(final Expression expression) {
+        return new ExpressionAdapter() {
+            @Override
+            public Object evaluate(Exchange exchange) {
+                String body = expression.evaluate(exchange, String.class);
+                if (body == null) {
+                    return null;
+                } else if (body.startsWith("{") && body.endsWith("}") || body.startsWith("[") && body.endsWith("]")) {
+                    return Jsoner.prettyPrint(body); //json
+                } else if (body.startsWith("<") && body.endsWith(">")) {
+                    return ExpressionBuilder.prettyXml(body); //xml
+                }
+
+                return body;
+            }
+
+            @Override
+            public String toString() {
+                return "pretty(" + expression + ")";
+            }
+        };
+    }
+
     /**
      * Returns the expression for the message body as pretty formatted string
      */
diff --git a/core/camel-support/src/main/java/org/apache/camel/support/processor/MarshalProcessor.java b/core/camel-support/src/main/java/org/apache/camel/support/processor/MarshalProcessor.java
index f1a299106ef..107180f753c 100644
--- a/core/camel-support/src/main/java/org/apache/camel/support/processor/MarshalProcessor.java
+++ b/core/camel-support/src/main/java/org/apache/camel/support/processor/MarshalProcessor.java
@@ -59,7 +59,6 @@ public class MarshalProcessor extends AsyncProcessorSupport implements Traceable
         final Object originalBody = in.getBody();
         Object body = originalBody;
         if (variableSend != null) {
-            // it may be a global variable
             body = ExchangeHelper.getVariable(exchange, variableSend);
         }
 
diff --git a/core/camel-support/src/main/java/org/apache/camel/support/processor/UnmarshalProcessor.java b/core/camel-support/src/main/java/org/apache/camel/support/processor/UnmarshalProcessor.java
index dbfb9e7206c..8b93cabd473 100644
--- a/core/camel-support/src/main/java/org/apache/camel/support/processor/UnmarshalProcessor.java
+++ b/core/camel-support/src/main/java/org/apache/camel/support/processor/UnmarshalProcessor.java
@@ -68,7 +68,6 @@ public class UnmarshalProcessor extends AsyncProcessorSupport implements Traceab
             final Object originalBody = in.getBody();
             Object body = originalBody;
             if (variableSend != null) {
-                // it may be a global variable
                 body = ExchangeHelper.getVariable(exchange, variableSend);
             }
             final Message out;
diff --git a/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java b/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java
index 60132d4ae6f..4ae07023836 100644
--- a/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java
+++ b/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java
@@ -2895,6 +2895,7 @@ public class ModelParser extends BaseParser {
             switch (key) {
                 case "headerName": def.setHeaderName(val); break;
                 case "propertyName": def.setPropertyName(val); break;
+                case "variableName": def.setVariableName(val); break;
                 default: return typedExpressionDefinitionAttributeHandler().accept(def, key, val);
             }
             return true;
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 6399d61082a..d80ec03e463 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
@@ -4161,6 +4161,7 @@ public class ModelWriter extends BaseWriter {
             throws IOException {
         doWriteTypedExpressionDefinitionAttributes(def);
         doWriteAttribute("headerName", def.getHeaderName());
+        doWriteAttribute("variableName", def.getVariableName());
         doWriteAttribute("propertyName", def.getPropertyName());
     }
     protected void doWriteSingleInputTypedExpressionDefinition(
diff --git a/core/camel-yaml-io/src/generated/java/org/apache/camel/yaml/out/ModelWriter.java b/core/camel-yaml-io/src/generated/java/org/apache/camel/yaml/out/ModelWriter.java
index fdfcdce5f8b..0a61565954b 100644
--- a/core/camel-yaml-io/src/generated/java/org/apache/camel/yaml/out/ModelWriter.java
+++ b/core/camel-yaml-io/src/generated/java/org/apache/camel/yaml/out/ModelWriter.java
@@ -4161,6 +4161,7 @@ public class ModelWriter extends BaseWriter {
             throws IOException {
         doWriteTypedExpressionDefinitionAttributes(def);
         doWriteAttribute("headerName", def.getHeaderName());
+        doWriteAttribute("variableName", def.getVariableName());
         doWriteAttribute("propertyName", def.getPropertyName());
     }
     protected void doWriteSingleInputTypedExpressionDefinition(
diff --git a/docs/user-manual/modules/ROOT/pages/variables.adoc b/docs/user-manual/modules/ROOT/pages/variables.adoc
index 31bcc331faa..0e84edd115e 100644
--- a/docs/user-manual/modules/ROOT/pages/variables.adoc
+++ b/docs/user-manual/modules/ROOT/pages/variables.adoc
@@ -30,6 +30,8 @@ For example, you can build a custom repository that stores the variables in a da
 
 Each repository must have its own unique id. However, it's also possible to replace the default `global` repository with another.
 
+IMPORTANT: The id `exchange` and `header` is reserved by Camel internally and should not be used as id for custom repositories.
+
 == Getting and setting variables from Java API
 
 To get a local variable from the current exchange, you can do this via Java API:
@@ -173,7 +175,7 @@ camel.variable.user-template = resource:file:/etc/user.json
 == Using Variables with EIPs
 
 The following commonly used EIPs for sending and receiving, and transforming messages, have
-first class support for using variables with the message body:
+special support for choosing to use variables over the current `Exchange`:
 
 - from
 - to
@@ -185,19 +187,50 @@ first class support for using variables with the message body:
 - marshal
 
 The intention is to make it more convenient and easy to _gather data_ from other systems without any ceremony to keep
-existing data by using techniques such as storing the data temporary using headers or exchange properties,
+existing data by using techniques such as storing the data temporary using headers, exchange properties,
 or with the xref:components:eips:claimCheck-eip.adoc[Claim Check] EIP.
 
 === Important concept when using variables and EIPs
 
-It is **important** to understand that the variables only use the message **body** and does not have support for anything else such
-as message headers. This is on purpose to keep it simpler and only work with the message body as the user data. If you have need
-to use variables with both message body and headers, then you can use `setVariable` and `getVariable`.
+It is **important** to understand that the variables focuses the use of the message **body** only.
+This is on purpose to keep it simpler and primary work with the message body as the user data.
+
+The following table summarises what the EIP supports with variables:
+
+|===
+|*EIP* | *VariableSend* | *VariableReceive*
+| From | | yes
+| To | yes | yes
+| ToD | yes | yes
+| Enrich | yes | yes
+| PollEnrich | | yes
+| WireTap | yes |
+| Unmarshal | yes | yes
+| Marshal | yes | yes
+|===
 
 The EIPs listed above have support for using variables when sending and receiving data. This is done by using the `variableSend` and `variableReceive` options
-to specify the name of the variable. When the EIP uses variables then the _data_ itself (i.e. message body) is only what is
-different from 'standard' Camel.
+to specify the name of the variable.
+
+The EIPs works in two modes where *variableSend* and *variableReceive* is a little bit different, so pay attention to the following table:
+
+|===
+| *VariableSend*       | *VariableReceive*
+| *Sending Headers:* Message   | *Received Headers:* Variable
+| *Sending Body:* Variable     | *Received Body:* Variable
+|===
 
+The *VariableSend* is intended for sending as regular Camel where the sending headers are from the current `Message` and the body is
+from the variable. In other words it's only the message body that is taken from the variable instead of the current `Message` body.
+
+The *VariableReceive* works in a different mode. The idea is that all the received data is stored as variables. This means the current `Message`
+is not changed at all. The received body is stored in the variable, and the received headers (transport headers etc.) are stored as read-only
+headers as variables as well. The names of the variable is `header:variableName.headerName`. For example if the variable is `myVar` and the header is `Content-Type`
+then the header is stored as a variable with the full name `header:myVar.Content-Type`.
+
+=== Example using VariableReceive
+
+When the EIP is using *VariableReceive*, then the `Message` on the `Exchange` is not in use, but the body and headers will be from the variable.
 For example given the following `Message` containing:
 
 [source,properties]
@@ -207,7 +240,7 @@ header.bar=456
 body=Hello World
 ----
 
-And a remote service is called via the route below, and this service returns a new header and body:
+And a remote service is called via the route below, and this service returns a new header (`level`) and body:
 
 [tabs]
 ====
@@ -251,7 +284,8 @@ header.level=gold
 body=Bye World
 ----
 
-However, if you use a variable (_myVar_) as the _sink_ to store the returned data from calling the remote service as shown:
+However, if you use *VariableReceive=myVar* to store the returned data from calling the remote service, into a variable, then
+the dynamics changes as follows:
 
 [tabs]
 ====
@@ -287,27 +321,31 @@ from:
 ----
 ====
 
-Then the `Message` body is not changed, but everything else is changed as without using variables:
+Then the `Message` on the current `Exchange` is not changed:
 
 [source,properties]
 ----
 header.foo=123
 header.bar=456
-header.level=gold
 body=Hello World
 ----
 
-And the variable contains the data:
+And the variable contains all the data received from the remote HTTP service separated into two variables:
 
 [source,properties]
 ----
 myVar=Bye World
+header:myVar.level=gold
 ----
 
-=== Using variable to store a copy of the incoming message body
+IMPORTANT: Notice the headers are stored with the syntax `header:variable.key`. In the example above the variable name is `myVar`,
+and the header key is `level`, which gives: `header:myVar.level`.
 
-You can configure the `from` to store a copy of the message body into a variable. This makes it easy to have quick access
-to the original incoming message body via the variable.
+
+=== Using variable to store incoming message body
+
+You can configure the `from` to store the message body into a variable, instead of the `Message`. This makes it easy to have quick access
+to the original incoming message body via the variable. Notice that the body on the `Message` will be `null`.
 
 The following example from a unit test shows how to do this. Notice how Java DSL uses `fromV` to make it possible to specify
 the name of the variable. In XML and YAML DSL you specify this using the `variableReceive` parameter.
@@ -357,213 +395,6 @@ from:
 ----
 ====
 
-=== Using variables when sending and receiving messages to an endpoint
-
-You can configure the `to` to use variables for any of the following (or both) when sending and receiving:
-
-- variableSend - name of variable that contains the message body to send instead of the current message body on the `Exchange`.
-- variableReceive - name of variable that should store the returned message payload (will not change the message body on the `Exchange`).
-
-For example, you can use the `variableSend` to tell Camel to use the variable as the message body when sending to an endpoint.
-If the `variableReceive` is also configured, then the reply message will be stored in the variable instead of the `Exchange` message body.
-
-IMPORTANT: This is only the message body. Message headers keep acting as usual.
-
-In the following example, we use a variable named `hello` as the message body when sending to the `direct:foo` endpoint:
-
-[tabs]
-====
-Java::
-+
-[source,java]
-----
-from("direct:send")
-    .setVariable("hello", simple("Camel"))
-    .to("mock:before")
-    .toV("direct:foo", "hello", null)
-    .to("mock:result");
-
-from("direct:foo")
-    .transform().simple("Bye ${body}");
-----
-XML::
-+
-[source,xml]
-----
-<route>
-  <from uri="direct:send"/>
-  <setVariable name="hello">
-    <simple>Camel</simple>
-  </setVariable>
-  <to uri="mock:before"/>
-  <to uri="direct:foo" variableSend="hello"/>
-  <to uri="mock:result"/>
-</route>
-<route>
-  <from uri="direct:foo"/>
-  <transform>
-    <simple>Bye ${body}</simple>
-  </transform>
-</route>
-----
-YAML::
-+
-[source,yaml]
-----
-- route:
-    from:
-      uri: direct:send
-      steps:
-        - setVariable:
-            name: hello
-            simple:
-              expression: Camel
-        - to:
-            uri: mock:before
-        - to:
-            uri: direct:foo
-            variableSend: hello
-        - to:
-            uri: mock:result
-- route:
-    from:
-      uri: direct:foo
-      steps:
-        - transform:
-            simple:
-              expression: "Bye ${body}"
-----
-====
-
-If you only want to store the result in a variable instead of the current `Exchange` message body, then you should use `variableReceive`
-as shown in the following:
-
-[tabs]
-====
-Java::
-+
-[source,java]
-----
-from("direct:receive")
-    .toV("direct:foo", null, "bye")
-    .to("mock:after")
-    .setBody(simple("${variable:bye}"))
-    .to("mock:result");
+NOTE: In the examples above the transform `Bye $\{body}` will result as `Bye ` because the `Message` has no message body, as the incoming
+message body is stored in the variable `myKey` instead.
 
-from("direct:foo")
-    .transform().simple("Bye ${body}");
-----
-XML::
-+
-[source,xml]
-----
-<route>
-  <from uri="direct:receive"/>
-  <to uri="direct:foo" variableReceive="bye"/>
-  <to uri="mock:after"/>
-  <setBody>
-    <simple>${variable:bye}</simple>
-  </setBody>
-  <to uri="mock:result"/>
-</route>
-<route>
-  <from uri="direct:foo"/>
-  <transform>
-    <simple>Bye ${body}</simple>
-  </transform>
-</route>
-----
-YAML::
-+
-[source,yaml]
-----
-- route:
-    from:
-      uri: direct:receive
-      steps:
-        - to:
-            uri: direct:foo
-            variableReceive: bye
-        - to:
-            uri: mock:after
-        - setBody:
-            variable: bye
-        - to:
-            uri: mock:result
-- route:
-    from:
-      uri: direct:foo
-      steps:
-        - transform:
-            simple:
-              expression: "Bye ${body}"
-----
-====
-
-And you can also use both of them together which means you are using variables for both what to send, and to store the result in a variable.
-This means the current `Exchange` message body is not in use at all.
-
-[tabs]
-====
-Java::
-+
-[source,java]
-----
-from("direct:sendAndReceive")
-    .setVariable("hello", simple("Camel"))
-    .to("mock:before")
-    .toV("direct:foo", "hello", "bye")
-    .to("mock:result");
-
-from("direct:foo")
-    .transform().simple("Bye ${body}");
-----
-XML::
-+
-[source,xml]
-----
-<route>
-  <from uri="direct:sendAndReceive"/>
-  <setVariable name="hello">
-    <simple>Camel</simple>
-  </setVariable>
-  <to uri="mock:before"/>
-  <to uri="direct:foo" variableSend="hello" variableReceive="bye"/>
-  <to uri="mock:result"/>
-</route>
-<route>
-  <from uri="direct:foo"/>
-  <transform>
-    <simple>Bye ${body}</simple>
-  </transform>
-</route>
-----
-YAML::
-+
-[source,yaml]
-----
-- route:
-    from:
-      uri: direct:sendAndReceive
-      steps:
-        - setVariable:
-            name: hello
-            simple:
-              expression: Camel
-        - to:
-            uri: mock:before
-        - to:
-            uri: direct:foo
-            variableReceive: bye
-            variableSend: hello
-        - to:
-            uri: mock:result
-- route:
-    from:
-      uri: direct:foo
-      steps:
-        - transform:
-            simple:
-              expression: "Bye ${body}"
-----
-====
diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializers.java b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializers.java
index 2e7c0d1ff1e..5e10764e353 100644
--- a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializers.java
+++ b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializers.java
@@ -6871,7 +6871,8 @@ public final class ModelDeserializers extends YamlDeserializerSupport {
                     @YamlProperty(name = "id", type = "string", description = "Sets the id of this node", displayName = "Id"),
                     @YamlProperty(name = "propertyName", type = "string", description = "Name of property to use as input, instead of the message body. It has a lower precedent than the headerName if both are set.", displayName = "Property Name"),
                     @YamlProperty(name = "resultType", type = "string", description = "Sets the class of the result type (type from output)", displayName = "Result Type"),
-                    @YamlProperty(name = "trim", type = "boolean", description = "Whether to trim the value to remove leading and trailing whitespaces and line breaks", displayName = "Trim")
+                    @YamlProperty(name = "trim", type = "boolean", description = "Whether to trim the value to remove leading and trailing whitespaces and line breaks", displayName = "Trim"),
+                    @YamlProperty(name = "variableName", type = "string", description = "Name of variable to use as input, instead of the message body It has as higher precedent if other are set.", displayName = "Variable Name")
             }
     )
     public static class Hl7TerserExpressionDeserializer extends YamlDeserializerBase<Hl7TerserExpression> {
@@ -6924,6 +6925,11 @@ public final class ModelDeserializers extends YamlDeserializerSupport {
                     target.setTrim(val);
                     break;
                 }
+                case "variableName": {
+                    String val = asText(node);
+                    target.setVariableName(val);
+                    break;
+                }
                 default: {
                     ExpressionDefinition ed = target.getExpressionType();
                     if (ed != null) {
@@ -7939,7 +7945,8 @@ public final class ModelDeserializers extends YamlDeserializerSupport {
                     @YamlProperty(name = "id", type = "string", description = "Sets the id of this node", displayName = "Id"),
                     @YamlProperty(name = "propertyName", type = "string", description = "Name of property to use as input, instead of the message body. It has a lower precedent than the headerName if both are set.", displayName = "Property Name"),
                     @YamlProperty(name = "resultType", type = "string", description = "Sets the class of the result type (type from output)", displayName = "Result Type"),
-                    @YamlProperty(name = "trim", type = "boolean", description = "Whether to trim the value to remove leading and trailing whitespaces and line breaks", displayName = "Trim")
+                    @YamlProperty(name = "trim", type = "boolean", description = "Whether to trim the value to remove leading and trailing whitespaces and line breaks", displayName = "Trim"),
+                    @YamlProperty(name = "variableName", type = "string", description = "Name of variable to use as input, instead of the message body It has as higher precedent if other are set.", displayName = "Variable Name")
             }
     )
     public static class JqExpressionDeserializer extends YamlDeserializerBase<JqExpression> {
@@ -7992,6 +7999,11 @@ public final class ModelDeserializers extends YamlDeserializerSupport {
                     target.setTrim(val);
                     break;
                 }
+                case "variableName": {
+                    String val = asText(node);
+                    target.setVariableName(val);
+                    break;
+                }
                 default: {
                     ExpressionDefinition ed = target.getExpressionType();
                     if (ed != null) {
@@ -8253,6 +8265,7 @@ public final class ModelDeserializers extends YamlDeserializerSupport {
                     @YamlProperty(name = "suppressExceptions", type = "boolean", description = "Whether to suppress exceptions such as PathNotFoundException.", displayName = "Suppress Exceptions"),
                     @YamlProperty(name = "trim", type = "boolean", description = "Whether to trim the value to remove leading and trailing whitespaces and line breaks", displayName = "Trim"),
                     @YamlProperty(name = "unpackArray", type = "boolean", description = "Whether to unpack a single element json-array into an object.", displayName = "Unpack Array"),
+                    @YamlProperty(name = "variableName", type = "string", description = "Name of variable to use as input, instead of the message body It has as higher precedent if other are set.", displayName = "Variable Name"),
                     @YamlProperty(name = "writeAsString", type = "boolean", description = "Whether to write the output of each row/element as a JSON String value instead of a Map/POJO value.", displayName = "Write As String")
             }
     )
@@ -8331,6 +8344,11 @@ public final class ModelDeserializers extends YamlDeserializerSupport {
                     target.setUnpackArray(val);
                     break;
                 }
+                case "variableName": {
+                    String val = asText(node);
+                    target.setVariableName(val);
+                    break;
+                }
                 case "writeAsString": {
                     String val = asText(node);
                     target.setWriteAsString(val);
diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl.json b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl.json
index 6b37183070a..fdddfca3136 100644
--- a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl.json
+++ b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl.json
@@ -13063,6 +13063,11 @@
               "type" : "boolean",
               "title" : "Trim",
               "description" : "Whether to trim the value to remove leading and trailing whitespaces and line breaks"
+            },
+            "variableName" : {
+              "type" : "string",
+              "title" : "Variable Name",
+              "description" : "Name of variable to use as input, instead of the message body It has as higher precedent if other are set."
             }
           }
         } ],
@@ -13226,6 +13231,11 @@
               "type" : "boolean",
               "title" : "Trim",
               "description" : "Whether to trim the value to remove leading and trailing whitespaces and line breaks"
+            },
+            "variableName" : {
+              "type" : "string",
+              "title" : "Variable Name",
+              "description" : "Name of variable to use as input, instead of the message body It has as higher precedent if other are set."
             }
           }
         } ],
@@ -13296,6 +13306,11 @@
               "title" : "Unpack Array",
               "description" : "Whether to unpack a single element json-array into an object."
             },
+            "variableName" : {
+              "type" : "string",
+              "title" : "Variable Name",
+              "description" : "Name of variable to use as input, instead of the message body It has as higher precedent if other are set."
+            },
             "writeAsString" : {
               "type" : "boolean",
               "title" : "Write As String",
diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/FromVariableTest.groovy b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/FromVariableTest.groovy
index 7316a4f874a..5112cb1a9ca 100644
--- a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/FromVariableTest.groovy
+++ b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/FromVariableTest.groovy
@@ -18,6 +18,7 @@ package org.apache.camel.dsl.yaml
 
 import org.apache.camel.component.mock.MockEndpoint
 import org.apache.camel.dsl.yaml.support.YamlTestSupport
+import org.junit.jupiter.api.Assertions
 
 class FromVariableTest extends YamlTestSupport {
 
@@ -28,6 +29,12 @@ class FromVariableTest extends YamlTestSupport {
                     uri: "direct:start"
                     variableReceive: "myKey"
                     steps:
+                      - setHeader:
+                          name: foo
+                          constant: "456"
+                      - setHeader:
+                          name: bar
+                          constant: "Murphy"
                       - transform:
                           simple: "Bye ${body}"
                       - to: "mock:foo"
@@ -37,7 +44,13 @@ class FromVariableTest extends YamlTestSupport {
             '''
 
             withMock('mock:foo') {
-                expectedBodiesReceived 'Bye World'
+                expectedBodiesReceived 'Bye '
+                whenAnyExchangeReceived { e -> {
+                    Map m = e.getVariable("header:myKey", Map.class)
+                    Assertions.assertNotNull(m)
+                    Assertions.assertEquals(1, m.size())
+                    Assertions.assertEquals(123, m.get("foo"))
+                }}
             }
             withMock('mock:result') {
                 expectedBodiesReceived 'World'
@@ -47,7 +60,7 @@ class FromVariableTest extends YamlTestSupport {
             context.start()
 
             withTemplate {
-                to('direct:start').withBody('World').send()
+                to('direct:start').withBody('World').withHeader("foo", 123).send()
             }
 
         then: