Skip to content

CloudEvents incompatiable with Spring-framework 7.x #704

Description

@Stromner

I know there is a similar issue up already about upgrading to Spring-boot 2.7.18 but this project has lagged so far behind now that you can't even launch it with using an up-to-date ecosystem since Spring Framework 7 introduced a breaking change:

HttpHeaders changes
The HttpHeaders API has been revisited in 7.0. This class no longer extends the MultiValueMap contract. Underlying servers treat headers more like a collection of pairs, and many map-like operations do not behave or perform well, because headers are case-insensitive in the first place. We therefore removed several methods as a result and introduced fallbacks as immediately @deprecated, like HttpHeaders#asMultiValueMap. Please consider other methods as much as possible. See #33913 for more details.

Stacktrace you get when running latest release of cloudevent and you try to receive a message:
"Class org.springframework.http.HttpHeaders does not implement the requested interface java.util.Map","error.stack_trace":"java.lang.IncompatibleClassChangeError: Class org.springframework.http.HttpHeaders does not implement the requested interface java.util.Map\n\tat io.cloudevents.http.HttpMessageFactory.lambda$createReaderFromMultimap$5(HttpMessageFactory.java:112)\n\tat io.cloudevents.http.HttpMessageFactory.createReader(HttpMessageFactory.java:70)\n\tat io.cloudevents.http.HttpMessageFactory.createReaderFromMultimap(HttpMessageFactory.java:114)\n\tat io.cloudevents.spring.http.CloudEventHttpUtils.toReader(CloudEventHttpUtils.java:55)\n\tat io.cloudevents.spring.mvc.CloudEventHttpMessageConverter.readInternal(CloudEventHttpMessageConverter.java:54)\n\tat io.cloudevents.spring.mvc.CloudEventHttpMessageConverter.readInternal(CloudEventHttpMessageConverter.java:39)\n\tat org.springframework.http.converter.AbstractHttpMessageConverter.read(AbstractHttpMessageConverter.java:196)\n\tat

Activity

  1. Stromner commented on Dec 17, 2025

    @Stromner
    Author

    Created fork to update to Spring Framework 7 and JDK 25; so we've a plan on our end how to do this if you were to reject the PR that I will create.

  2. ranma2913 commented on Jul 13, 2026

    @ranma2913

    #739 provided some changes.

    What's left for this Issue?

    I've got these issues too:

    Spring Framework 7 HttpHeaders Compatibility

    Spring Framework 7 changed org.springframework.http.HttpHeaders so it no longer implements the Map / MultiValueMap contract.

    Current versions of io.cloudevents:cloudevents-spring pass HttpHeaders directly to:

    HttpMessageFactory.createReaderFromMultimap(headers, body.get());

    The CloudEvents HTTP reader expects a Map<String, List<String>>, so applications running Spring Framework 7 fail while deserializing inbound CloudEvents with:

    java.lang.IncompatibleClassChangeError:
    Class org.springframework.http.ReadOnlyHttpHeaders does not implement
    the requested interface java.util.Map
    

    In WebFlux, Reactor treats this linkage error as fatal. The HTTP exchange can remain incomplete, causing callers such as WebTestClient to report a timeout instead of the underlying exception.

    Impact

    • Inbound structured and binary CloudEvent handling fails on Spring Framework 7.
    • Both Spring MVC and WebFlux CloudEvents HTTP readers are affected because they use the shared CloudEventHttpUtils.toReader(...) implementation.
    • Outbound CloudEvent serialization is not affected by this specific issue.

    Temporary Application Workaround

    Until a CloudEvents SDK release includes the upstream fix, applications can register a compatible HttpMessageReader<CloudEvent> that copies request headers into a standard mutable Map<String, List<String>> before invoking HttpMessageFactory.createReaderFromMultimap(...).

    Upstream Status

    PR #739 updates CloudEventHttpUtils.toReader(...) to copy HttpHeaders entries into a regular map before passing them to the CloudEvents HTTP message factory.

    The fix is merged but is not included in the currently released 4.1.1 artifact.

  3. ranma2913 commented on Jul 13, 2026

    @ranma2913

    FYI wanted to share my work around:

    Spring Boot 4.x / Spring Framework 7 CloudEvents Workaround (Detailed)

    This write-up documents the workaround we applied while running on Spring Boot 4.x (Spring Framework 7) with io.cloudevents:cloudevents-spring:4.1.1.

    Problem Summary

    Spring Framework 7 changed HttpHeaders so it no longer implements the Map / MultiValueMap contract expected by older CloudEvents Spring reader code paths.

    CloudEvents 4.1.1 still routes inbound requests through logic equivalent to:

    HttpMessageFactory.createReaderFromMultimap(headers, body.get());

    When headers is a Spring 7 HttpHeaders implementation, this can fail with:

    java.lang.IncompatibleClassChangeError:
    Class org.springframework.http.ReadOnlyHttpHeaders does not implement
    the requested interface java.util.Map
    

    In WebFlux test clients, this can appear as request timeouts because the exchange never completes cleanly.

    Workaround Strategy

    Instead of using CloudEventHttpMessageReader directly, register a compatible reader that:

    1. Reads request body bytes exactly once.
    2. Copies request headers into a plain mutable MultiValueMap<String, String>.
    3. Calls HttpMessageFactory.createReaderFromMultimap(...) with that copied map.

    1) Compatible Reader (full snippet)

    package com.example.app.cloudevent;
    
    import io.cloudevents.CloudEvent;
    import io.cloudevents.core.message.MessageReader;
    import io.cloudevents.http.HttpMessageFactory;
    import java.io.InputStream;
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    import java.util.Map;
    import org.springframework.core.ResolvableType;
    import org.springframework.core.io.buffer.DataBufferUtils;
    import org.springframework.http.HttpHeaders;
    import org.springframework.http.MediaType;
    import org.springframework.http.ReactiveHttpInputMessage;
    import org.springframework.http.codec.HttpMessageReader;
    import org.springframework.util.LinkedMultiValueMap;
    import org.springframework.util.MultiValueMap;
    import org.springframework.util.StreamUtils;
    import reactor.core.publisher.Flux;
    import reactor.core.publisher.Mono;
    
    /**
     * CloudEvents reader compatible with Spring Framework 7 HttpHeaders behavior.
     */
    public class CompatibleCloudEventHttpMessageReader implements HttpMessageReader<CloudEvent> {
    
      @Override
      public List<MediaType> getReadableMediaTypes() {
        return Arrays.asList(MediaType.APPLICATION_OCTET_STREAM, MediaType.ALL);
      }
    
      @Override
      public boolean canRead(ResolvableType elementType, MediaType mediaType) {
        return CloudEvent.class.isAssignableFrom(elementType.toClass());
      }
    
      @Override
      public Flux<CloudEvent> read(
          ResolvableType elementType, ReactiveHttpInputMessage message, Map<String, Object> hints) {
        return Flux.from(readMono(elementType, message, hints));
      }
    
      @Override
      public Mono<CloudEvent> readMono(
          ResolvableType elementType, ReactiveHttpInputMessage message, Map<String, Object> hints) {
        MultiValueMap<String, String> headers = copyHeaders(message.getHeaders());
    
        return DataBufferUtils.join(message.getBody())
            .map(
                buffer -> {
                  try (InputStream inputStream = buffer.asInputStream(true)) {
                    return StreamUtils.copyToByteArray(inputStream);
                  } catch (Exception ex) {
                    throw new IllegalArgumentException(ex);
                  }
                })
            .map(bytes -> HttpMessageFactory.createReaderFromMultimap(headers, bytes))
            .map(MessageReader::toEvent);
      }
    
      private static MultiValueMap<String, String> copyHeaders(HttpHeaders sourceHeaders) {
        MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
        sourceHeaders.forEach((name, values) -> headers.put(name, new ArrayList<>(values)));
        return headers;
      }
    }

    2) WebFlux Codec Registration Change

    package com.example.app.cloudevent;
    
    import io.cloudevents.jackson.JsonFormat;
    import io.cloudevents.spring.webflux.CloudEventHttpMessageWriter;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.http.codec.ServerCodecConfigurer;
    import org.springframework.web.reactive.config.WebFluxConfigurer;
    
    @Configuration
    public class CloudEventWebFluxConfiguration implements WebFluxConfigurer {
    
      @Override
      public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
        // Use compatible reader for Spring 7 HttpHeaders behavior
        configurer.customCodecs().register(new CompatibleCloudEventHttpMessageReader());
        configurer.customCodecs().register(new CloudEventHttpMessageWriter());
      }
    
      @Bean
      public JsonFormat cloudEventJsonFormat() {
        return new JsonFormat();
      }
    }

    Upstream Tracking

    PR #739 includes the same conceptual fix (copying headers to a regular map before reading).

    Planned Cleanup Once Released

    After a CloudEvents release containing PR #739 is available in your artifact repositories:

    1. Upgrade cloudevents.version in pom.xml.
    2. Restore standard CloudEventHttpMessageReader registration.
    3. Remove CompatibleCloudEventHttpMessageReader.
    4. Re-run full test suite.
  4. jacekbilski commented on Aug 16, 2026

    @jacekbilski
    Contributor

    #746 includes Spring Boot 4/Framework 7 support, I'm working on getting it merged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions