From c10266e57ea5160c9d753efdb40f7de2d710a75e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Deleuze?= Date: Mon, 24 Nov 2025 12:14:25 +0100 Subject: [PATCH] Fix handling of ServerSentEvent with Jackson encoder This commit fixes ServerSentEvent handling with Jackson encoder when no Accept header is specified. It also moves the String checks to the JSON codec level, as they do not make sense for binary formats. Closes gh-35872 --- .../http/codec/AbstractJacksonDecoder.java | 9 +-------- .../http/codec/AbstractJacksonEncoder.java | 9 +++------ .../http/codec/json/JacksonJsonDecoder.java | 5 +++++ .../http/codec/json/JacksonJsonEncoder.java | 5 +++++ .../http/codec/cbor/JacksonCborDecoderTests.java | 2 +- .../http/codec/cbor/JacksonCborEncoderTests.java | 2 +- .../http/codec/smile/JacksonSmileDecoderTests.java | 2 +- .../http/codec/smile/JacksonSmileEncoderTests.java | 3 ++- .../result/method/annotation/SseIntegrationTests.java | 1 - 9 files changed, 19 insertions(+), 19 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/http/codec/AbstractJacksonDecoder.java b/spring-web/src/main/java/org/springframework/http/codec/AbstractJacksonDecoder.java index 3a5270dfe5c..63314e1b1a5 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/AbstractJacksonDecoder.java +++ b/spring-web/src/main/java/org/springframework/http/codec/AbstractJacksonDecoder.java @@ -102,14 +102,7 @@ public abstract class AbstractJacksonDecoder extends Jac @Override public boolean canDecode(ResolvableType elementType, @Nullable MimeType mimeType) { - if (!supportsMimeType(mimeType)) { - return false; - } - T mapper = selectMapper(elementType, mimeType); - if (mapper == null) { - return false; - } - return !CharSequence.class.isAssignableFrom(elementType.toClass()); + return supportsMimeType(mimeType) && (selectMapper(elementType, mimeType) != null); } @Override diff --git a/spring-web/src/main/java/org/springframework/http/codec/AbstractJacksonEncoder.java b/spring-web/src/main/java/org/springframework/http/codec/AbstractJacksonEncoder.java index 79f58b01a70..4a11813550c 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/AbstractJacksonEncoder.java +++ b/spring-web/src/main/java/org/springframework/http/codec/AbstractJacksonEncoder.java @@ -126,14 +126,11 @@ public abstract class AbstractJacksonEncoder extends Jac if (this.mapperRegistrations != null && selectMapper(elementType, mimeType) == null) { return false; } - Class clazz = elementType.resolve(); - if (clazz == null) { - return true; - } - if (MappingJacksonValue.class.isAssignableFrom(clazz)) { + Class elementClass = elementType.toClass(); + if (MappingJacksonValue.class.isAssignableFrom(elementClass)) { throw new UnsupportedOperationException("MappingJacksonValue is not supported, use hints instead"); } - return !String.class.isAssignableFrom(clazz); + return !ServerSentEvent.class.isAssignableFrom(elementClass); } @Override diff --git a/spring-web/src/main/java/org/springframework/http/codec/json/JacksonJsonDecoder.java b/spring-web/src/main/java/org/springframework/http/codec/json/JacksonJsonDecoder.java index e0feef2f812..73cb908d6fe 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/json/JacksonJsonDecoder.java +++ b/spring-web/src/main/java/org/springframework/http/codec/json/JacksonJsonDecoder.java @@ -104,6 +104,11 @@ public class JacksonJsonDecoder extends AbstractJacksonDecoder { super(mapper, mimeTypes); } + @Override + public boolean canDecode(ResolvableType elementType, @Nullable MimeType mimeType) { + return super.canDecode(elementType, mimeType) && !CharSequence.class.isAssignableFrom(elementType.toClass()); + } + @Override protected Flux processInput(Publisher input, ResolvableType elementType, @Nullable MimeType mimeType, @Nullable Map hints) { diff --git a/spring-web/src/main/java/org/springframework/http/codec/json/JacksonJsonEncoder.java b/spring-web/src/main/java/org/springframework/http/codec/json/JacksonJsonEncoder.java index 74251313171..e8a14d01b03 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/json/JacksonJsonEncoder.java +++ b/spring-web/src/main/java/org/springframework/http/codec/json/JacksonJsonEncoder.java @@ -121,6 +121,11 @@ public class JacksonJsonEncoder extends AbstractJacksonEncoder { } + @Override + public boolean canEncode(ResolvableType elementType, @Nullable MimeType mimeType) { + return super.canEncode(elementType, mimeType) && !String.class.isAssignableFrom(elementType.toClass()); + } + @Override protected List getMediaTypesForProblemDetail() { return problemDetailMimeTypes; diff --git a/spring-web/src/test/java/org/springframework/http/codec/cbor/JacksonCborDecoderTests.java b/spring-web/src/test/java/org/springframework/http/codec/cbor/JacksonCborDecoderTests.java index f60033da3df..dbb7111de3e 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/cbor/JacksonCborDecoderTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/cbor/JacksonCborDecoderTests.java @@ -57,7 +57,7 @@ class JacksonCborDecoderTests extends AbstractDecoderTests { assertThat(decoder.canDecode(ResolvableType.forClass(Pojo.class), MediaType.APPLICATION_CBOR)).isTrue(); assertThat(decoder.canDecode(ResolvableType.forClass(Pojo.class), null)).isTrue(); - assertThat(decoder.canDecode(ResolvableType.forClass(String.class), null)).isFalse(); + assertThat(decoder.canDecode(ResolvableType.forClass(String.class), null)).isTrue(); assertThat(decoder.canDecode(ResolvableType.forClass(Pojo.class), APPLICATION_JSON)).isFalse(); } diff --git a/spring-web/src/test/java/org/springframework/http/codec/cbor/JacksonCborEncoderTests.java b/spring-web/src/test/java/org/springframework/http/codec/cbor/JacksonCborEncoderTests.java index 2e7b0bc3e1f..f9579457cbb 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/cbor/JacksonCborEncoderTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/cbor/JacksonCborEncoderTests.java @@ -59,6 +59,7 @@ class JacksonCborEncoderTests extends AbstractLeakCheckingTests { ResolvableType pojoType = ResolvableType.forClass(Pojo.class); assertThat(this.encoder.canEncode(pojoType, MediaType.APPLICATION_CBOR)).isTrue(); assertThat(this.encoder.canEncode(pojoType, null)).isTrue(); + assertThat(this.encoder.canEncode(ResolvableType.forClass(String.class), null)).isTrue(); // SPR-15464 assertThat(this.encoder.canEncode(ResolvableType.NONE, null)).isTrue(); @@ -66,7 +67,6 @@ class JacksonCborEncoderTests extends AbstractLeakCheckingTests { @Test void canNotEncode() { - assertThat(this.encoder.canEncode(ResolvableType.forClass(String.class), null)).isFalse(); assertThat(this.encoder.canEncode(ResolvableType.forClass(Pojo.class), APPLICATION_XML)).isFalse(); } diff --git a/spring-web/src/test/java/org/springframework/http/codec/smile/JacksonSmileDecoderTests.java b/spring-web/src/test/java/org/springframework/http/codec/smile/JacksonSmileDecoderTests.java index db4ce8d29d7..e0c7aa465a8 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/smile/JacksonSmileDecoderTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/smile/JacksonSmileDecoderTests.java @@ -62,7 +62,7 @@ class JacksonSmileDecoderTests extends AbstractDecoderTests assertThat(decoder.canDecode(ResolvableType.forClass(Pojo.class), STREAM_SMILE_MIME_TYPE)).isTrue(); assertThat(decoder.canDecode(ResolvableType.forClass(Pojo.class), null)).isTrue(); - assertThat(decoder.canDecode(ResolvableType.forClass(String.class), null)).isFalse(); + assertThat(decoder.canDecode(ResolvableType.forClass(String.class), null)).isTrue(); assertThat(decoder.canDecode(ResolvableType.forClass(Pojo.class), APPLICATION_JSON)).isFalse(); } diff --git a/spring-web/src/test/java/org/springframework/http/codec/smile/JacksonSmileEncoderTests.java b/spring-web/src/test/java/org/springframework/http/codec/smile/JacksonSmileEncoderTests.java index b4b97df6f74..c32559c5b8c 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/smile/JacksonSmileEncoderTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/smile/JacksonSmileEncoderTests.java @@ -65,6 +65,7 @@ class JacksonSmileEncoderTests extends AbstractEncoderTests assertThat(this.encoder.canEncode(pojoType, SMILE_MIME_TYPE)).isTrue(); assertThat(this.encoder.canEncode(pojoType, STREAM_SMILE_MIME_TYPE)).isTrue(); assertThat(this.encoder.canEncode(pojoType, null)).isTrue(); + assertThat(this.encoder.canEncode(ResolvableType.forClass(String.class), null)).isTrue(); // SPR-15464 assertThat(this.encoder.canEncode(ResolvableType.NONE, null)).isTrue(); @@ -72,7 +73,7 @@ class JacksonSmileEncoderTests extends AbstractEncoderTests @Test void cannotEncode() { - assertThat(this.encoder.canEncode(ResolvableType.forClass(String.class), null)).isFalse(); + assertThat(this.encoder.canEncode(ResolvableType.forClass(Pojo.class), APPLICATION_XML)).isFalse(); } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/SseIntegrationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/SseIntegrationTests.java index 7b4677bc1f8..4d4d6f8cebc 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/SseIntegrationTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/SseIntegrationTests.java @@ -146,7 +146,6 @@ class SseIntegrationTests extends AbstractHttpHandlerIntegrationTests { Flux> result = this.webClient.get() .uri("/event") - .accept(TEXT_EVENT_STREAM) .retrieve() .bodyToFlux(new ParameterizedTypeReference<>() {});