Describe the bug
When a structured-mode CloudEvent (Content-Type: application/cloudevents+json, whole envelope in the body) is consumed by a function whose target type is the CNCF io.cloudevents.CloudEvent, message conversion fails.
CloudEventMessageUtils.toCanonical(...) transforms the structured message into a binary-shaped message (data extracted to the payload, attributes moved to ce-* headers), but:
- it does not reset the
contentType header — the outgoing message keeps application/cloudevents+json even though the payload is no longer the serialized envelope; and
- it leaves the extracted
data as a deserialized Map, not serialized bytes.
Downstream, the CNCF io.cloudevents.spring.messaging.CloudEventMessageConverter re-decides structured-vs-binary purely from contentType. Because the stale application/cloudevents+json remains, it takes the structured reader and tries to re-parse the (already extracted) payload as the envelope:
io.cloudevents.rw.CloudEventRWException: Could not parse. Unknown encoding. Invalid content type or spec version
at io.cloudevents.core.message.impl.MessageUtils.parseStructuredOrBinaryMessage(MessageUtils.java:69)
at io.cloudevents.spring.messaging.CloudEventMessageConverter.createMessageReader(CloudEventMessageConverter.java:60)
at io.cloudevents.spring.messaging.CloudEventMessageConverter.fromMessage(CloudEventMessageConverter.java:45)
at org.springframework.cloud.function.context.config.SmartCompositeMessageConverter.fromMessage(SmartCompositeMessageConverter.java:137)
at org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry$FunctionInvocationWrapper.convertInputMessageIfNecessary(SimpleFunctionRegistry.java:1488)
Even after the contentType is corrected so the binary reader is selected, a second symptom remains: the CNCF binary reader only reads byte[]/String payloads (CloudEventMessageConverter#getBinaryData returns null for anything else), so the Map payload is dropped and the delivered cloudEvent.getData() is null while the attributes are present.
This only affects the io.cloudevents.CloudEvent target type. A plain POJO target works, because Spring's own message converters can bind a Map payload to a POJO — which is why the existing CloudEventFunctionTests#testStructuredPojoToPojoDefaultOutputAttributeProvider passes and this case is not covered.
Affected versions
spring-cloud-function-context:4.3.x. CloudEventMessageUtils.toCanonical / buildBinaryMessageFromStructuredMap are byte-identical across these.
io.cloudevents:cloudevents-spring: 2.2.1 (the optional dependency declared by spring-cloud-function-context);
- Java: 21.
Sample CloudEvent (structured mode)
Content-Type: application/cloudevents+json, body:
{
"specversion" : "1.0",
"type" : "org.springframework",
"source" : "https://spring.io/",
"id" : "A234-1234-1234",
"datacontenttype" : "application/json",
"data" : {
"version" : "1.0",
"releaseName" : "Spring Framework",
"releaseDate" : "24-03-2004"
}
}
Steps to reproduce
Minimal function with an io.cloudevents.CloudEvent input type (the cloudevents-spring converter is auto-registered by ContextFunctionCatalogAutoConfiguration$CloudEventsMessageConverterConfiguration):
@EnableAutoConfiguration
@Configuration
static class TestConfiguration {
@Bean
Function<Message<CloudEvent>, Message<CloudEvent>> echoCloudEvent() {
return Function.identity();
}
}
@Test
void structuredToCloudEventTypedPayload() {
String payload = /* the structured JSON envelope above */;
Function<Object, Object> function = context.getBean(FunctionCatalog.class).lookup("echoCloudEvent");
Message<String> inputMessage = MessageBuilder
.withPayload(payload)
.setHeader(MessageHeaders.CONTENT_TYPE, "application/cloudevents+json")
.build();
// structured mode: attributes are in the body, not the headers
assertThat(CloudEventMessageUtils.isCloudEvent(inputMessage)).isFalse();
Message<CloudEvent> result = (Message<CloudEvent>) function.apply(inputMessage); // throws
}
Actual behavior
function.apply(...) throws CloudEventRWException: Could not parse. Unknown encoding during input conversion. (If contentType is corrected in isolation, the call succeeds but the delivered CloudEvent has getData() == null.)
Expected behavior
The function receives a fully-populated io.cloudevents.CloudEvent — correct specversion, type, source, id, datacontenttype, and non-null data — consistent with how the same structured event is handled when the target type is a POJO.
Root cause
In CloudEventMessageUtils.buildBinaryMessageFromStructuredMap(...), after extracting data and moving attributes to ce-* headers, the original headers are copied back onto the outgoing message — re-applying contentType=application/cloudevents+json — and the data remains a Map. The binary-mode branch of toCanonical already resets contentType to the data content-type; the structured branch does not, and it does not serialize the data.
Proposed fix
In buildBinaryMessageFromStructuredMap, after building the binary-mode message:
- reset
MessageHeaders.CONTENT_TYPE to the data content-type (mirroring the binary-mode branch), and
- re-serialize the extracted
data to bytes using the data content-type when it is not already byte[]/String.
With both applied, the structured event converts correctly to io.cloudevents.CloudEvent (attributes + data), and all existing CloudEvent tests still pass. Happy to open a PR with a CloudEventFunctionTests case covering the io.cloudevents.CloudEvent target type.
Describe the bug
When a structured-mode CloudEvent (
Content-Type: application/cloudevents+json, whole envelope in the body) is consumed by a function whose target type is the CNCFio.cloudevents.CloudEvent, message conversion fails.CloudEventMessageUtils.toCanonical(...)transforms the structured message into a binary-shaped message (data extracted to the payload, attributes moved toce-*headers), but:contentTypeheader — the outgoing message keepsapplication/cloudevents+jsoneven though the payload is no longer the serialized envelope; anddataas a deserializedMap, not serialized bytes.Downstream, the CNCF
io.cloudevents.spring.messaging.CloudEventMessageConverterre-decides structured-vs-binary purely fromcontentType. Because the staleapplication/cloudevents+jsonremains, it takes the structured reader and tries to re-parse the (already extracted) payload as the envelope:Even after the
contentTypeis corrected so the binary reader is selected, a second symptom remains: the CNCF binary reader only readsbyte[]/Stringpayloads (CloudEventMessageConverter#getBinaryDatareturnsnullfor anything else), so theMappayload is dropped and the deliveredcloudEvent.getData()isnullwhile the attributes are present.This only affects the
io.cloudevents.CloudEventtarget type. A plain POJO target works, because Spring's own message converters can bind aMappayload to a POJO — which is why the existingCloudEventFunctionTests#testStructuredPojoToPojoDefaultOutputAttributeProviderpasses and this case is not covered.Affected versions
spring-cloud-function-context:4.3.x.CloudEventMessageUtils.toCanonical/buildBinaryMessageFromStructuredMapare byte-identical across these.io.cloudevents:cloudevents-spring:2.2.1(the optional dependency declared byspring-cloud-function-context);Sample CloudEvent (structured mode)
Content-Type: application/cloudevents+json, body:{ "specversion" : "1.0", "type" : "org.springframework", "source" : "https://spring.io/", "id" : "A234-1234-1234", "datacontenttype" : "application/json", "data" : { "version" : "1.0", "releaseName" : "Spring Framework", "releaseDate" : "24-03-2004" } }Steps to reproduce
Minimal function with an
io.cloudevents.CloudEventinput type (thecloudevents-springconverter is auto-registered byContextFunctionCatalogAutoConfiguration$CloudEventsMessageConverterConfiguration):Actual behavior
function.apply(...)throwsCloudEventRWException: Could not parse. Unknown encodingduring input conversion. (IfcontentTypeis corrected in isolation, the call succeeds but the deliveredCloudEventhasgetData() == null.)Expected behavior
The function receives a fully-populated
io.cloudevents.CloudEvent— correctspecversion,type,source,id,datacontenttype, and non-nulldata— consistent with how the same structured event is handled when the target type is a POJO.Root cause
In
CloudEventMessageUtils.buildBinaryMessageFromStructuredMap(...), after extractingdataand moving attributes toce-*headers, the original headers are copied back onto the outgoing message — re-applyingcontentType=application/cloudevents+json— and thedataremains aMap. The binary-mode branch oftoCanonicalalready resetscontentTypeto the data content-type; the structured branch does not, and it does not serialize the data.Proposed fix
In
buildBinaryMessageFromStructuredMap, after building the binary-mode message:MessageHeaders.CONTENT_TYPEto the data content-type (mirroring the binary-mode branch), anddatato bytes using the data content-type when it is not alreadybyte[]/String.With both applied, the structured event converts correctly to
io.cloudevents.CloudEvent(attributes + data), and all existing CloudEvent tests still pass. Happy to open a PR with aCloudEventFunctionTestscase covering theio.cloudevents.CloudEventtarget type.