[
https://issues.apache.org/jira/browse/CAMEL-24844?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18117274#comment-18117274
]
Claus Ibsen commented on CAMEL-24844:
-------------------------------------
Status 2026-09-20 (afternoon), and the plan in four phases.
*Runtime half, in progress (two PRs open):*
* PR 26626: MessageHistory gets getBodyType() and getBodySize() (@since 4.23);
DefaultMessageHistory.captureBody captures the body's canonical class ("null"
for a null body) and, when the MessageSizeStrategy is enabled (the dev profile
does), its size; the default, metrics and micrometer factories call it. The
Message History table printed with a failure gains the Body type and Size
columns; the debug dev console rows, the error registry's step strings
(bodyType=... bodySize=...) and the backlog debugger's JMX XML carry both.
Review addressed, CI running.
* PR 26627: the message dump every tracer event carries says "null" for a null
body and gets a size for text and byte bodies from the size strategy; the
camel-jbang history and error views show the size next to the type; the history
tool of the camel-jbang-mcp server gets summary=true (steps with route, node,
elapsed, body type and size, no payloads); camel history --json, and the
message table says whether a size counts elements or bytes. CI running.
* Principle agreed: no body-type hints in generic exceptions (an error about a
header must not get a body sentence); the universal channels are the failure
table and the history, which state facts for every language and component. One
body-only branch stays open: RuntimeBeanExpressionException on a GenericFile,
stream or text body ("unmarshal or convert it before reading a field"), to be
folded into phase A.
*The four phases:*
# *Phase A, the static check* (2 days, next): BodyTypeChecks in
camel-jbang-core, 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. A hand-written
table for the ladder's vocabulary and six rules (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. Proven by replaying the 60 failure traces of the round-2 series: 19 of
156 attempts were these collisions.
# *Phase B, the runtime views* (done with 26626 and 26627, pending merge).
# *Phase C, the catalog metadata* (1 week + review): a bodyTypes block per
component (consumerOut, producerIn, producerOut), data format (marshalIn/Out,
unmarshalIn/Out) and language (input, output), two dimensions each: format (the
Kamelet data type names: json, xml, csv, text, binary) and carrier (text,
bytes|stream, file, map, list, pojo, node, null, any), plus the option that
changes it. One annotation per class through the mojos into the JSON and the
doc pages. Filled by an AI pass over the whole catalog, verified by a
tracer-based harness that compares the class observed at each step of the
reference examples with the metadata; a disagreement fails the build. This is
what generalises beyond jsonpath and jq to every language and component. Open
decisions: the exact vocabulary, and annotation on the class versus a curated
file the mojos read.
# *Phase D, the suggestions* (2-3 days, after C): the validator proposes the
conversion step (transformDataType, marshal, convertBodyTo) and the catalog
tools of the camel-jbang-mcp server carry the format, so an agent can ask what
the body is after a step.
The measured baseline stays the reference: series l (2026-09-19), 20 examples x
3, pass@3 9/20, 19 body-type collisions in 156 attempts; the next series runs
on a build with all of the round-2 fixes, and phase A is measured on the same
traces.
> 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)