[ 
https://issues.apache.org/jira/browse/CAMEL-24844?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18117268#comment-18117268
 ] 

Claus Ibsen commented on CAMEL-24844:
-------------------------------------

Analysis after the round-2 series (2026-09-20; three suites x 20 ladder 
examples, 156 model attempts).

*Measured.* 19 of the 156 attempts died on a body type collision, in 9 of the 
20 examples: `${body.key}` on a Map after unmarshal json (8), a key read on the 
file consumer's GenericFile / a stream cache / text (5), an http producer or 
marshal handed a Map (2), Groovy body.x on a GenericFile (2), split on a Map 
(1), jsonpath on a null body (1), `${jsonpath(...)}` read as OGNL on a 
GenericFile (1). None is catchable by the schema or by the per-step catalog 
checks: the type comes from the step before.

*What exists.* The catalog JSON has no in/out body type (a component carries 
consumerOnly, producerOnly, lenientProperties, browsable, remote); a field 
travels through the annotation, EndpointSchemaGeneratorMojo, the tooling model, 
JsonMapper and the docs tables. 47 transformers are catalogued with the 
DataType / TransformerRegistry SPI and transformDataType; Kamelets declare 
dataTypes in/out. At runtime MessageHelper.dumpAsJSon writes body.type into 
every dump the backlog tracer (camel trace) and the debugger record per step; 
DefaultMessageHistory itself carries node and elapsed only, so "the class at 
each step" is the tracer's dumps. The validator has a schema layer with 
positions and a line-based catalog layer ("Line N: ...") in camel-jbang-core; 
neither walks a route in order.

*Design.* Two dimensions in the metadata: format (the Kamelet data type: json, 
xml, csv, text, binary) and carrier (text, bytes|stream, file, map, list, pojo, 
node, null, any); a `bodyTypes` block per component (consumerOut, producerIn, 
producerOut), data format (marshalIn/Out, unmarshalIn/Out) and language (input, 
output), from one annotation per class through the mojos into the JSON and the 
doc pages. The static half is a walk per route carrying (format, carrier) 
through steps, branches, split, to (InOut vs InOnly), direct chains, 
convertBodyTo, marshal/unmarshal, setBody; unknown stops the walk silently. Six 
rules cover the 19 collisions (the Map key form, the file/text body needs an 
unmarshal, jsonpath on a Map, a null body on a timer route, an http producer 
given a Map, split on a Map), each with the sentence to write. The runtime 
half: the exceptions name the carrier reaching the step (always available) and 
the step that set it (with the tracer on); the trace, TUI and MCP history tools 
show body.type as a column; a tracer-based harness compares the observed class 
per step of the reference examples with the static prediction, which is what 
keeps AI-filled metadata honest.

*Phases.* A: BodyTypeChecks in camel-jbang-core with a hand-written table for 
the ladder's vocabulary and the six rules, proven by replaying the 60 failure 
traces (2 days). B: exceptions name the carrier; the type column (1-2 days). C: 
annotation, mojos, model, JSON, docs; the AI pass over the catalog; the 
verification harness (1 week plus review). D: the validator suggests the 
conversion step and the MCP catalog tools carry the format (2-3 days). A first, 
alone, proves the value on the benchmark before the catalog changes; C is the 
durable asset.

Full text with the risks in the camel-ai-vision drafts 
(body-type-flow-analysis.md).

> 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