From 9140ed6fa94e34f6371702a7c2b3c0941bc0393f Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Wed, 25 Mar 2026 21:45:24 +0100 Subject: [PATCH] Fix SSE flushing in SseServerResponse Prior to this commit, `SseServerResponse.send()` would flush the output stream returned by `getBody()`. Since gh-36385, `ServletServerHttpResponse` wraps this stream with a non-flushing decorator to avoid performance issues with `HttpMessageConverter` implementations that flush excessively. As a result, SSE events were no longer flushed to the client. This commit changes `send()` to call `outputMessage.flush()` instead of `body.flush()`, which properly delegates to the servlet response `flushBuffer()` and is not affected by the non-flushing wrapper. Fixes gh-36537 --- .../servlet/function/SseServerResponse.java | 6 ++-- .../function/SseServerResponseTests.java | 35 +++++++++++++++++++ 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/SseServerResponse.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/SseServerResponse.java index dc42b78a2dd..b12a672c6d5 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/SseServerResponse.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/SseServerResponse.java @@ -17,7 +17,6 @@ package org.springframework.web.servlet.function; import java.io.IOException; -import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.Collections; @@ -137,9 +136,8 @@ final class SseServerResponse extends AbstractServerResponse { public void send() throws IOException { this.builder.append('\n'); try { - OutputStream body = this.outputMessage.getBody(); - body.write(builderBytes()); - body.flush(); + this.outputMessage.getBody().write(builderBytes()); + this.outputMessage.flush(); } catch (IOException ex) { this.sendFailed = true; diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/function/SseServerResponseTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/function/SseServerResponseTests.java index a23f0fdcc0b..964238abe81 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/function/SseServerResponseTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/function/SseServerResponseTests.java @@ -75,6 +75,41 @@ class SseServerResponseTests { assertThat(this.mockResponse.getContentAsString()).isEqualTo(expected); } + @Test // gh-36537 + void sendStringFlushesResponse() throws Exception { + ServerResponse response = ServerResponse.sse(sse -> { + try { + sse.send("foo bar"); + } + catch (IOException ex) { + throw new UncheckedIOException(ex); + } + }); + + ServerResponse.Context context = Collections::emptyList; + + response.writeTo(this.mockRequest, this.mockResponse, context); + assertThat(this.mockResponse.isCommitted()).isTrue(); + } + + @Test // gh-36537 + void sendObjectFlushesResponse() throws Exception { + Person person = new Person("John Doe", 42); + ServerResponse response = ServerResponse.sse(sse -> { + try { + sse.send(person); + } + catch (IOException ex) { + throw new UncheckedIOException(ex); + } + }); + + ServerResponse.Context context = () -> List.of(new JacksonJsonHttpMessageConverter()); + + response.writeTo(this.mockRequest, this.mockResponse, context); + assertThat(this.mockResponse.isCommitted()).isTrue(); + } + @Test void sendObject() throws Exception { Person person = new Person("John Doe", 42);