Andrea Cosentino created CAMEL-26589:
----------------------------------------
Summary: Data type contract fails on byte[] bodies, breaking
kamelets with dataTypes
Key: CAMEL-26589
URL: https://issues.apache.org/jira/browse/CAMEL-26589
Project: Camel
Issue Type: Bug
Components: camel-kamelet, camel-yaml-dsl, camel-core
Affects Versions: 4.19.0, 4.18.1, 4.22.0
Reporter: Andrea Cosentino
When a route has a data type contract ({{inputType}} / {{outputType}}) and the
message body is a Java array such as {{byte[]}}, {{ContractAdvice}} fails the
exchange even though no conversion is needed:
{noformat}
org.apache.camel.CamelExecutionException: Failed to convert body to 'java:[B'
content using type conversion for null on the exchange: Exchange[...]
at
org.apache.camel.processor.transformer.TypeConverterTransformer.transform(TypeConverterTransformer.java:73)
at
org.apache.camel.processor.ContractAdvice.convertIfRequired(ContractAdvice.java:157)
at
org.apache.camel.processor.ContractAdvice.doTransform(ContractAdvice.java:127)
at
org.apache.camel.processor.ContractAdvice.after(ContractAdvice.java:101)
...
Caused by: java.lang.ClassNotFoundException: [B
at
org.apache.camel.impl.engine.DefaultClassResolver.resolveMandatoryClass(DefaultClassResolver.java:136)
at
org.apache.camel.processor.transformer.TypeConverterTransformer.transform(TypeConverterTransformer.java:61)
{noformat}
Since CAMEL-23174 (4.18.1, 4.19.0), {{KameletRoutesBuilderLoader}} turns a
Kamelet's {{spec.dataTypes.in/out}} into the route {{inputType}} /
{{outputType}}. Every Kamelet that declares {{dataTypes}} therefore hits this
whenever its body is an array, which is the normal case for binary sources such
as {{aws-s3-source}}.
h3. Cause
# When a route has a contract, {{RouteReifier}} calls
{{camelContext.setUseDataType(true)}}. From then on {{MessageSupport.setBody}}
tags every body with {{new DataType(body.getClass())}}. For a {{byte[]}} body
that is {{java:\[B}}, because {{byte[].class.getName()}} is {{\[B}}.
# {{ContractAdvice.after}} -> {{doTransform}} -> {{convertIfRequired(message,
from)}} with {{from = java:\[B}} builds a
{{TypeConverterTransformer(DataType)}}, which calls
{{ClassResolver.resolveMandatoryClass("\[B")}} before it checks whether the
body already has that type.
# {{DefaultClassResolver}} -> {{ObjectHelper.loadClass}} only uses
{{ClassLoader.loadClass(name)}}, which cannot load an array class from its
binary name ({{Class.forName("\[B", false, loader)}} can).
{{ObjectHelper.loadSimpleType}} knows {{byte[]}} but not {{\[B}}. The lookup
fails and the exchange is marked failed.
This logic is unchanged between 4.18.0 and 4.22.0 (code inspection), so
explicit route contracts with array bodies presumably fail on older versions
too. What CAMEL-23174 changed is that every Kamelet declaring {{dataTypes}} now
gets such a contract.
h3. Reproducer (Camel 4.22.0, plain JVM)
{code:java}
from("direct:contract").outputType("application-octet-stream").log("in contract
route");
Exchange e = template.send("direct:contract", ex ->
ex.getMessage().setBody("hello".getBytes()));
// e.getException(): CamelExecutionException: Failed to convert body to
'java:[B' ... Caused by: ClassNotFoundException: [B
// the same route with a String body succeeds
{code}
Kamelet variant, loaded from {{classpath:kamelets/bytes-source.kamelet.yaml}}
and consumed with {{from("kamelet:bytes-source")}}. Sending a {{byte[]}} to
{{direct:kamelet-in}} fails the same way; a {{String}} body succeeds.
{noformat}
apiVersion: camel.apache.org/v1
kind: Kamelet
metadata:
name: bytes-source
spec:
definition:
title: "Bytes Source"
properties: {}
dataTypes:
out:
default: binary
types:
binary:
format: "application-octet-stream"
mediaType: application/octet-stream
template:
from:
uri: "direct:kamelet-in"
steps:
- to: "kamelet:sink"
{noformat}
h3. Impact
* The contract is checked when the Kamelet route completes, so the steps after
{{kamelet:sink}} run first; only then is the exchange marked failed and the
consumer takes its failure path.
* Verified with {{aws-s3-source}} (Kamelets 4.22.0, default
{{includeBody=true}}, so the body is {{byte[]}}): every exchange fails at the
end of the Kamelet route, {{AWS2S3Consumer}} runs {{processRollback}} instead
of {{processCommit}}, {{deleteAfterRead}} never deletes, and the same objects
are consumed again on every poll. It is logged at WARN only ("Exchange failed,
so rolling back message status"), so it is easy to miss. Reported downstream in
[apache/camel-kafka-connector#1849|https://github.com/apache/camel-kafka-connector/issues/1849].
* 19 Kamelets in the 4.22.0 catalog declare {{dataTypes}}. The sources with a
binary default output are the most likely to carry {{byte[]}}: aws-s3-source,
azure-eventhubs-source, azure-storage-blob-source,
azure-storage-datalake-source, azure-storage-files-source,
google-storage-source and ibm-cos-source. Only aws-s3-source was verified.
h3. Possible fixes
* Resolve binary array names in {{ObjectHelper.loadClass}} /
{{DefaultClassResolver}}, for example by falling back to {{Class.forName(name,
false, loader)}} for names starting with {{\[}}.
* Keep the {{Class}} in {{DataType(Class)}}, so {{TypeConverterTransformer}}
does not have to resolve it by name.
* Skip the conversion in {{TypeConverterTransformer}} /
{{ContractAdvice.convertIfRequired}} when the body is already an instance of
the requested type.
Tests should cover a {{byte[]}} body for both a route contract and a Kamelet
with {{dataTypes}}.
h3. Workaround
Use the component endpoint directly instead of the Kamelet, so no contract is
added (verified with {{aws2-s3:...?deleteAfterRead=true}}).
----
_Claude Code on behalf of Andrea Cosentino_
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)