Spring version: 6.2.10

We utilize Spring's SSE implementation to receive events from a third-party source and stream them to other components. We noticed that ServerSentEventHttpMessageWriter and ServerSentEventHttpMessageReader are not consistent: data that is serialized cannot be deserialized back to the original value.

reproducible test case:

    @Test
    void shouldSerializeAndDeserializeSse() {
        // given
        String originalData = "\n\n";
        String expectedSerialized =
                "id:1\n" +
                        "event:message\n" +
                        "data:\n" +
                        "data:\n" +
                        "data:\n\n";

        Flux<ServerSentEvent<String>> events = Flux.just(
                ServerSentEvent.builder(originalData)
                        .id("1")
                        .event("message")
                        .build()
        );

        // when – serialize
        MockServerHttpResponse response = new MockServerHttpResponse();
        ServerSentEventHttpMessageWriter writer = new ServerSentEventHttpMessageWriter();
        ResolvableType eventType = ResolvableType.forClassWithGenerics(ServerSentEvent.class, String.class);
        writer.write(events, eventType, MediaType.TEXT_EVENT_STREAM, response, null).block();

        String actualSerialized = response.getBodyAsString().block();

        // then – check serialization
        assertThat(actualSerialized).isEqualTo(expectedSerialized); // passes

        // when – deserialize back
        ServerSentEventHttpMessageReader reader = new ServerSentEventHttpMessageReader();
        List<ServerSentEvent<String>> deserialized = reader
                .read(eventType, new MockInputMessage(actualSerialized), Collections.emptyMap())
                .cast((Class<ServerSentEvent<String>>)(Class<?>)ServerSentEvent.class)
                .collectList()
                .block();

        // then – check deserialization
        assertThat(deserialized).hasSize(1);
        assertThat(deserialized.get(0).data()).isEqualTo(originalData); // fails
    }

Observed behavior: Serialization produces the expected output. After deserialization, data() is null instead of the original value "\n\n".

Expected behavior: Round-trip serialization and deserialization should be consistent.

Comment From: rstoyanchev

It looks on the reading side we treat empty String as null. We can make an improvement as suggested, but better not to introduce that going forward given the possibility for impact on existing applications.

Comment From: rstoyanchev

The spec also confirms that an empty data line should be included regardless of whether it is empty or not:

If the field name is "data" Append the field value to the data buffer, then append a single U+000A LINE FEED (LF) character to the data buffer.