[
https://issues.apache.org/jira/browse/CAMEL-24844?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18128365#comment-18128365
]
Claus Ibsen commented on CAMEL-24844:
-------------------------------------
h3. Plan (2026-10-18)
*What the benchmark says now.* The r17 series of the local-model benchmark
(2026-10-17/18; 10 runs of the 21 ladder examples, 659 of 710 steps passed): 17
of the 210 example runs hit a body type collision, and 12 of those failed a
step. 14 of the 17 are one shape: parsed data (a Map or List from {{unmarshal:
json}}, often through Groovy {{body.find}}) reaching a step that needs text -
the http producer (9), the platform-http reply (3), a file write (2). The other
three: a Groovy field read on text (the CAMEL-25330 hint fired),
{{$\{body.qty\}}} on the stream cache of an HTTP request, and
{{$\{body.size()\}}} on the iterator of sql's StreamList. The Map key form, the
top class of round 2, is gone since CAMEL-24916. The http producer's hint
(CAMEL-25309) helps sometimes: contracts-openapi-client recovered 5 of 6 times,
connect-http-client failed the step 3 of 3 with the same message. A model fixes
before it runs, so a static check moves the score more than a runtime message,
as measured on 2026-09-23.
*Quick wins first:*
# Answer the design questions on CAMEL-25200 (body origin in camel overview).
# A static rule for the 14: the validator's walk tracks parsed data after
{{unmarshal}}, and through Groovy {{find}} / {{findAll}} / {{collect}} on it,
and reports it at {{to: http}}, {{to: file}} and the end of a route that
answers an HTTP request, unless the REST binding marshals the reply. A hand
table like the one of TextBodyFlow, which moves into the catalog with
CAMEL-24961.
# The CAMEL-25309 sentence for the platform-http reply
({{VertxPlatformHttpSupport.writeResponseAsFallback}}) and the file producer
({{FileOperations.storeFile}}): 5 of the 17, all of which failed a step.
# The collision count as a line of the benchmark summary, so the measure of
each issue is one number per series.
*Then, in order:*
# CAMEL-25749, the shared helper: one sentence for every failure about the
body, with the step that set it. It takes over the hints and helps every user,
in production too.
# CAMEL-25750, observed types: the steps only a run can tell (kafka, jms, HTTP
consumers, beans), such as the {{$\{body.qty\}}} on the request stream.
# CAMEL-25334 as a prototype: the Groovy part behind an option, compared on the
ladder with the mode off and on. Its return side (structured results go back to
JSON text) would remove the top class rather than explain it. It can run in
parallel with CAMEL-25750.
# CAMEL-24961, the catalog metadata, last: the mechanism and the pilot set in
4.24, checked against recorded runs; then the hand tables of the validator move
into the catalog; the rest of the 407 components group by group.
The remaining collisions are few and nearly all one shape, so the targeted
fixes pay off first; the runtime pieces help every user next; the catalog comes
last, since its benchmark payoff is small now, while the docs and the catalog
tools need it.
_Claude Code on behalf of davsclaus_
> 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
> Fix For: 4.24.0
>
>
> 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)