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 064d6da1d6b8 CAMEL-22894: extract `body`, `header` and `variable` 
Simple functions into dedicated factories (#23382)
064d6da1d6b8 is described below

commit 064d6da1d6b85cba734c2d9c8fb64ba50b6e082f
Author: Adriano Machado <[email protected]>
AuthorDate: Wed May 20 13:35:27 2026 -0400

    CAMEL-22894: extract `body`, `header` and `variable` Simple functions into 
dedicated factories (#23382)
    
    Extract three built-in Simple language functions (body, header, variable) 
from
    `SimpleFunctionExpression` into dedicated `SimpleLanguageFunctionFactory` 
implementations
    (`BodyFunctionFactory`, `HeaderFunctionFactory`, 
`VariableFunctionFactory`), following
    the same pattern already established for `random`, `skip`, `collate` and 
`join`.
    
    Shared utilities (`ifStartsWithReturnRemainder`, `splitOgnl`, 
`ognlCodeMethods`, `appendClass`, `parseInHeader`, `parseVariable`) are 
consolidated in `SimpleFunctionHelper` so the new factories can import them 
without introducing a circular dependency on the `ast` package.
    
    The two legacy dispatch methods (`tryCreate`, `tryCreateCode`) in 
`SimpleFunctionDispatcher`
    are preserved as `@Deprecated(forRemoval=true)` with a Javadoc note that 
they were
    migrated as-is and are candidates for removal once all functions are 
extracted (CAMEL-22894).
    
    Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
    
    rh-pre-commit.version: 2.3.2
    rh-pre-commit.check-secrets: ENABLED
---
 .../camel/spi/SimpleLanguageFunctionFactory.java   |   2 +-
 .../language/simple/SimpleFunctionDispatcher.java  |  20 +-
 .../language/simple/SimpleFunctionHelper.java      | 137 ++++
 .../simple/ast/SimpleFunctionExpression.java       | 720 +--------------------
 .../simple/functions/BodyFunctionFactory.java      | 293 +++++++++
 .../simple/functions/CollateFunctionFactory.java   |   2 +-
 .../simple/functions/HeaderFunctionFactory.java    | 218 +++++++
 .../simple/functions/JoinFunctionFactory.java      |   2 +-
 .../simple/functions/RandomFunctionFactory.java    |   2 +-
 .../simple/functions/SkipFunctionFactory.java      |   2 +-
 .../simple/functions/VariableFunctionFactory.java  | 162 +++++
 .../simple/functions/BodyFunctionFactoryTest.java  | 125 ++++
 .../functions/HeaderFunctionFactoryTest.java       |  96 +++
 .../functions/VariableFunctionFactoryTest.java     |  86 +++
 14 files changed, 1148 insertions(+), 719 deletions(-)

diff --git 
a/core/camel-api/src/main/java/org/apache/camel/spi/SimpleLanguageFunctionFactory.java
 
b/core/camel-api/src/main/java/org/apache/camel/spi/SimpleLanguageFunctionFactory.java
index 9667ccb3409f..1a8b763ed160 100644
--- 
a/core/camel-api/src/main/java/org/apache/camel/spi/SimpleLanguageFunctionFactory.java
+++ 
b/core/camel-api/src/main/java/org/apache/camel/spi/SimpleLanguageFunctionFactory.java
@@ -53,7 +53,7 @@ public interface SimpleLanguageFunctionFactory {
      * @param      function     the function
      * @param      index        index of the function in the literal input
      * @return                  the source code or <tt>null</tt> if not 
supported by this factory.
-     * @deprecated              will be removed in 5.0 along with csimple
+     * @deprecated              will be removed along with csimple
      */
     @Nullable
     @Deprecated(since = "4.21")
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleFunctionDispatcher.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleFunctionDispatcher.java
index 65057ecb8124..0f41262c81dc 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleFunctionDispatcher.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleFunctionDispatcher.java
@@ -21,14 +21,17 @@ import java.util.function.Predicate;
 
 import org.apache.camel.CamelContext;
 import org.apache.camel.Expression;
+import org.apache.camel.language.simple.functions.BodyFunctionFactory;
 import org.apache.camel.language.simple.functions.CollateFunctionFactory;
+import org.apache.camel.language.simple.functions.HeaderFunctionFactory;
 import org.apache.camel.language.simple.functions.JoinFunctionFactory;
 import org.apache.camel.language.simple.functions.RandomFunctionFactory;
 import org.apache.camel.language.simple.functions.SkipFunctionFactory;
+import org.apache.camel.language.simple.functions.VariableFunctionFactory;
 import org.apache.camel.spi.SimpleLanguageFunctionFactory;
 import org.apache.camel.support.ResolverHelper;
 
-import static 
org.apache.camel.language.simple.ast.SimpleFunctionExpression.ifStartsWithReturnRemainder;
+import static 
org.apache.camel.language.simple.SimpleFunctionHelper.ifStartsWithReturnRemainder;
 
 /**
  * Dispatches Simple/CSimple function lookup to built-in function factories 
and to {@link SimpleLanguageFunctionFactory}
@@ -45,9 +48,12 @@ public final class SimpleFunctionDispatcher {
     /**
      * Built-in factories shipped by camel-core-languages itself. Iterated 
before {@link #EXPRESSION_ENTRIES}, matching
      * the original priority of these functions inside {@code 
SimpleFunctionExpression}. Each factory returns
-     * {@code null} for inputs it does not recognise, so no gating predicate 
is needed.
+     * {@code null} for inputs it does not recognize, so no gating predicate 
is needed.
      */
     private static final List<SimpleLanguageFunctionFactory> BUILT_INS = 
List.of(
+            new BodyFunctionFactory(),
+            new HeaderFunctionFactory(),
+            new VariableFunctionFactory(),
             new RandomFunctionFactory(),
             new SkipFunctionFactory(),
             new CollateFunctionFactory(),
@@ -69,6 +75,11 @@ public final class SimpleFunctionDispatcher {
     private SimpleFunctionDispatcher() {
     }
 
+    /**
+     * Migrated as-is from main; not currently called. Candidate for removal 
once all Simple functions are extracted
+     * into dedicated {@link SimpleLanguageFunctionFactory} implementations 
(CAMEL-22894).
+     */
+    @Deprecated(forRemoval = true)
     public static Expression tryCreate(CamelContext camelContext, String 
function, int index) {
         Expression answer = tryCreateBuiltIn(camelContext, function, index);
         if (answer != null) {
@@ -101,6 +112,11 @@ public final class SimpleFunctionDispatcher {
         return null;
     }
 
+    /**
+     * Migrated as-is from main; not currently called. Candidate for removal 
once all Simple functions are extracted
+     * into dedicated {@link SimpleLanguageFunctionFactory} implementations 
(CAMEL-22894).
+     */
+    @Deprecated(forRemoval = true)
     public static String tryCreateCode(CamelContext camelContext, String 
function, int index) {
         String code = tryCreateCodeBuiltIn(camelContext, function, index);
         if (code != null) {
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleFunctionHelper.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleFunctionHelper.java
index d6ae1b132dce..6e11f57f7c7a 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleFunctionHelper.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleFunctionHelper.java
@@ -19,11 +19,148 @@ package org.apache.camel.language.simple;
 import java.util.ArrayList;
 import java.util.List;
 
+import org.apache.camel.util.OgnlHelper;
+import org.apache.camel.util.StringHelper;
+
 public final class SimpleFunctionHelper {
 
     private SimpleFunctionHelper() {
     }
 
+    public static String ifStartsWithReturnRemainder(String prefix, String 
text) {
+        if (text.startsWith(prefix)) {
+            String remainder = text.substring(prefix.length());
+            if (!remainder.isEmpty()) {
+                return remainder;
+            }
+        }
+        return null;
+    }
+
+    public static List<String> splitOgnl(String remainder) {
+        List<String> methods = OgnlHelper.splitOgnl(remainder);
+        List<String> answer = new ArrayList<>();
+        for (String m : methods) {
+            if (m.startsWith(".")) {
+                m = m.substring(1);
+            }
+            boolean index = m.startsWith("[") && m.endsWith("]");
+            if (index) {
+                String last = answer.isEmpty() ? null : 
answer.get(answer.size() - 1);
+                boolean lastIndex = last != null && last.startsWith("[") && 
last.endsWith("]");
+                if (lastIndex) {
+                    answer.set(answer.size() - 1, last + m);
+                } else {
+                    answer.add(m);
+                }
+            } else {
+                answer.add(m);
+            }
+        }
+        return answer;
+    }
+
+    public static String ognlCodeMethods(String remainder, String type) {
+        StringBuilder sb = new StringBuilder(256);
+
+        if (remainder != null) {
+            List<String> methods = splitOgnl(remainder);
+            for (String m : methods) {
+                if (m.startsWith("(")) {
+                    sb.append(m);
+                    continue;
+                }
+
+                String index = StringHelper.betweenOuterPair(m, '[', ']');
+                if (index != null) {
+                    m = StringHelper.before(m, "[");
+                }
+
+                if (m != null && m.equals("length")) {
+                    if (type != null && type.contains("[]")) {
+                        sb.append(".length");
+                        continue;
+                    }
+                }
+
+                if (m != null) {
+                    m = OgnlHelper.methodAsDoubleQuotes(m);
+                }
+
+                if (m != null && !m.isEmpty()) {
+                    sb.append(".");
+                    char ch = m.charAt(m.length() - 1);
+                    if (Character.isAlphabetic(ch)) {
+                        if (!m.startsWith("get")) {
+                            sb.append("get");
+                            sb.append(Character.toUpperCase(m.charAt(0)));
+                            sb.append(m.substring(1));
+                        } else {
+                            sb.append(m);
+                        }
+                        sb.append("()");
+                    } else {
+                        sb.append(m);
+                    }
+                }
+
+                if (index != null) {
+                    sb.append(".get(");
+                    try {
+                        long lon = Long.parseLong(index);
+                        sb.append(lon);
+                        if (lon > Integer.MAX_VALUE) {
+                            sb.append("l");
+                        }
+                    } catch (Exception e) {
+                        index = 
StringHelper.removeLeadingAndEndingQuotes(index);
+                        sb.append("\"");
+                        sb.append(index);
+                        sb.append("\"");
+                    }
+                    sb.append(")");
+                }
+            }
+        }
+
+        if (!sb.isEmpty()) {
+            return sb.toString();
+        } else {
+            return remainder;
+        }
+    }
+
+    public static String appendClass(String type) {
+        type = StringHelper.removeQuotes(type);
+        if (!type.endsWith(".class")) {
+            type = type + ".class";
+        }
+        return type;
+    }
+
+    public static String parseInHeader(String function) {
+        String remainder;
+        remainder = ifStartsWithReturnRemainder("in.headers", function);
+        if (remainder == null) {
+            remainder = ifStartsWithReturnRemainder("in.header", function);
+        }
+        if (remainder == null) {
+            remainder = ifStartsWithReturnRemainder("headers", function);
+        }
+        if (remainder == null) {
+            remainder = ifStartsWithReturnRemainder("header", function);
+        }
+        return remainder;
+    }
+
+    public static String parseVariable(String function) {
+        String remainder = ifStartsWithReturnRemainder("variables", function);
+        if (remainder == null) {
+            remainder = ifStartsWithReturnRemainder("variable", function);
+        }
+        return remainder;
+    }
+
     public static String[] codeSplitSafe(String input, char separator, boolean 
trim, boolean keepQuotes) {
         if (input == null) {
             return null;
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 843691c5a6b3..2e5d7ba037b3 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
@@ -30,6 +30,7 @@ import org.apache.camel.RuntimeCamelException;
 import org.apache.camel.language.simple.BaseSimpleParser;
 import org.apache.camel.language.simple.SimpleExpressionBuilder;
 import org.apache.camel.language.simple.SimpleFunctionDispatcher;
+import org.apache.camel.language.simple.SimpleFunctionHelper;
 import org.apache.camel.language.simple.SimplePredicateParser;
 import org.apache.camel.language.simple.types.SimpleParserException;
 import org.apache.camel.language.simple.types.SimpleToken;
@@ -119,16 +120,6 @@ public class SimpleFunctionExpression extends 
LiteralExpression {
             return answer;
         }
 
-        // body and headers first
-        answer = createSimpleExpressionBodyOrHeader(function, strict);
-        if (answer != null) {
-            return answer;
-        }
-        // variables
-        answer = createSimpleExpressionVariables(function, strict);
-        if (answer != null) {
-            return answer;
-        }
         // custom functions
         answer = createSimpleCustomFunction(camelContext, function, strict);
         if (answer != null) {
@@ -457,181 +448,6 @@ public class SimpleFunctionExpression extends 
LiteralExpression {
         return null;
     }
 
-    private Expression createSimpleExpressionBodyOrHeader(String function, 
boolean strict) {
-        // bodyAs
-        String remainder = ifStartsWithReturnRemainder("bodyAs(", function);
-        if (remainder != null) {
-            String type = StringHelper.before(remainder, ")");
-            if (type == null) {
-                throw new SimpleParserException("Valid syntax: ${bodyAs(type)} 
was: " + function, token.getIndex());
-            }
-            type = StringHelper.removeQuotes(type);
-            remainder = StringHelper.after(remainder, ")");
-            if (ObjectHelper.isNotEmpty(remainder)) {
-                boolean invalid = 
OgnlHelper.isInvalidValidOgnlExpression(remainder);
-                if (invalid) {
-                    throw new SimpleParserException("Valid syntax: 
${bodyAs(type).OGNL} was: " + function, token.getIndex());
-                }
-                return SimpleExpressionBuilder.bodyOgnlExpression(type, 
remainder);
-            } else {
-                return ExpressionBuilder.bodyExpression(type);
-            }
-        }
-        // mandatoryBodyAs
-        remainder = ifStartsWithReturnRemainder("mandatoryBodyAs(", function);
-        if (remainder != null) {
-            String type = StringHelper.before(remainder, ")");
-            if (type == null) {
-                throw new SimpleParserException("Valid syntax: 
${mandatoryBodyAs(type)} was: " + function, token.getIndex());
-            }
-            type = StringHelper.removeQuotes(type);
-            remainder = StringHelper.after(remainder, ")");
-            if (ObjectHelper.isNotEmpty(remainder)) {
-                boolean invalid = 
OgnlHelper.isInvalidValidOgnlExpression(remainder);
-                if (invalid) {
-                    throw new SimpleParserException(
-                            "Valid syntax: ${mandatoryBodyAs(type).OGNL} was: 
" + function, token.getIndex());
-                }
-                return 
SimpleExpressionBuilder.mandatoryBodyOgnlExpression(type, remainder);
-            } else {
-                return SimpleExpressionBuilder.mandatoryBodyExpression(type);
-            }
-        }
-
-        // body OGNL
-        remainder = ifStartsWithReturnRemainder("body", function);
-        if (remainder == null) {
-            remainder = ifStartsWithReturnRemainder("in.body", function);
-        }
-        if (remainder != null) {
-            // OGNL must start with a ".", "?" or "[".
-            boolean ognlStart = remainder.startsWith(".") || 
remainder.startsWith("?") || remainder.startsWith("[");
-            boolean invalid = !ognlStart || 
OgnlHelper.isInvalidValidOgnlExpression(remainder);
-            if (invalid) {
-                throw new SimpleParserException("Valid syntax: ${body.OGNL} 
was: " + function, token.getIndex());
-            }
-            return SimpleExpressionBuilder.bodyOgnlExpression(remainder);
-        }
-
-        // headerAs
-        remainder = ifStartsWithReturnRemainder("headerAs(", function);
-        if (remainder != null) {
-            String keyAndType = StringHelper.before(remainder, ")");
-            if (keyAndType == null) {
-                throw new SimpleParserException("Valid syntax: ${headerAs(key, 
type)} was: " + function, token.getIndex());
-            }
-
-            String key = StringHelper.before(keyAndType, ",");
-            String type = StringHelper.after(keyAndType, ",");
-            remainder = StringHelper.after(remainder, ")");
-            if (ObjectHelper.isEmpty(key) || ObjectHelper.isEmpty(type) || 
ObjectHelper.isNotEmpty(remainder)) {
-                throw new SimpleParserException("Valid syntax: ${headerAs(key, 
type)} was: " + function, token.getIndex());
-            }
-            key = StringHelper.removeQuotes(key);
-            type = StringHelper.removeQuotes(type);
-            return ExpressionBuilder.headerExpression(key, type);
-        }
-
-        // headers function
-        if ("in.headers".equals(function) || "headers".equals(function)) {
-            return ExpressionBuilder.headersExpression();
-        } else if ("headers.size".equals(function) || 
"headers.size()".equals(function)
-                || "headers.length".equals(function) || 
"headers.length()".equals(function)) {
-            return ExpressionBuilder.headersSizeExpression();
-        }
-
-        // in header function
-        remainder = parseInHeader(function);
-        if (remainder != null) {
-            // remove leading character (dot, colon or ?)
-            if (remainder.startsWith(".") || remainder.startsWith(":") || 
remainder.startsWith("?")) {
-                remainder = remainder.substring(1);
-            }
-            // remove starting and ending brackets
-            if (remainder.startsWith("[") && remainder.endsWith("]")) {
-                remainder = remainder.substring(1, remainder.length() - 1);
-            }
-            // remove quotes from key
-            String key = StringHelper.removeLeadingAndEndingQuotes(remainder);
-
-            // validate syntax
-            boolean invalid = OgnlHelper.isInvalidValidOgnlExpression(key);
-            if (invalid) {
-                throw new SimpleParserException("Valid syntax: 
${header.name[key]} was: " + function, token.getIndex());
-            }
-
-            if (OgnlHelper.isValidOgnlExpression(key)) {
-                // ognl based header
-                return SimpleExpressionBuilder.headersOgnlExpression(key);
-            } else {
-                // regular header
-                return ExpressionBuilder.headerExpression(key);
-            }
-        }
-
-        return null;
-    }
-
-    private Expression createSimpleExpressionVariables(String function, 
boolean strict) {
-        // variableAs
-        String remainder = ifStartsWithReturnRemainder("variableAs(", 
function);
-        if (remainder != null) {
-            String keyAndType = StringHelper.before(remainder, ")");
-            if (keyAndType == null) {
-                throw new SimpleParserException("Valid syntax: 
${variableAs(key, type)} was: " + function, token.getIndex());
-            }
-
-            String key = StringHelper.before(keyAndType, ",");
-            String type = StringHelper.after(keyAndType, ",");
-            remainder = StringHelper.after(remainder, ")");
-            if (ObjectHelper.isEmpty(key) || ObjectHelper.isEmpty(type) || 
ObjectHelper.isNotEmpty(remainder)) {
-                throw new SimpleParserException("Valid syntax: 
${variableAs(key, type)} was: " + function, token.getIndex());
-            }
-            key = StringHelper.removeQuotes(key);
-            type = StringHelper.removeQuotes(type);
-            return ExpressionBuilder.variableExpression(key, type);
-        }
-
-        // variables function
-        if ("variables".equals(function)) {
-            return ExpressionBuilder.variablesExpression();
-        } else if ("variables.size".equals(function) || 
"variables.size()".equals(function)
-                || "variables.length".equals(function) || 
"variables.length()".equals(function)) {
-            return ExpressionBuilder.variablesSizeExpression();
-        }
-
-        // variable function
-        remainder = parseVariable(function);
-        if (remainder != null) {
-            // remove leading character (dot, colon or ?)
-            if (remainder.startsWith(".") || remainder.startsWith(":") || 
remainder.startsWith("?")) {
-                remainder = remainder.substring(1);
-            }
-            // remove starting and ending brackets
-            if (remainder.startsWith("[") && remainder.endsWith("]")) {
-                remainder = remainder.substring(1, remainder.length() - 1);
-            }
-            // remove quotes from key
-            String key = StringHelper.removeLeadingAndEndingQuotes(remainder);
-
-            // validate syntax
-            boolean invalid = OgnlHelper.isInvalidValidOgnlExpression(key);
-            if (invalid) {
-                throw new SimpleParserException("Valid syntax: 
${variable.name[key]} was: " + function, token.getIndex());
-            }
-
-            if (OgnlHelper.isValidOgnlExpression(key)) {
-                // ognl based variable
-                return SimpleExpressionBuilder.variablesOgnlExpression(key);
-            } else {
-                // regular variable
-                return ExpressionBuilder.variableExpression(key);
-            }
-        }
-
-        return null;
-    }
-
     private Expression createSimpleCustomFunction(CamelContext camelContext, 
String function, boolean strict) {
         String remainder = ifStartsWithReturnRemainder("function(", function);
         if (remainder != null) {
@@ -758,21 +574,7 @@ public class SimpleFunctionExpression extends 
LiteralExpression {
     }
 
     private Expression createSimpleExpressionDirectly(CamelContext 
camelContext, String expression) {
-        if (ObjectHelper.isEqualToAny(expression, "body", "in.body")) {
-            return ExpressionBuilder.bodyExpression();
-        } else if (ObjectHelper.equal(expression, "bodyType")) {
-            return ExpressionBuilder.bodyTypeExpression();
-        } else if (ObjectHelper.equal(expression, "prettyBody")) {
-            return ExpressionBuilder.prettyBodyExpression();
-        } else if (ObjectHelper.equal(expression, "toJsonBody")) {
-            return 
ExpressionBuilder.toJsonExpression(ExpressionBuilder.bodyExpression(), false);
-        } else if (ObjectHelper.equal(expression, "toPrettyJsonBody")) {
-            return 
ExpressionBuilder.toJsonExpression(ExpressionBuilder.bodyExpression(), true);
-        } else if (ObjectHelper.equal(expression, "bodyOneLine")) {
-            return ExpressionBuilder.bodyOneLine();
-        } else if (ObjectHelper.equal(expression, "originalBody")) {
-            return ExpressionBuilder.originalBodyExpression();
-        } else if (ObjectHelper.equal(expression, "id")) {
+        if (ObjectHelper.equal(expression, "id")) {
             return ExpressionBuilder.messageIdExpression();
         } else if (ObjectHelper.equal(expression, "messageTimestamp")) {
             return ExpressionBuilder.messageTimestampExpression();
@@ -1814,14 +1616,9 @@ public class SimpleFunctionExpression extends 
LiteralExpression {
         return null;
     }
 
+    @Deprecated(since = "4.21")
     public static String ifStartsWithReturnRemainder(String prefix, String 
text) {
-        if (text.startsWith(prefix)) {
-            String remainder = text.substring(prefix.length());
-            if (!remainder.isEmpty()) {
-                return remainder;
-            }
-        }
-        return null;
+        return SimpleFunctionHelper.ifStartsWithReturnRemainder(prefix, text);
     }
 
     @Override
@@ -1838,24 +1635,11 @@ public class SimpleFunctionExpression extends 
LiteralExpression {
             return answer;
         }
 
-        // body, headers and exchange property first
-        answer = createCodeBody(function);
-        if (answer != null) {
-            return answer;
-        }
-        answer = createCodeHeader(function);
-        if (answer != null) {
-            return answer;
-        }
+        // exchange property first
         answer = createCodeExchangeProperty(function);
         if (answer != null) {
             return answer;
         }
-        answer = createCodeVariables(function);
-        if (answer != null) {
-            return answer;
-        }
-
         // camelContext OGNL
         String remainder = ifStartsWithReturnRemainder("camelContext", 
function);
         if (remainder != null) {
@@ -2063,19 +1847,7 @@ public class SimpleFunctionExpression extends 
LiteralExpression {
     }
 
     public String createCodeDirectly(String expression) throws 
SimpleParserException {
-        if (ObjectHelper.isEqualToAny(expression, "body", "in.body")) {
-            return "body";
-        } else if (ObjectHelper.equal(expression, "bodyType")) {
-            return "bodyType(exchange)";
-        } else if (ObjectHelper.equal(expression, "prettyBody")) {
-            return "prettyBody(exchange)";
-        } else if (ObjectHelper.equal(expression, "toJsonBody")) {
-            return "toJsonBody(exchange, false)";
-        } else if (ObjectHelper.equal(expression, "toPrettyJsonBody")) {
-            return "toJsonBody(exchange, true)";
-        } else if (ObjectHelper.equal(expression, "bodyOneLine")) {
-            return "bodyOneLine(exchange)";
-        } else if (ObjectHelper.equal(expression, "id")) {
+        if (ObjectHelper.equal(expression, "id")) {
             return "message.getMessageId()";
         } else if (ObjectHelper.equal(expression, "messageTimestamp")) {
             return "message.getMessageTimestamp()";
@@ -2112,408 +1884,6 @@ public class SimpleFunctionExpression extends 
LiteralExpression {
         return null;
     }
 
-    private String createCodeBody(final String function) {
-        // bodyAsIndex
-        String remainder = ifStartsWithReturnRemainder("bodyAsIndex(", 
function);
-        if (remainder != null) {
-            String typeAndIndex = StringHelper.before(remainder, ")");
-            if (typeAndIndex == null) {
-                throw new SimpleParserException(
-                        "Valid syntax: ${bodyAsIndex(type, index).OGNL} was: " 
+ function, token.getIndex());
-            }
-
-            String type = StringHelper.before(typeAndIndex, ",");
-            String index = StringHelper.after(typeAndIndex, ",");
-            remainder = StringHelper.after(remainder, ")");
-            if (ObjectHelper.isEmpty(type) || ObjectHelper.isEmpty(index)) {
-                throw new SimpleParserException(
-                        "Valid syntax: ${bodyAsIndex(type, index).OGNL} was: " 
+ function, token.getIndex());
-            }
-            type = type.trim();
-            type = appendClass(type);
-            type = type.replace('$', '.');
-            index = StringHelper.removeQuotes(index);
-            index = index.trim();
-            if (ObjectHelper.isNotEmpty(remainder)) {
-                boolean invalid = 
OgnlHelper.isInvalidValidOgnlExpression(remainder);
-                if (invalid) {
-                    throw new SimpleParserException(
-                            "Valid syntax: ${bodyAsIndex(type, index).OGNL} 
was: " + function, token.getIndex());
-                }
-                return "bodyAsIndex(message, " + type + ", \"" + index + "\")" 
+ ognlCodeMethods(remainder, type);
-            } else {
-                return "bodyAsIndex(message, " + type + ", \"" + index + "\")";
-            }
-        }
-
-        // bodyAs
-        remainder = ifStartsWithReturnRemainder("bodyAs(", function);
-        if (remainder != null) {
-            String type = StringHelper.before(remainder, ")");
-            if (type == null) {
-                throw new SimpleParserException("Valid syntax: ${bodyAs(type)} 
was: " + function, token.getIndex());
-            }
-            type = appendClass(type);
-            type = type.replace('$', '.');
-            type = type.trim();
-            remainder = StringHelper.after(remainder, ")");
-            if (ObjectHelper.isNotEmpty(remainder)) {
-                boolean invalid = 
OgnlHelper.isInvalidValidOgnlExpression(remainder);
-                if (invalid) {
-                    throw new SimpleParserException("Valid syntax: 
${bodyAs(type).OGNL} was: " + function, token.getIndex());
-                }
-                if (remainder.startsWith("[")) {
-                    // is there any index, then we should use bodyAsIndex 
function instead
-                    // (use splitOgnl which assembles multiple indexes into a 
single part)
-                    List<String> parts = splitOgnl(remainder);
-                    if (!parts.isEmpty()) {
-                        String func = "bodyAsIndex(" + type + ", \"" + 
parts.remove(0) + "\")";
-                        String last = String.join("", parts);
-                        if (!last.isEmpty()) {
-                            func += "." + last;
-                        }
-                        return createCodeBody(func);
-                    }
-                }
-                return "bodyAs(message, " + type + ")" + 
ognlCodeMethods(remainder, type);
-            } else {
-                return "bodyAs(message, " + type + ")";
-            }
-        }
-
-        // mandatoryBodyAsIndex
-        remainder = ifStartsWithReturnRemainder("mandatoryBodyAsIndex(", 
function);
-        if (remainder != null) {
-            String typeAndIndex = StringHelper.before(remainder, ")");
-            if (typeAndIndex == null) {
-                throw new SimpleParserException(
-                        "Valid syntax: ${mandatoryBodyAsIndex(type, 
index).OGNL} was: " + function, token.getIndex());
-            }
-
-            String type = StringHelper.before(typeAndIndex, ",");
-            String index = StringHelper.after(typeAndIndex, ",");
-            remainder = StringHelper.after(remainder, ")");
-            if (ObjectHelper.isEmpty(type) || ObjectHelper.isEmpty(index)) {
-                throw new SimpleParserException(
-                        "Valid syntax: ${mandatoryBodyAsIndex(type, 
index).OGNL} was: " + function, token.getIndex());
-            }
-            type = type.trim();
-            type = appendClass(type);
-            type = type.replace('$', '.');
-            index = StringHelper.removeQuotes(index);
-            index = index.trim();
-            if (ObjectHelper.isNotEmpty(remainder)) {
-                boolean invalid = 
OgnlHelper.isInvalidValidOgnlExpression(remainder);
-                if (invalid) {
-                    throw new SimpleParserException(
-                            "Valid syntax: ${mandatoryBodyAsIndex(type, 
index).OGNL} was: " + function, token.getIndex());
-                }
-                return "mandatoryBodyAsIndex(message, " + type + ", \"" + 
index + "\")" + ognlCodeMethods(remainder, type);
-            } else {
-                return "mandatoryBodyAsIndex(message, " + type + ", \"" + 
index + "\")";
-            }
-        }
-
-        // mandatoryBodyAs
-        remainder = ifStartsWithReturnRemainder("mandatoryBodyAs(", function);
-        if (remainder != null) {
-            String type = StringHelper.before(remainder, ")");
-            if (type == null) {
-                throw new SimpleParserException("Valid syntax: 
${mandatoryBodyAs(type)} was: " + function, token.getIndex());
-            }
-            type = appendClass(type);
-            type = type.replace('$', '.');
-            type = type.trim();
-            remainder = StringHelper.after(remainder, ")");
-            if (ObjectHelper.isNotEmpty(remainder)) {
-                boolean invalid = 
OgnlHelper.isInvalidValidOgnlExpression(remainder);
-                if (invalid) {
-                    throw new SimpleParserException(
-                            "Valid syntax: ${mandatoryBodyAs(type).OGNL} was: 
" + function, token.getIndex());
-                }
-                if (remainder.startsWith("[")) {
-                    // is there any index, then we should use 
mandatoryBodyAsIndex function instead
-                    // (use splitOgnl which assembles multiple indexes into a 
single part)
-                    List<String> parts = splitOgnl(remainder);
-                    if (!parts.isEmpty()) {
-                        String func = "mandatoryBodyAsIndex(" + type + ", \"" 
+ parts.remove(0) + "\")";
-                        String last = String.join("", parts);
-                        if (!last.isEmpty()) {
-                            func += "." + last;
-                        }
-                        return createCodeBody(func);
-                    }
-                }
-                return "mandatoryBodyAs(message, " + type + ")" + 
ognlCodeMethods(remainder, type);
-            } else {
-                return "mandatoryBodyAs(message, " + type + ")";
-            }
-        }
-
-        // body OGNL
-        remainder = ifStartsWithReturnRemainder("body", function);
-        if (remainder == null) {
-            remainder = ifStartsWithReturnRemainder("in.body", function);
-        }
-        if (remainder != null) {
-            // OGNL must start with a . ? or [
-            boolean ognlStart = remainder.startsWith(".") || 
remainder.startsWith("?") || remainder.startsWith("[");
-            boolean invalid = !ognlStart || 
OgnlHelper.isInvalidValidOgnlExpression(remainder);
-            if (invalid) {
-                throw new SimpleParserException("Valid syntax: ${body.OGNL} 
was: " + function, token.getIndex());
-            }
-            if (remainder.startsWith("[")) {
-                // is there any index, then we should use bodyAsIndex function 
instead
-                // (use splitOgnl which assembles multiple indexes into a 
single part)
-                List<String> parts = splitOgnl(remainder);
-                if (!parts.isEmpty()) {
-                    String func = "bodyAsIndex(Object.class, \"" + 
parts.remove(0) + "\")";
-                    String last = String.join("", parts);
-                    if (!last.isEmpty()) {
-                        func += "." + last;
-                    }
-                    return createCodeBody(func);
-                }
-            }
-            return "body" + ognlCodeMethods(remainder, null);
-        }
-
-        return null;
-    }
-
-    private String createCodeHeader(final String function) {
-        // headerAsIndex
-        String remainder = ifStartsWithReturnRemainder("headerAsIndex(", 
function);
-        if (remainder != null) {
-            String keyTypeAndIndex = StringHelper.before(remainder, ")");
-            if (keyTypeAndIndex == null) {
-                throw new SimpleParserException(
-                        "Valid syntax: ${headerAsIndex(key, type, index)} was: 
" + function, token.getIndex());
-            }
-            String[] parts = keyTypeAndIndex.split(",");
-            if (parts.length != 3) {
-                throw new SimpleParserException(
-                        "Valid syntax: ${headerAsIndex(key, type, index)} was: 
" + function, token.getIndex());
-            }
-            String key = parts[0];
-            String type = parts[1];
-            String index = parts[2];
-            if (ObjectHelper.isEmpty(key) || ObjectHelper.isEmpty(type) || 
ObjectHelper.isEmpty(index)) {
-                throw new SimpleParserException(
-                        "Valid syntax: ${headerAsIndex(key, type, index)} was: 
" + function, token.getIndex());
-            }
-            key = StringHelper.removeQuotes(key);
-            key = key.trim();
-            type = appendClass(type);
-            type = type.replace('$', '.');
-            type = type.trim();
-            index = StringHelper.removeQuotes(index);
-            index = index.trim();
-            remainder = StringHelper.after(remainder, ")");
-            if (ObjectHelper.isNotEmpty(remainder)) {
-                boolean invalid = 
OgnlHelper.isInvalidValidOgnlExpression(remainder);
-                if (invalid) {
-                    throw new SimpleParserException(
-                            "Valid syntax: ${headerAsIndex(key, type, 
index).OGNL} was: " + function, token.getIndex());
-                }
-                return "headerAsIndex(message, " + type + ", \"" + key + "\", 
\"" + index + "\")"
-                       + ognlCodeMethods(remainder, type);
-            } else {
-                return "headerAsIndex(message, " + type + ", \"" + key + "\", 
\"" + index + "\")";
-            }
-        }
-
-        // headerAs
-        remainder = ifStartsWithReturnRemainder("headerAs(", function);
-        if (remainder != null) {
-            String keyAndType = StringHelper.before(remainder, ")");
-            if (keyAndType == null) {
-                throw new SimpleParserException("Valid syntax: ${headerAs(key, 
type)} was: " + function, token.getIndex());
-            }
-
-            String key = StringHelper.before(keyAndType, ",");
-            String type = StringHelper.after(keyAndType, ",");
-            remainder = StringHelper.after(remainder, ")");
-            if (ObjectHelper.isEmpty(key) || ObjectHelper.isEmpty(type)) {
-                throw new SimpleParserException("Valid syntax: ${headerAs(key, 
type)} was: " + function, token.getIndex());
-            }
-            key = StringHelper.removeQuotes(key);
-            key = key.trim();
-            type = appendClass(type);
-            type = type.replace('$', '.');
-            type = type.trim();
-            return "headerAs(message, \"" + key + "\", " + type + ")" + 
ognlCodeMethods(remainder, type);
-        }
-
-        // headers function
-        if ("in.headers".equals(function) || "headers".equals(function)) {
-            return "message.getHeaders()";
-        } else if ("headers.size".equals(function) || 
"headers.size()".equals(function)
-                || "headers.length".equals(function) || 
"headers.length()".equals(function)) {
-            return "message.getHeaders().size()";
-        }
-
-        // in header function
-        remainder = parseInHeader(function);
-        if (remainder != null) {
-            // remove leading character (dot, colon or ?)
-            if (remainder.startsWith(".") || remainder.startsWith(":") || 
remainder.startsWith("?")) {
-                remainder = remainder.substring(1);
-            }
-            // remove starting and ending brackets
-            if (remainder.startsWith("[") && remainder.endsWith("]")) {
-                remainder = remainder.substring(1, remainder.length() - 1);
-            }
-            // remove quotes from key
-            String key = StringHelper.removeLeadingAndEndingQuotes(remainder);
-            key = key.trim();
-
-            // validate syntax
-            boolean invalid = OgnlHelper.isInvalidValidOgnlExpression(key);
-            if (invalid) {
-                throw new SimpleParserException("Valid syntax: 
${header.name[key]} was: " + function, token.getIndex());
-            }
-
-            // the key can contain index as it may be a map header.foo[0]
-            // and the key can also be OGNL (e.g., if there is a dot)
-            boolean index = false;
-            List<String> parts = splitOgnl(key);
-            if (!parts.isEmpty()) {
-                String s = parts.get(0);
-                int pos = s.indexOf('[');
-                if (pos != -1) {
-                    index = true;
-                    // split key into name and index
-                    String before = s.substring(0, pos);
-                    String after = s.substring(pos);
-                    parts.set(0, before);
-                    parts.add(1, after);
-                }
-            }
-            if (index) {
-                // is there any index, then we should use headerAsIndex 
function instead
-                // (use splitOgnl which assembles multiple indexes into a 
single part?)
-                String func = "headerAsIndex(\"" + parts.get(0) + "\", 
Object.class, \"" + parts.get(1) + "\")";
-                if (parts.size() > 2) {
-                    String last = String.join("", parts.subList(2, 
parts.size()));
-                    if (!last.isEmpty()) {
-                        func += "." + last;
-                    }
-                }
-                return createCodeHeader(func);
-            } else if (OgnlHelper.isValidOgnlExpression(key)) {
-                // ognl based header must be typed
-                throw new SimpleParserException("Valid syntax: ${headerAs(key, 
type).OGNL} was: " + function, token.getIndex());
-            } else {
-                // regular header
-                return "header(message, \"" + key + "\")";
-            }
-        }
-
-        return null;
-    }
-
-    private String parseInHeader(String function) {
-        String remainder;
-        remainder = ifStartsWithReturnRemainder("in.headers", function);
-        if (remainder == null) {
-            remainder = ifStartsWithReturnRemainder("in.header", function);
-        }
-        if (remainder == null) {
-            remainder = ifStartsWithReturnRemainder("headers", function);
-        }
-        if (remainder == null) {
-            remainder = ifStartsWithReturnRemainder("header", function);
-        }
-        return remainder;
-    }
-
-    private String parseVariable(String function) {
-        String remainder;
-        remainder = ifStartsWithReturnRemainder("variables", function);
-        if (remainder == null) {
-            remainder = ifStartsWithReturnRemainder("variable", function);
-        }
-        return remainder;
-    }
-
-    private String createCodeVariables(final String function) {
-        // variableAs
-        String remainder = ifStartsWithReturnRemainder("variableAs(", 
function);
-        if (remainder != null) {
-            String keyAndType = StringHelper.before(remainder, ")");
-            if (keyAndType == null) {
-                throw new SimpleParserException("Valid syntax: 
${variableAs(key, type)} was: " + function, token.getIndex());
-            }
-            String key = StringHelper.before(keyAndType, ",");
-            String type = StringHelper.after(keyAndType, ",");
-            remainder = StringHelper.after(remainder, ")");
-            if (ObjectHelper.isEmpty(key) || ObjectHelper.isEmpty(type)) {
-                throw new SimpleParserException(
-                        "Valid syntax: ${variableAs(key, type)} was: " + 
function, token.getIndex());
-            }
-            key = StringHelper.removeQuotes(key);
-            key = key.trim();
-            type = appendClass(type);
-            type = type.replace('$', '.');
-            type = type.trim();
-            return "variableAs(exchange, \"" + key + "\", " + type + ")" + 
ognlCodeMethods(remainder, type);
-        }
-
-        // variables function
-        if ("variables".equals(function)) {
-            return "variables(exchange)";
-        } else if ("variables.size".equals(function) || 
"variables.size()".equals(function)
-                || "variables.length".equals(function) || 
"variables.length()".equals(function)) {
-            return "variablesSize(exchange)";
-        }
-
-        // variable
-        remainder = ifStartsWithReturnRemainder("variable", function);
-        if (remainder != null) {
-            // remove leading character (dot or ?)
-            if (remainder.startsWith(".") || remainder.startsWith("?")) {
-                remainder = remainder.substring(1);
-            }
-            // remove starting and ending brackets
-            if (remainder.startsWith("[") && remainder.endsWith("]")) {
-                remainder = remainder.substring(1, remainder.length() - 1);
-            }
-            // remove quotes from key
-            String key = StringHelper.removeLeadingAndEndingQuotes(remainder);
-            key = key.trim();
-
-            // validate syntax
-            boolean invalid = OgnlHelper.isInvalidValidOgnlExpression(key);
-            if (invalid) {
-                throw new SimpleParserException(
-                        "Valid syntax: ${variable.name[key]} was: " + 
function, token.getIndex());
-            }
-
-            // it is an index?
-            String index = null;
-            if (key.endsWith("]")) {
-                index = StringHelper.between(key, "[", "]");
-                if (index != null) {
-                    key = StringHelper.before(key, "[");
-                }
-            }
-            if (index != null) {
-                index = StringHelper.removeLeadingAndEndingQuotes(index);
-                return "variableAsIndex(exchange, Object.class, \"" + key + 
"\", \"" + index + "\")";
-            } else if (OgnlHelper.isValidOgnlExpression(remainder)) {
-                // ognl based exchange property must be typed
-                throw new SimpleParserException(
-                        "Valid syntax: ${variableAs(key, type)} was: " + 
function, token.getIndex());
-            } else {
-                // regular property
-                return "variable(exchange, \"" + key + "\")";
-            }
-        }
-
-        return null;
-    }
-
     private String createCodeExchangeProperty(final String function) {
         // exchangePropertyAsIndex
         String remainder = 
ifStartsWithReturnRemainder("exchangePropertyAsIndex(", function);
@@ -4078,83 +3448,9 @@ public class SimpleFunctionExpression extends 
LiteralExpression {
         return null;
     }
 
+    @Deprecated(since = "4.21")
     public static String ognlCodeMethods(String remainder, String type) {
-        StringBuilder sb = new StringBuilder(256);
-
-        if (remainder != null) {
-            List<String> methods = splitOgnl(remainder);
-            for (int i = 0; i < methods.size(); i++) {
-                String m = methods.get(i);
-                if (m.startsWith("(")) {
-                    // its parameters for the function so add as-is and 
continue
-                    sb.append(m);
-                    continue;
-                }
-
-                // clip index
-                String index = StringHelper.betweenOuterPair(m, '[', ']');
-                if (index != null) {
-                    m = StringHelper.before(m, "[");
-                }
-
-                // special for length on arrays
-                if (m != null && m.equals("length")) {
-                    if (type != null && type.contains("[]")) {
-                        sb.append(".length");
-                        continue;
-                    }
-                }
-
-                // single quotes for string literals should be replaced as 
double quotes
-                if (m != null) {
-                    m = OgnlHelper.methodAsDoubleQuotes(m);
-                }
-
-                // shorthand getter syntax: .name -> .getName()
-                if (m != null && !m.isEmpty()) {
-                    // a method so append with a dot
-                    sb.append(".");
-                    char ch = m.charAt(m.length() - 1);
-                    if (Character.isAlphabetic(ch)) {
-                        if (!m.startsWith("get")) {
-                            sb.append("get");
-                            sb.append(Character.toUpperCase(m.charAt(0)));
-                            sb.append(m.substring(1));
-                        } else {
-                            sb.append(m);
-                        }
-                        sb.append("()");
-                    } else {
-                        sb.append(m);
-                    }
-                }
-
-                // append index via a get method - eg get for a list, or get 
for a map (array not supported)
-                if (index != null) {
-                    sb.append(".get(");
-                    try {
-                        long lon = Long.parseLong(index);
-                        sb.append(lon);
-                        if (lon > Integer.MAX_VALUE) {
-                            sb.append("l");
-                        }
-                    } catch (Exception e) {
-                        // its text based
-                        index = 
StringHelper.removeLeadingAndEndingQuotes(index);
-                        sb.append("\"");
-                        sb.append(index);
-                        sb.append("\"");
-                    }
-                    sb.append(")");
-                }
-            }
-        }
-
-        if (!sb.isEmpty()) {
-            return sb.toString();
-        } else {
-            return remainder;
-        }
+        return SimpleFunctionHelper.ognlCodeMethods(remainder, type);
     }
 
     public static String[] codeSplitSafe(String input, char separator, boolean 
trim, boolean keepQuotes) {
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/BodyFunctionFactory.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/BodyFunctionFactory.java
new file mode 100644
index 000000000000..76b59ee26d20
--- /dev/null
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/BodyFunctionFactory.java
@@ -0,0 +1,293 @@
+/*
+ * 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.language.simple.functions;
+
+import java.util.List;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Expression;
+import org.apache.camel.language.simple.SimpleExpressionBuilder;
+import org.apache.camel.language.simple.types.SimpleParserException;
+import org.apache.camel.spi.SimpleLanguageFunctionFactory;
+import org.apache.camel.support.builder.ExpressionBuilder;
+import org.apache.camel.util.ObjectHelper;
+import org.apache.camel.util.OgnlHelper;
+import org.apache.camel.util.StringHelper;
+
+import static 
org.apache.camel.language.simple.SimpleFunctionHelper.appendClass;
+import static 
org.apache.camel.language.simple.SimpleFunctionHelper.ifStartsWithReturnRemainder;
+import static 
org.apache.camel.language.simple.SimpleFunctionHelper.ognlCodeMethods;
+import static org.apache.camel.language.simple.SimpleFunctionHelper.splitOgnl;
+
+/**
+ * Built-in Simple functions for the message body: {@code ${body}}, {@code 
${bodyAs(type)}},
+ * {@code ${bodyAs(type).OGNL}}, {@code ${mandatoryBodyAs(type)}}, {@code 
${bodyType}}, etc.
+ */
+public final class BodyFunctionFactory implements 
SimpleLanguageFunctionFactory {
+
+    @Override
+    public Expression createFunction(CamelContext camelContext, String 
function, int index) {
+        if (ObjectHelper.isEqualToAny(function, "body", "in.body")) {
+            return ExpressionBuilder.bodyExpression();
+        } else if (ObjectHelper.equal(function, "bodyType")) {
+            return ExpressionBuilder.bodyTypeExpression();
+        } else if (ObjectHelper.equal(function, "prettyBody")) {
+            return ExpressionBuilder.prettyBodyExpression();
+        } else if (ObjectHelper.equal(function, "toJsonBody")) {
+            return 
ExpressionBuilder.toJsonExpression(ExpressionBuilder.bodyExpression(), false);
+        } else if (ObjectHelper.equal(function, "toPrettyJsonBody")) {
+            return 
ExpressionBuilder.toJsonExpression(ExpressionBuilder.bodyExpression(), true);
+        } else if (ObjectHelper.equal(function, "bodyOneLine")) {
+            return ExpressionBuilder.bodyOneLine();
+        } else if (ObjectHelper.equal(function, "originalBody")) {
+            return ExpressionBuilder.originalBodyExpression();
+        }
+
+        // bodyAs
+        String remainder = ifStartsWithReturnRemainder("bodyAs(", function);
+        if (remainder != null) {
+            String type = StringHelper.before(remainder, ")");
+            if (type == null) {
+                throw new SimpleParserException("Valid syntax: ${bodyAs(type)} 
was: " + function, index);
+            }
+            type = StringHelper.removeQuotes(type);
+            remainder = StringHelper.after(remainder, ")");
+            if (ObjectHelper.isNotEmpty(remainder)) {
+                boolean invalid = 
OgnlHelper.isInvalidValidOgnlExpression(remainder);
+                if (invalid) {
+                    throw new SimpleParserException("Valid syntax: 
${bodyAs(type).OGNL} was: " + function, index);
+                }
+                return SimpleExpressionBuilder.bodyOgnlExpression(type, 
remainder);
+            } else {
+                return ExpressionBuilder.bodyExpression(type);
+            }
+        }
+
+        // mandatoryBodyAs
+        remainder = ifStartsWithReturnRemainder("mandatoryBodyAs(", function);
+        if (remainder != null) {
+            String type = StringHelper.before(remainder, ")");
+            if (type == null) {
+                throw new SimpleParserException("Valid syntax: 
${mandatoryBodyAs(type)} was: " + function, index);
+            }
+            type = StringHelper.removeQuotes(type);
+            remainder = StringHelper.after(remainder, ")");
+            if (ObjectHelper.isNotEmpty(remainder)) {
+                boolean invalid = 
OgnlHelper.isInvalidValidOgnlExpression(remainder);
+                if (invalid) {
+                    throw new SimpleParserException(
+                            "Valid syntax: ${mandatoryBodyAs(type).OGNL} was: 
" + function, index);
+                }
+                return 
SimpleExpressionBuilder.mandatoryBodyOgnlExpression(type, remainder);
+            } else {
+                return SimpleExpressionBuilder.mandatoryBodyExpression(type);
+            }
+        }
+
+        // body OGNL (must come after exact matches)
+        remainder = ifStartsWithReturnRemainder("body", function);
+        if (remainder == null) {
+            remainder = ifStartsWithReturnRemainder("in.body", function);
+        }
+        if (remainder != null) {
+            boolean ognlStart = remainder.startsWith(".") || 
remainder.startsWith("?") || remainder.startsWith("[");
+            boolean invalid = !ognlStart || 
OgnlHelper.isInvalidValidOgnlExpression(remainder);
+            if (invalid) {
+                throw new SimpleParserException("Valid syntax: ${body.OGNL} 
was: " + function, index);
+            }
+            return SimpleExpressionBuilder.bodyOgnlExpression(remainder);
+        }
+
+        return null;
+    }
+
+    @Override
+    @SuppressWarnings("deprecation")
+    public String createCode(CamelContext camelContext, String function, int 
index) {
+        if (ObjectHelper.isEqualToAny(function, "body", "in.body")) {
+            return "body";
+        } else if (ObjectHelper.equal(function, "bodyType")) {
+            return "bodyType(exchange)";
+        } else if (ObjectHelper.equal(function, "prettyBody")) {
+            return "prettyBody(exchange)";
+        } else if (ObjectHelper.equal(function, "toJsonBody")) {
+            return "toJsonBody(exchange, false)";
+        } else if (ObjectHelper.equal(function, "toPrettyJsonBody")) {
+            return "toJsonBody(exchange, true)";
+        } else if (ObjectHelper.equal(function, "bodyOneLine")) {
+            return "bodyOneLine(exchange)";
+        }
+
+        // bodyAsIndex
+        String remainder = ifStartsWithReturnRemainder("bodyAsIndex(", 
function);
+        if (remainder != null) {
+            String typeAndIndex = StringHelper.before(remainder, ")");
+            if (typeAndIndex == null) {
+                throw new SimpleParserException(
+                        "Valid syntax: ${bodyAsIndex(type, index).OGNL} was: " 
+ function, index);
+            }
+            String type = StringHelper.before(typeAndIndex, ",");
+            String idx = StringHelper.after(typeAndIndex, ",");
+            remainder = StringHelper.after(remainder, ")");
+            if (ObjectHelper.isEmpty(type) || ObjectHelper.isEmpty(idx)) {
+                throw new SimpleParserException(
+                        "Valid syntax: ${bodyAsIndex(type, index).OGNL} was: " 
+ function, index);
+            }
+            type = type.trim();
+            type = appendClass(type);
+            type = type.replace('$', '.');
+            idx = StringHelper.removeQuotes(idx);
+            idx = idx.trim();
+            if (ObjectHelper.isNotEmpty(remainder)) {
+                boolean invalid = 
OgnlHelper.isInvalidValidOgnlExpression(remainder);
+                if (invalid) {
+                    throw new SimpleParserException(
+                            "Valid syntax: ${bodyAsIndex(type, index).OGNL} 
was: " + function, index);
+                }
+                return "bodyAsIndex(message, " + type + ", \"" + idx + "\")" + 
ognlCodeMethods(remainder, type);
+            } else {
+                return "bodyAsIndex(message, " + type + ", \"" + idx + "\")";
+            }
+        }
+
+        // bodyAs
+        remainder = ifStartsWithReturnRemainder("bodyAs(", function);
+        if (remainder != null) {
+            String type = StringHelper.before(remainder, ")");
+            if (type == null) {
+                throw new SimpleParserException("Valid syntax: ${bodyAs(type)} 
was: " + function, index);
+            }
+            type = appendClass(type);
+            type = type.replace('$', '.');
+            type = type.trim();
+            remainder = StringHelper.after(remainder, ")");
+            if (ObjectHelper.isNotEmpty(remainder)) {
+                boolean invalid = 
OgnlHelper.isInvalidValidOgnlExpression(remainder);
+                if (invalid) {
+                    throw new SimpleParserException("Valid syntax: 
${bodyAs(type).OGNL} was: " + function, index);
+                }
+                if (remainder.startsWith("[")) {
+                    List<String> parts = splitOgnl(remainder);
+                    if (!parts.isEmpty()) {
+                        String func = "bodyAsIndex(" + type + ", \"" + 
parts.remove(0) + "\")";
+                        String last = String.join("", parts);
+                        if (!last.isEmpty()) {
+                            func += "." + last;
+                        }
+                        return createCode(camelContext, func, index);
+                    }
+                }
+                return "bodyAs(message, " + type + ")" + 
ognlCodeMethods(remainder, type);
+            } else {
+                return "bodyAs(message, " + type + ")";
+            }
+        }
+
+        // mandatoryBodyAsIndex
+        remainder = ifStartsWithReturnRemainder("mandatoryBodyAsIndex(", 
function);
+        if (remainder != null) {
+            String typeAndIndex = StringHelper.before(remainder, ")");
+            if (typeAndIndex == null) {
+                throw new SimpleParserException(
+                        "Valid syntax: ${mandatoryBodyAsIndex(type, 
index).OGNL} was: " + function, index);
+            }
+            String type = StringHelper.before(typeAndIndex, ",");
+            String idx = StringHelper.after(typeAndIndex, ",");
+            remainder = StringHelper.after(remainder, ")");
+            if (ObjectHelper.isEmpty(type) || ObjectHelper.isEmpty(idx)) {
+                throw new SimpleParserException(
+                        "Valid syntax: ${mandatoryBodyAsIndex(type, 
index).OGNL} was: " + function, index);
+            }
+            type = type.trim();
+            type = appendClass(type);
+            type = type.replace('$', '.');
+            idx = StringHelper.removeQuotes(idx);
+            idx = idx.trim();
+            if (ObjectHelper.isNotEmpty(remainder)) {
+                boolean invalid = 
OgnlHelper.isInvalidValidOgnlExpression(remainder);
+                if (invalid) {
+                    throw new SimpleParserException(
+                            "Valid syntax: ${mandatoryBodyAsIndex(type, 
index).OGNL} was: " + function, index);
+                }
+                return "mandatoryBodyAsIndex(message, " + type + ", \"" + idx 
+ "\")" + ognlCodeMethods(remainder, type);
+            } else {
+                return "mandatoryBodyAsIndex(message, " + type + ", \"" + idx 
+ "\")";
+            }
+        }
+
+        // mandatoryBodyAs
+        remainder = ifStartsWithReturnRemainder("mandatoryBodyAs(", function);
+        if (remainder != null) {
+            String type = StringHelper.before(remainder, ")");
+            if (type == null) {
+                throw new SimpleParserException("Valid syntax: 
${mandatoryBodyAs(type)} was: " + function, index);
+            }
+            type = appendClass(type);
+            type = type.replace('$', '.');
+            type = type.trim();
+            remainder = StringHelper.after(remainder, ")");
+            if (ObjectHelper.isNotEmpty(remainder)) {
+                boolean invalid = 
OgnlHelper.isInvalidValidOgnlExpression(remainder);
+                if (invalid) {
+                    throw new SimpleParserException(
+                            "Valid syntax: ${mandatoryBodyAs(type).OGNL} was: 
" + function, index);
+                }
+                if (remainder.startsWith("[")) {
+                    List<String> parts = splitOgnl(remainder);
+                    if (!parts.isEmpty()) {
+                        String func = "mandatoryBodyAsIndex(" + type + ", \"" 
+ parts.remove(0) + "\")";
+                        String last = String.join("", parts);
+                        if (!last.isEmpty()) {
+                            func += "." + last;
+                        }
+                        return createCode(camelContext, func, index);
+                    }
+                }
+                return "mandatoryBodyAs(message, " + type + ")" + 
ognlCodeMethods(remainder, type);
+            } else {
+                return "mandatoryBodyAs(message, " + type + ")";
+            }
+        }
+
+        // body OGNL (must come after exact matches)
+        remainder = ifStartsWithReturnRemainder("body", function);
+        if (remainder == null) {
+            remainder = ifStartsWithReturnRemainder("in.body", function);
+        }
+        if (remainder != null) {
+            boolean ognlStart = remainder.startsWith(".") || 
remainder.startsWith("?") || remainder.startsWith("[");
+            boolean invalid = !ognlStart || 
OgnlHelper.isInvalidValidOgnlExpression(remainder);
+            if (invalid) {
+                throw new SimpleParserException("Valid syntax: ${body.OGNL} 
was: " + function, index);
+            }
+            if (remainder.startsWith("[")) {
+                List<String> parts = splitOgnl(remainder);
+                if (!parts.isEmpty()) {
+                    String func = "bodyAsIndex(Object.class, \"" + 
parts.remove(0) + "\")";
+                    String last = String.join("", parts);
+                    if (!last.isEmpty()) {
+                        func += "." + last;
+                    }
+                    return createCode(camelContext, func, index);
+                }
+            }
+            return "body" + ognlCodeMethods(remainder, null);
+        }
+
+        return null;
+    }
+}
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/CollateFunctionFactory.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/CollateFunctionFactory.java
index c2a3a43c5894..e469426bbe61 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/CollateFunctionFactory.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/CollateFunctionFactory.java
@@ -24,7 +24,7 @@ import org.apache.camel.spi.SimpleLanguageFunctionFactory;
 import org.apache.camel.util.ObjectHelper;
 import org.apache.camel.util.StringHelper;
 
-import static 
org.apache.camel.language.simple.ast.SimpleFunctionExpression.ifStartsWithReturnRemainder;
+import static 
org.apache.camel.language.simple.SimpleFunctionHelper.ifStartsWithReturnRemainder;
 
 /**
  * Built-in Simple function: {@code ${collate(group)}}.
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/HeaderFunctionFactory.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/HeaderFunctionFactory.java
new file mode 100644
index 000000000000..f1068ea009c1
--- /dev/null
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/HeaderFunctionFactory.java
@@ -0,0 +1,218 @@
+/*
+ * 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.language.simple.functions;
+
+import java.util.List;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Expression;
+import org.apache.camel.language.simple.SimpleExpressionBuilder;
+import org.apache.camel.language.simple.types.SimpleParserException;
+import org.apache.camel.spi.SimpleLanguageFunctionFactory;
+import org.apache.camel.support.builder.ExpressionBuilder;
+import org.apache.camel.util.ObjectHelper;
+import org.apache.camel.util.OgnlHelper;
+import org.apache.camel.util.StringHelper;
+
+import static 
org.apache.camel.language.simple.SimpleFunctionHelper.appendClass;
+import static 
org.apache.camel.language.simple.SimpleFunctionHelper.ifStartsWithReturnRemainder;
+import static 
org.apache.camel.language.simple.SimpleFunctionHelper.ognlCodeMethods;
+import static 
org.apache.camel.language.simple.SimpleFunctionHelper.parseInHeader;
+import static org.apache.camel.language.simple.SimpleFunctionHelper.splitOgnl;
+
+/**
+ * Built-in Simple functions for message headers: {@code ${header.name}}, 
{@code ${headerAs(key, type)}},
+ * {@code ${headers}}, {@code ${headers.size}}, etc.
+ */
+public final class HeaderFunctionFactory implements 
SimpleLanguageFunctionFactory {
+
+    @Override
+    public Expression createFunction(CamelContext camelContext, String 
function, int index) {
+        // headerAs
+        String remainder = ifStartsWithReturnRemainder("headerAs(", function);
+        if (remainder != null) {
+            String keyAndType = StringHelper.before(remainder, ")");
+            if (keyAndType == null) {
+                throw new SimpleParserException("Valid syntax: ${headerAs(key, 
type)} was: " + function, index);
+            }
+            String key = StringHelper.before(keyAndType, ",");
+            String type = StringHelper.after(keyAndType, ",");
+            remainder = StringHelper.after(remainder, ")");
+            if (ObjectHelper.isEmpty(key) || ObjectHelper.isEmpty(type) || 
ObjectHelper.isNotEmpty(remainder)) {
+                throw new SimpleParserException("Valid syntax: ${headerAs(key, 
type)} was: " + function, index);
+            }
+            key = StringHelper.removeQuotes(key);
+            type = StringHelper.removeQuotes(type);
+            return ExpressionBuilder.headerExpression(key, type);
+        }
+
+        // headers exact matches (must check before parseInHeader to avoid 
mis-routing)
+        if ("in.headers".equals(function) || "headers".equals(function)) {
+            return ExpressionBuilder.headersExpression();
+        } else if ("headers.size".equals(function) || 
"headers.size()".equals(function)
+                || "headers.length".equals(function) || 
"headers.length()".equals(function)) {
+            return ExpressionBuilder.headersSizeExpression();
+        }
+
+        // in header function (header.name, in.header.name, headers.name, etc.)
+        remainder = parseInHeader(function);
+        if (remainder != null) {
+            if (remainder.startsWith(".") || remainder.startsWith(":") || 
remainder.startsWith("?")) {
+                remainder = remainder.substring(1);
+            }
+            if (remainder.startsWith("[") && remainder.endsWith("]")) {
+                remainder = remainder.substring(1, remainder.length() - 1);
+            }
+            String key = StringHelper.removeLeadingAndEndingQuotes(remainder);
+
+            boolean invalid = OgnlHelper.isInvalidValidOgnlExpression(key);
+            if (invalid) {
+                throw new SimpleParserException("Valid syntax: 
${header.name[key]} was: " + function, index);
+            }
+
+            if (OgnlHelper.isValidOgnlExpression(key)) {
+                return SimpleExpressionBuilder.headersOgnlExpression(key);
+            } else {
+                return ExpressionBuilder.headerExpression(key);
+            }
+        }
+
+        return null;
+    }
+
+    @Override
+    @SuppressWarnings("deprecation")
+    public String createCode(CamelContext camelContext, String function, int 
index) {
+        // headerAsIndex
+        String remainder = ifStartsWithReturnRemainder("headerAsIndex(", 
function);
+        if (remainder != null) {
+            String keyTypeAndIndex = StringHelper.before(remainder, ")");
+            if (keyTypeAndIndex == null) {
+                throw new SimpleParserException(
+                        "Valid syntax: ${headerAsIndex(key, type, index)} was: 
" + function, index);
+            }
+            String[] parts = keyTypeAndIndex.split(",");
+            if (parts.length != 3) {
+                throw new SimpleParserException(
+                        "Valid syntax: ${headerAsIndex(key, type, index)} was: 
" + function, index);
+            }
+            String key = parts[0];
+            String type = parts[1];
+            String idx = parts[2];
+            if (ObjectHelper.isEmpty(key) || ObjectHelper.isEmpty(type) || 
ObjectHelper.isEmpty(idx)) {
+                throw new SimpleParserException(
+                        "Valid syntax: ${headerAsIndex(key, type, index)} was: 
" + function, index);
+            }
+            key = StringHelper.removeQuotes(key);
+            key = key.trim();
+            type = appendClass(type);
+            type = type.replace('$', '.');
+            type = type.trim();
+            idx = StringHelper.removeQuotes(idx);
+            idx = idx.trim();
+            remainder = StringHelper.after(remainder, ")");
+            if (ObjectHelper.isNotEmpty(remainder)) {
+                boolean invalid = 
OgnlHelper.isInvalidValidOgnlExpression(remainder);
+                if (invalid) {
+                    throw new SimpleParserException(
+                            "Valid syntax: ${headerAsIndex(key, type, 
index).OGNL} was: " + function, index);
+                }
+                return "headerAsIndex(message, " + type + ", \"" + key + "\", 
\"" + idx + "\")"
+                       + ognlCodeMethods(remainder, type);
+            } else {
+                return "headerAsIndex(message, " + type + ", \"" + key + "\", 
\"" + idx + "\")";
+            }
+        }
+
+        // headerAs
+        remainder = ifStartsWithReturnRemainder("headerAs(", function);
+        if (remainder != null) {
+            String keyAndType = StringHelper.before(remainder, ")");
+            if (keyAndType == null) {
+                throw new SimpleParserException("Valid syntax: ${headerAs(key, 
type)} was: " + function, index);
+            }
+            String key = StringHelper.before(keyAndType, ",");
+            String type = StringHelper.after(keyAndType, ",");
+            remainder = StringHelper.after(remainder, ")");
+            if (ObjectHelper.isEmpty(key) || ObjectHelper.isEmpty(type)) {
+                throw new SimpleParserException("Valid syntax: ${headerAs(key, 
type)} was: " + function, index);
+            }
+            key = StringHelper.removeQuotes(key);
+            key = key.trim();
+            type = appendClass(type);
+            type = type.replace('$', '.');
+            type = type.trim();
+            return "headerAs(message, \"" + key + "\", " + type + ")" + 
ognlCodeMethods(remainder, type);
+        }
+
+        // headers exact matches (must check before parseInHeader)
+        if ("in.headers".equals(function) || "headers".equals(function)) {
+            return "message.getHeaders()";
+        } else if ("headers.size".equals(function) || 
"headers.size()".equals(function)
+                || "headers.length".equals(function) || 
"headers.length()".equals(function)) {
+            return "message.getHeaders().size()";
+        }
+
+        // in header function
+        remainder = parseInHeader(function);
+        if (remainder != null) {
+            if (remainder.startsWith(".") || remainder.startsWith(":") || 
remainder.startsWith("?")) {
+                remainder = remainder.substring(1);
+            }
+            if (remainder.startsWith("[") && remainder.endsWith("]")) {
+                remainder = remainder.substring(1, remainder.length() - 1);
+            }
+            String key = StringHelper.removeLeadingAndEndingQuotes(remainder);
+            key = key.trim();
+
+            boolean invalid = OgnlHelper.isInvalidValidOgnlExpression(key);
+            if (invalid) {
+                throw new SimpleParserException("Valid syntax: 
${header.name[key]} was: " + function, index);
+            }
+
+            boolean hasIndex = false;
+            List<String> parts = splitOgnl(key);
+            if (!parts.isEmpty()) {
+                String s = parts.get(0);
+                int pos = s.indexOf('[');
+                if (pos != -1) {
+                    hasIndex = true;
+                    String before = s.substring(0, pos);
+                    String after = s.substring(pos);
+                    parts.set(0, before);
+                    parts.add(1, after);
+                }
+            }
+            if (hasIndex) {
+                String func = "headerAsIndex(\"" + parts.get(0) + "\", 
Object.class, \"" + parts.get(1) + "\")";
+                if (parts.size() > 2) {
+                    String last = String.join("", parts.subList(2, 
parts.size()));
+                    if (!last.isEmpty()) {
+                        func += "." + last;
+                    }
+                }
+                return createCode(camelContext, func, index);
+            } else if (OgnlHelper.isValidOgnlExpression(key)) {
+                throw new SimpleParserException("Valid syntax: ${headerAs(key, 
type).OGNL} was: " + function, index);
+            } else {
+                return "header(message, \"" + key + "\")";
+            }
+        }
+
+        return null;
+    }
+}
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/JoinFunctionFactory.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/JoinFunctionFactory.java
index ea124ccdd6d2..7032da44246d 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/JoinFunctionFactory.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/JoinFunctionFactory.java
@@ -26,7 +26,7 @@ import org.apache.camel.util.ObjectHelper;
 import org.apache.camel.util.StringHelper;
 import org.apache.camel.util.StringQuoteHelper;
 
-import static 
org.apache.camel.language.simple.ast.SimpleFunctionExpression.ifStartsWithReturnRemainder;
+import static 
org.apache.camel.language.simple.SimpleFunctionHelper.ifStartsWithReturnRemainder;
 
 /**
  * Built-in Simple function: {@code ${join()}} / {@code ${join(separator)}} / 
{@code ${join(separator,prefix)}} /
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/RandomFunctionFactory.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/RandomFunctionFactory.java
index 81e00c9aa521..136d1d48eb5a 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/RandomFunctionFactory.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/RandomFunctionFactory.java
@@ -24,7 +24,7 @@ import org.apache.camel.spi.SimpleLanguageFunctionFactory;
 import org.apache.camel.util.ObjectHelper;
 import org.apache.camel.util.StringHelper;
 
-import static 
org.apache.camel.language.simple.ast.SimpleFunctionExpression.ifStartsWithReturnRemainder;
+import static 
org.apache.camel.language.simple.SimpleFunctionHelper.ifStartsWithReturnRemainder;
 
 /**
  * Built-in Simple function: {@code ${random(max)}} / {@code 
${random(min,max)}}.
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/SkipFunctionFactory.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/SkipFunctionFactory.java
index e5fb4073fcd8..25d8aa2862d3 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/SkipFunctionFactory.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/SkipFunctionFactory.java
@@ -24,7 +24,7 @@ import org.apache.camel.spi.SimpleLanguageFunctionFactory;
 import org.apache.camel.util.ObjectHelper;
 import org.apache.camel.util.StringHelper;
 
-import static 
org.apache.camel.language.simple.ast.SimpleFunctionExpression.ifStartsWithReturnRemainder;
+import static 
org.apache.camel.language.simple.SimpleFunctionHelper.ifStartsWithReturnRemainder;
 
 /**
  * Built-in Simple function: {@code ${skip(number)}}.
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/VariableFunctionFactory.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/VariableFunctionFactory.java
new file mode 100644
index 000000000000..368368a55885
--- /dev/null
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/functions/VariableFunctionFactory.java
@@ -0,0 +1,162 @@
+/*
+ * 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.language.simple.functions;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Expression;
+import org.apache.camel.language.simple.SimpleExpressionBuilder;
+import org.apache.camel.language.simple.types.SimpleParserException;
+import org.apache.camel.spi.SimpleLanguageFunctionFactory;
+import org.apache.camel.support.builder.ExpressionBuilder;
+import org.apache.camel.util.ObjectHelper;
+import org.apache.camel.util.OgnlHelper;
+import org.apache.camel.util.StringHelper;
+
+import static 
org.apache.camel.language.simple.SimpleFunctionHelper.appendClass;
+import static 
org.apache.camel.language.simple.SimpleFunctionHelper.ifStartsWithReturnRemainder;
+import static 
org.apache.camel.language.simple.SimpleFunctionHelper.ognlCodeMethods;
+import static 
org.apache.camel.language.simple.SimpleFunctionHelper.parseVariable;
+
+/**
+ * Built-in Simple functions for variables: {@code ${variable.name}}, {@code 
${variableAs(key, type)}},
+ * {@code ${variables}}, {@code ${variables.size}}, etc.
+ */
+public final class VariableFunctionFactory implements 
SimpleLanguageFunctionFactory {
+
+    @Override
+    public Expression createFunction(CamelContext camelContext, String 
function, int index) {
+        // variableAs
+        String remainder = ifStartsWithReturnRemainder("variableAs(", 
function);
+        if (remainder != null) {
+            String keyAndType = StringHelper.before(remainder, ")");
+            if (keyAndType == null) {
+                throw new SimpleParserException("Valid syntax: 
${variableAs(key, type)} was: " + function, index);
+            }
+            String key = StringHelper.before(keyAndType, ",");
+            String type = StringHelper.after(keyAndType, ",");
+            remainder = StringHelper.after(remainder, ")");
+            if (ObjectHelper.isEmpty(key) || ObjectHelper.isEmpty(type) || 
ObjectHelper.isNotEmpty(remainder)) {
+                throw new SimpleParserException("Valid syntax: 
${variableAs(key, type)} was: " + function, index);
+            }
+            key = StringHelper.removeQuotes(key);
+            type = StringHelper.removeQuotes(type);
+            return ExpressionBuilder.variableExpression(key, type);
+        }
+
+        // variables exact matches (must check before parseVariable to avoid 
mis-routing)
+        if ("variables".equals(function)) {
+            return ExpressionBuilder.variablesExpression();
+        } else if ("variables.size".equals(function) || 
"variables.size()".equals(function)
+                || "variables.length".equals(function) || 
"variables.length()".equals(function)) {
+            return ExpressionBuilder.variablesSizeExpression();
+        }
+
+        // variable function (variable.name, variables.name, etc.)
+        remainder = parseVariable(function);
+        if (remainder != null) {
+            if (remainder.startsWith(".") || remainder.startsWith(":") || 
remainder.startsWith("?")) {
+                remainder = remainder.substring(1);
+            }
+            if (remainder.startsWith("[") && remainder.endsWith("]")) {
+                remainder = remainder.substring(1, remainder.length() - 1);
+            }
+            String key = StringHelper.removeLeadingAndEndingQuotes(remainder);
+
+            boolean invalid = OgnlHelper.isInvalidValidOgnlExpression(key);
+            if (invalid) {
+                throw new SimpleParserException("Valid syntax: 
${variable.name[key]} was: " + function, index);
+            }
+
+            if (OgnlHelper.isValidOgnlExpression(key)) {
+                return SimpleExpressionBuilder.variablesOgnlExpression(key);
+            } else {
+                return ExpressionBuilder.variableExpression(key);
+            }
+        }
+
+        return null;
+    }
+
+    @Override
+    @SuppressWarnings("deprecation")
+    public String createCode(CamelContext camelContext, String function, int 
index) {
+        // variableAs
+        String remainder = ifStartsWithReturnRemainder("variableAs(", 
function);
+        if (remainder != null) {
+            String keyAndType = StringHelper.before(remainder, ")");
+            if (keyAndType == null) {
+                throw new SimpleParserException("Valid syntax: 
${variableAs(key, type)} was: " + function, index);
+            }
+            String key = StringHelper.before(keyAndType, ",");
+            String type = StringHelper.after(keyAndType, ",");
+            remainder = StringHelper.after(remainder, ")");
+            if (ObjectHelper.isEmpty(key) || ObjectHelper.isEmpty(type)) {
+                throw new SimpleParserException("Valid syntax: 
${variableAs(key, type)} was: " + function, index);
+            }
+            key = StringHelper.removeQuotes(key);
+            key = key.trim();
+            type = appendClass(type);
+            type = type.replace('$', '.');
+            type = type.trim();
+            return "variableAs(exchange, \"" + key + "\", " + type + ")" + 
ognlCodeMethods(remainder, type);
+        }
+
+        // variables exact matches (must check before variable prefix)
+        if ("variables".equals(function)) {
+            return "variables(exchange)";
+        } else if ("variables.size".equals(function) || 
"variables.size()".equals(function)
+                || "variables.length".equals(function) || 
"variables.length()".equals(function)) {
+            return "variablesSize(exchange)";
+        }
+
+        // variable (note: only matches "variable" prefix, not "variables" — 
preserving original asymmetry)
+        remainder = ifStartsWithReturnRemainder("variable", function);
+        if (remainder != null) {
+            if (remainder.startsWith(".") || remainder.startsWith("?")) {
+                remainder = remainder.substring(1);
+            }
+            if (remainder.startsWith("[") && remainder.endsWith("]")) {
+                remainder = remainder.substring(1, remainder.length() - 1);
+            }
+            String key = StringHelper.removeLeadingAndEndingQuotes(remainder);
+            key = key.trim();
+
+            boolean invalid = OgnlHelper.isInvalidValidOgnlExpression(key);
+            if (invalid) {
+                throw new SimpleParserException("Valid syntax: 
${variable.name[key]} was: " + function, index);
+            }
+
+            String idx = null;
+            if (key.endsWith("]")) {
+                idx = StringHelper.between(key, "[", "]");
+                if (idx != null) {
+                    key = StringHelper.before(key, "[");
+                }
+            }
+            if (idx != null) {
+                idx = StringHelper.removeLeadingAndEndingQuotes(idx);
+                return "variableAsIndex(exchange, Object.class, \"" + key + 
"\", \"" + idx + "\")";
+            } else if (OgnlHelper.isValidOgnlExpression(remainder)) {
+                throw new SimpleParserException("Valid syntax: 
${variableAs(key, type)} was: " + function, index);
+            } else {
+                return "variable(exchange, \"" + key + "\")";
+            }
+        }
+
+        return null;
+    }
+}
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/language/simple/functions/BodyFunctionFactoryTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/functions/BodyFunctionFactoryTest.java
new file mode 100644
index 000000000000..e5b3643ab60e
--- /dev/null
+++ 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/functions/BodyFunctionFactoryTest.java
@@ -0,0 +1,125 @@
+/*
+ * 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.language.simple.functions;
+
+import org.apache.camel.spi.SimpleLanguageFunctionFactory;
+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.assertNull;
+
+public class BodyFunctionFactoryTest extends 
AbstractSimpleFunctionFactoryTestSupport {
+
+    @Override
+    protected SimpleLanguageFunctionFactory createFactory() {
+        return new BodyFunctionFactory();
+    }
+
+    @Test
+    public void testBody() {
+        exchange.getIn().setBody("Hello World");
+        assertEquals("Hello World", evaluate("body", String.class));
+    }
+
+    @Test
+    public void testInBody() {
+        exchange.getIn().setBody("Hello World");
+        assertEquals("Hello World", evaluate("in.body", String.class));
+    }
+
+    @Test
+    public void testBodyAs() {
+        exchange.getIn().setBody(42);
+        assertEquals("42", evaluate("bodyAs(String)", String.class));
+    }
+
+    @Test
+    public void testMandatoryBodyAs() {
+        exchange.getIn().setBody(42);
+        assertEquals(42, evaluate("mandatoryBodyAs(Integer)", Integer.class));
+    }
+
+    @Test
+    public void testBodyOgnl() {
+        exchange.getIn().setBody("Hello World");
+        assertEquals(11, evaluate("body.length()", Integer.class));
+    }
+
+    @Test
+    public void testBodyType() {
+        exchange.getIn().setBody("Hello World");
+        assertNotNull(evaluate("bodyType"));
+    }
+
+    @Test
+    public void testCreateCodeBody() {
+        assertEquals("body", createCode("body"));
+    }
+
+    @Test
+    public void testCreateCodeInBody() {
+        assertEquals("body", createCode("in.body"));
+    }
+
+    @Test
+    public void testCreateCodeBodyType() {
+        assertEquals("bodyType(exchange)", createCode("bodyType"));
+    }
+
+    @Test
+    public void testCreateCodePrettyBody() {
+        assertEquals("prettyBody(exchange)", createCode("prettyBody"));
+    }
+
+    @Test
+    public void testCreateCodeToJsonBody() {
+        assertEquals("toJsonBody(exchange, false)", createCode("toJsonBody"));
+    }
+
+    @Test
+    public void testCreateCodeToPrettyJsonBody() {
+        assertEquals("toJsonBody(exchange, true)", 
createCode("toPrettyJsonBody"));
+    }
+
+    @Test
+    public void testCreateCodeBodyOneLine() {
+        assertEquals("bodyOneLine(exchange)", createCode("bodyOneLine"));
+    }
+
+    @Test
+    public void testCreateCodeBodyAs() {
+        assertEquals("bodyAs(message, String.class)", 
createCode("bodyAs(String)"));
+        assertEquals("bodyAs(message, java.lang.Integer.class)", 
createCode("bodyAs(java.lang.Integer)"));
+    }
+
+    @Test
+    public void testCreateCodeMandatoryBodyAs() {
+        assertEquals("mandatoryBodyAs(message, String.class)", 
createCode("mandatoryBodyAs(String)"));
+    }
+
+    @Test
+    public void testCreateCodeBodyOgnl() {
+        assertEquals("body.getLength()", createCode("body.length"));
+    }
+
+    @Test
+    public void testUnknownFunctionReturnsNull() {
+        assertNull(createFactory().createFunction(context, "random(10)", 0));
+        assertNull(createFactory().createCode(context, "random(10)", 0));
+    }
+}
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/language/simple/functions/HeaderFunctionFactoryTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/functions/HeaderFunctionFactoryTest.java
new file mode 100644
index 000000000000..72b972a37ccb
--- /dev/null
+++ 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/functions/HeaderFunctionFactoryTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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.language.simple.functions;
+
+import java.util.Map;
+
+import org.apache.camel.spi.SimpleLanguageFunctionFactory;
+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.assertNull;
+
+public class HeaderFunctionFactoryTest extends 
AbstractSimpleFunctionFactoryTestSupport {
+
+    @Override
+    protected SimpleLanguageFunctionFactory createFactory() {
+        return new HeaderFunctionFactory();
+    }
+
+    @Test
+    public void testHeader() {
+        exchange.getIn().setHeader("foo", "bar");
+        assertEquals("bar", evaluate("header.foo", String.class));
+    }
+
+    @Test
+    public void testInHeader() {
+        exchange.getIn().setHeader("foo", "bar");
+        assertEquals("bar", evaluate("in.header.foo", String.class));
+    }
+
+    @Test
+    public void testHeaders() {
+        exchange.getIn().setHeader("foo", "bar");
+        Map<?, ?> headers = evaluate("headers", Map.class);
+        assertNotNull(headers);
+        assertEquals("bar", headers.get("foo"));
+    }
+
+    @Test
+    public void testHeaderAs() {
+        exchange.getIn().setHeader("num", "42");
+        assertEquals(42, evaluate("headerAs(num, Integer)", Integer.class));
+    }
+
+    @Test
+    public void testCreateCodeHeader() {
+        assertEquals("header(message, \"foo\")", createCode("header.foo"));
+    }
+
+    @Test
+    public void testCreateCodeInHeader() {
+        assertEquals("header(message, \"foo\")", createCode("in.header.foo"));
+    }
+
+    @Test
+    public void testCreateCodeHeaders() {
+        assertEquals("message.getHeaders()", createCode("headers"));
+        assertEquals("message.getHeaders()", createCode("in.headers"));
+    }
+
+    @Test
+    public void testCreateCodeHeadersSize() {
+        assertEquals("message.getHeaders().size()", 
createCode("headers.size"));
+        assertEquals("message.getHeaders().size()", 
createCode("headers.size()"));
+        assertEquals("message.getHeaders().size()", 
createCode("headers.length"));
+        assertEquals("message.getHeaders().size()", 
createCode("headers.length()"));
+    }
+
+    @Test
+    public void testCreateCodeHeaderAs() {
+        assertEquals("headerAs(message, \"num\", Integer.class)", 
createCode("headerAs(num, Integer)"));
+        assertEquals("headerAs(message, \"num\", java.lang.Integer.class)", 
createCode("headerAs(num, java.lang.Integer)"));
+    }
+
+    @Test
+    public void testUnknownFunctionReturnsNull() {
+        assertNull(createFactory().createFunction(context, "body", 0));
+        assertNull(createFactory().createCode(context, "body", 0));
+    }
+}
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/language/simple/functions/VariableFunctionFactoryTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/functions/VariableFunctionFactoryTest.java
new file mode 100644
index 000000000000..313ec8e4677a
--- /dev/null
+++ 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/functions/VariableFunctionFactoryTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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.language.simple.functions;
+
+import java.util.Map;
+
+import org.apache.camel.spi.SimpleLanguageFunctionFactory;
+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.assertNull;
+
+public class VariableFunctionFactoryTest extends 
AbstractSimpleFunctionFactoryTestSupport {
+
+    @Override
+    protected SimpleLanguageFunctionFactory createFactory() {
+        return new VariableFunctionFactory();
+    }
+
+    @Test
+    public void testVariable() {
+        context.getVariable("myVar", Object.class);
+        exchange.setVariable("myVar", "hello");
+        assertEquals("hello", evaluate("variable.myVar", String.class));
+    }
+
+    @Test
+    public void testVariableAs() {
+        exchange.setVariable("num", "99");
+        assertEquals(99, evaluate("variableAs(num, Integer)", Integer.class));
+    }
+
+    @Test
+    public void testVariables() {
+        exchange.setVariable("foo", "bar");
+        Map<?, ?> vars = evaluate("variables", Map.class);
+        assertNotNull(vars);
+        assertEquals("bar", vars.get("foo"));
+    }
+
+    @Test
+    public void testCreateCodeVariable() {
+        assertEquals("variable(exchange, \"myVar\")", 
createCode("variable.myVar"));
+    }
+
+    @Test
+    public void testCreateCodeVariables() {
+        assertEquals("variables(exchange)", createCode("variables"));
+    }
+
+    @Test
+    public void testCreateCodeVariablesSize() {
+        assertEquals("variablesSize(exchange)", createCode("variables.size"));
+        assertEquals("variablesSize(exchange)", 
createCode("variables.size()"));
+        assertEquals("variablesSize(exchange)", 
createCode("variables.length"));
+        assertEquals("variablesSize(exchange)", 
createCode("variables.length()"));
+    }
+
+    @Test
+    public void testCreateCodeVariableAs() {
+        assertEquals("variableAs(exchange, \"num\", Integer.class)", 
createCode("variableAs(num, Integer)"));
+        assertEquals("variableAs(exchange, \"num\", java.lang.Integer.class)",
+                createCode("variableAs(num, java.lang.Integer)"));
+    }
+
+    @Test
+    public void testUnknownFunctionReturnsNull() {
+        assertNull(createFactory().createFunction(context, "body", 0));
+        assertNull(createFactory().createCode(context, "body", 0));
+    }
+}

Reply via email to