[ 
https://issues.apache.org/jira/browse/CAMEL-24844?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Claus Ibsen reassigned CAMEL-24844:
-----------------------------------

    Assignee: Claus Ibsen

> Body type flow: the validator carries the body's Java type from step to step 
> and warns where a step assumes text; the message history's recorded body 
> types feed the same check at runtime
> ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
>
>                 Key: CAMEL-24844
>                 URL: https://issues.apache.org/jira/browse/CAMEL-24844
>             Project: Camel
>          Issue Type: Improvement
>          Components: camel-core, camel-jbang, camel-yaml-dsl
>            Reporter: Claus Ibsen
>            Assignee: Claus Ibsen
>            Priority: Major
>
> Camel carries the payload as the natural Java object: a GenericFile from the 
> file consumer, a Map or List after {{unmarshal json}}, a byte[] or a 
> StreamCache after {{marshal}}, an InputStream from http. Integration people 
> think in text and bytes, and write routes as if the body were a String. Every 
> failure in the round-2 local-model benchmark that was not a YAML shape error 
> was that collision, and a person writing the same route gets the same runtime 
> exception:
> * {{${body.orderId}}} after {{unmarshal: json}} (the body is a Map): 
> MethodNotFoundException, in five examples. The runtime message now says "the 
> value is a Map: a key is read with [orderId]".
> * {{body.email}} in a Groovy expression on a GenericFile, before any 
> conversion: MissingPropertyException.
> * {{jsonpath}} on a body that is already a Map after unmarshal, or on a null 
> body on a timer route (CAMEL-24838).
> * {{${body}}} after {{unmarshal: json}} treated as JSON text: it is the Map's 
> toString ({{{id=ORD-1001, ...}}}).
> * {{new ByteArrayInputStream(body)}} in Groovy after {{marshal}}: with stream 
> caching on (the Camel CLI default) the body is a StreamCache, not a byte[]: 
> "could not find matching constructor". Same route, different Java type 
> depending on a runtime setting.
> Measured with the CLI on 4.23.0-SNAPSHOT: logging {{${body}}} prints readable 
> text at every stage (GenericFile, Map, marshal output, InputStream with 
> stream caching on, twice); only with 
> {{camel.main.streamCachingEnabled=false}} does a log step consume an 
> InputStream and leave every later step an empty body.
> The object model must stay: it is why a Map flows into a bean and a stream 
> into a file without copies. What can change is that Camel knows the type at 
> every step and says nothing until the step that assumes text explodes.
> Proposal, in two halves that share one rule set (which step produces which 
> type, which expressions and steps need which type):
> # *Static, in {{camel validate yaml}} and the camel-jbang-mcp validation 
> tool.* Walk the route and carry the body type forward from the catalog: 
> {{from: file}} gives GenericFile, {{unmarshal: json}} gives Map/List (or the 
> unmarshalType), {{marshal}} gives byte[]/StreamCache, {{unmarshal: 
> jacksonXml}} gives Map, {{split}} on a List gives an element, {{setBody: 
> constant}} gives String, {{to: http}} gives InputStream, {{convertBodyTo}} 
> gives its type, a bean gives its return type when it can be resolved. At each 
> step check the expression against the type and say it in the words of the 
> text world, with the line: "after unmarshal json the body is a Map: 
> ${body.orderId} fails, write ${body[orderId]}"; "jsonpath needs the JSON 
> text, move it before the unmarshal or use simple on the Map"; "the body is 
> the file (GenericFile): convert it with convertBodyTo String or unmarshal it 
> before the Groovy expression reads body.email"; "after marshal the body is 
> bytes (a cached stream when stream caching is on): a Groovy script gets it 
> with exchange.message.getBody(byte[])". Unknown types (a bean with no source) 
> stop the flow silently, no false positives.
> # *At runtime, from the message history.* The message history already 
> records, per step, what went through, including the class of the body at each 
> previous step. Use it in two places: (a) the exception message of a failing 
> expression or bean invocation can say "the body reaching this step was a 
> java.util.LinkedHashMap (set by unmarshal at line 12)", the fact a person 
> otherwise gets from a debugger; (b) {{camel trace}} and the camel-jbang-mcp 
> tools that show a message's history can show the body type per step, so the 
> flow is visible on a running app, and the static half can be checked against 
> it (a recorded history of the reference run is the ground truth for the 
> catalog's type rules).
> The static half is where most of the value is (it reaches the file before it 
> runs); the runtime half makes the rule set honest and gives the "what is the 
> body here" answer on a live app.
> Context: the round-2 local-model benchmark on the camel-jbang-examples ladder 
> (2026-09-19); the pattern was in aggregator, order-lines, csv-to-json, 
> groovy, openapi-client, filter-and-multicast, json-transform.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to