From e6a076017bdfb4dc8879095b80f31c4681c36a5f Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Mon, 3 Nov 2025 14:33:12 +0100 Subject: [PATCH] Configure custom Kotlin Serialization converter Prior to this commit, `HttpMessageConverters` would consider the Kotlin Serialization JSON converter as an alternative to the Jackson variant. As seen in related issues, this converter is more commonly used for annotated classes specifically and applications often rely on Jackson acting as a fallback for types not supported by Kotlin Serialization. This commit enables applications to configure such a converter on `HttpMessageConverters` and order it ahead of of the JSON one. Closes gh-35733 --- .../DefaultHttpMessageConverters.java | 270 ++++++++++-------- .../http/converter/HttpMessageConverters.java | 10 + .../DefaultHttpMessageConvertersTests.java | 41 +-- .../JacksonJsonHttpMessageConverterTests.java | 6 + .../client/RestClientIntegrationTests.java | 19 -- .../web/client/RestTemplateTests.java | 14 - 6 files changed, 184 insertions(+), 176 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/http/converter/DefaultHttpMessageConverters.java b/spring-web/src/main/java/org/springframework/http/converter/DefaultHttpMessageConverters.java index 5b273b14d29..66bd4e9b79f 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/DefaultHttpMessageConverters.java +++ b/spring-web/src/main/java/org/springframework/http/converter/DefaultHttpMessageConverters.java @@ -104,33 +104,35 @@ class DefaultHttpMessageConverters implements HttpMessageConverters { boolean registerDefaults; - @Nullable ByteArrayHttpMessageConverter byteArrayMessageConverter; + @Nullable ByteArrayHttpMessageConverter byteArrayConverter; - @Nullable HttpMessageConverter stringMessageConverter; + @Nullable HttpMessageConverter stringConverter; - @Nullable HttpMessageConverter resourceMessageConverter; + @Nullable HttpMessageConverter resourceConverter; - @Nullable HttpMessageConverter resourceRegionMessageConverter; + @Nullable HttpMessageConverter resourceRegionConverter; @Nullable Consumer> configurer; - @Nullable HttpMessageConverter jsonMessageConverter; + @Nullable HttpMessageConverter kotlinJsonConverter; - @Nullable HttpMessageConverter xmlMessageConverter; + @Nullable HttpMessageConverter jsonConverter; - @Nullable HttpMessageConverter smileMessageConverter; + @Nullable HttpMessageConverter xmlConverter; - @Nullable HttpMessageConverter cborMessageConverter; + @Nullable HttpMessageConverter smileConverter; - @Nullable HttpMessageConverter yamlMessageConverter; + @Nullable HttpMessageConverter cborConverter; - @Nullable HttpMessageConverter protobufMessageConverter; + @Nullable HttpMessageConverter yamlConverter; - @Nullable HttpMessageConverter atomMessageConverter; + @Nullable HttpMessageConverter protobufConverter; - @Nullable HttpMessageConverter rssMessageConverter; + @Nullable HttpMessageConverter atomConverter; - final List> customMessageConverters = new ArrayList<>(); + @Nullable HttpMessageConverter rssConverter; + + final List> customConverters = new ArrayList<>(); static { @@ -156,45 +158,50 @@ class DefaultHttpMessageConverters implements HttpMessageConverters { } - void setStringMessageConverter(HttpMessageConverter stringMessageConverter) { - Assert.isTrue(stringMessageConverter.getSupportedMediaTypes().contains(MediaType.TEXT_PLAIN), - "stringMessageConverter should support 'text/plain'"); - this.stringMessageConverter = stringMessageConverter; + void setStringConverter(HttpMessageConverter stringConverter) { + Assert.isTrue(stringConverter.getSupportedMediaTypes().contains(MediaType.TEXT_PLAIN), + "stringConverter should support 'text/plain'"); + this.stringConverter = stringConverter; } - void setJsonMessageConverter(HttpMessageConverter jsonMessageConverter) { - Assert.isTrue(jsonMessageConverter.getSupportedMediaTypes().contains(MediaType.APPLICATION_JSON), - "jsonMessageConverter should support 'application/json'"); - this.jsonMessageConverter = jsonMessageConverter; + void setKotlinSerializationJsonConverter(HttpMessageConverter kotlinJsonConverter) { + Assert.notNull(kotlinJsonConverter, "kotlinJsonConverter must not be null"); + this.kotlinJsonConverter = kotlinJsonConverter; } - void setXmlMessageConverter(HttpMessageConverter xmlMessageConverter) { - Assert.isTrue(xmlMessageConverter.getSupportedMediaTypes().contains(MediaType.TEXT_XML), - "xmlMessageConverter should support 'text/xml'"); - this.xmlMessageConverter = xmlMessageConverter; + void setJsonConverter(HttpMessageConverter jsonConverter) { + Assert.isTrue(jsonConverter.getSupportedMediaTypes().contains(MediaType.APPLICATION_JSON), + "jsonConverter should support 'application/json'"); + this.jsonConverter = jsonConverter; } - void setSmileMessageConverter(HttpMessageConverter smileMessageConverter) { - Assert.isTrue(smileMessageConverter.getSupportedMediaTypes().contains(new MediaType("application", "x-jackson-smile")), - "smileMessageConverter should support 'application/x-jackson-smile'"); - this.smileMessageConverter = smileMessageConverter; + void setXmlConverter(HttpMessageConverter xmlConverter) { + Assert.isTrue(xmlConverter.getSupportedMediaTypes().contains(MediaType.TEXT_XML), + "xmlConverter should support 'text/xml'"); + this.xmlConverter = xmlConverter; } - void setCborMessageConverter(HttpMessageConverter cborMessageConverter) { - Assert.isTrue(cborMessageConverter.getSupportedMediaTypes().contains(MediaType.APPLICATION_CBOR), - "cborMessageConverter should support 'application/cbor'"); - this.cborMessageConverter = cborMessageConverter; + void setSmileConverter(HttpMessageConverter smileConverter) { + Assert.isTrue(smileConverter.getSupportedMediaTypes().contains(new MediaType("application", "x-jackson-smile")), + "smileConverter should support 'application/x-jackson-smile'"); + this.smileConverter = smileConverter; } - void setYamlMessageConverter(HttpMessageConverter yamlMessageConverter) { - Assert.isTrue(yamlMessageConverter.getSupportedMediaTypes().contains(MediaType.APPLICATION_YAML), - "yamlMessageConverter should support 'application/yaml'"); - this.yamlMessageConverter = yamlMessageConverter; + void setCborConverter(HttpMessageConverter cborConverter) { + Assert.isTrue(cborConverter.getSupportedMediaTypes().contains(MediaType.APPLICATION_CBOR), + "cborConverter should support 'application/cbor'"); + this.cborConverter = cborConverter; + } + + void setYamlConverter(HttpMessageConverter yamlConverter) { + Assert.isTrue(yamlConverter.getSupportedMediaTypes().contains(MediaType.APPLICATION_YAML), + "yamlConverter should support 'application/yaml'"); + this.yamlConverter = yamlConverter; } void addCustomMessageConverter(HttpMessageConverter customConverter) { Assert.notNull(customConverter, "'customConverter' must not be null"); - this.customMessageConverters.add(customConverter); + this.customConverters.add(customConverter); } void addMessageConverterConfigurer(Consumer> configurer) { @@ -203,126 +210,131 @@ class DefaultHttpMessageConverters implements HttpMessageConverters { List> getBaseConverters() { List> converters = new ArrayList<>(); - if (this.byteArrayMessageConverter != null) { - converters.add(this.byteArrayMessageConverter); + if (this.byteArrayConverter != null) { + converters.add(this.byteArrayConverter); } - if (this.stringMessageConverter != null) { - converters.add(this.stringMessageConverter); + if (this.stringConverter != null) { + converters.add(this.stringConverter); } return converters; } List> getCoreConverters() { List> converters = new ArrayList<>(); - if (this.jsonMessageConverter != null) { - converters.add(this.jsonMessageConverter); + if (this.kotlinJsonConverter != null) { + converters.add(this.kotlinJsonConverter); } - if (this.smileMessageConverter != null) { - converters.add(this.smileMessageConverter); + if (this.jsonConverter != null) { + converters.add(this.jsonConverter); } - if (this.cborMessageConverter!= null) { - converters.add(this.cborMessageConverter); + if (this.smileConverter != null) { + converters.add(this.smileConverter); } - if (this.yamlMessageConverter!= null) { - converters.add(this.yamlMessageConverter); + if (this.cborConverter != null) { + converters.add(this.cborConverter); } - if (this.xmlMessageConverter!= null) { - converters.add(this.xmlMessageConverter); + if (this.yamlConverter != null) { + converters.add(this.yamlConverter); } - if (this.protobufMessageConverter != null) { - converters.add(this.protobufMessageConverter); + if (this.xmlConverter != null) { + converters.add(this.xmlConverter); } - if (this.atomMessageConverter != null) { - converters.add(this.atomMessageConverter); + if (this.protobufConverter != null) { + converters.add(this.protobufConverter); } - if (this.rssMessageConverter != null) { - converters.add(this.rssMessageConverter); + if (this.atomConverter != null) { + converters.add(this.atomConverter); + } + if (this.rssConverter != null) { + converters.add(this.rssConverter); } return converters; } List> getCustomConverters() { - return this.customMessageConverters; + return this.customConverters; } void detectMessageConverters() { - this.byteArrayMessageConverter = new ByteArrayHttpMessageConverter(); + this.byteArrayConverter = new ByteArrayHttpMessageConverter(); - if (this.stringMessageConverter == null) { - this.stringMessageConverter = new StringHttpMessageConverter(); + if (this.stringConverter == null) { + this.stringConverter = new StringHttpMessageConverter(); } - if (this.jsonMessageConverter == null) { + if (this.kotlinJsonConverter == null) { + if (KOTLIN_SERIALIZATION_JSON_PRESENT) { + this.kotlinJsonConverter = new KotlinSerializationJsonHttpMessageConverter(); + } + } + if (this.jsonConverter == null) { if (JACKSON_PRESENT) { - this.jsonMessageConverter = new JacksonJsonHttpMessageConverter(); + this.jsonConverter = new JacksonJsonHttpMessageConverter(); } else if (JACKSON_2_PRESENT) { - this.jsonMessageConverter = new MappingJackson2HttpMessageConverter(); + this.jsonConverter = new MappingJackson2HttpMessageConverter(); } else if (GSON_PRESENT) { - this.jsonMessageConverter = new GsonHttpMessageConverter(); + this.jsonConverter = new GsonHttpMessageConverter(); } else if (JSONB_PRESENT) { - this.jsonMessageConverter = new JsonbHttpMessageConverter(); - } - else if (KOTLIN_SERIALIZATION_JSON_PRESENT) { - this.jsonMessageConverter = new KotlinSerializationJsonHttpMessageConverter(); + this.jsonConverter = new JsonbHttpMessageConverter(); } } - if (this.xmlMessageConverter == null) { + if (this.xmlConverter == null) { if (JACKSON_XML_PRESENT) { - this.xmlMessageConverter = new JacksonXmlHttpMessageConverter(); + this.xmlConverter = new JacksonXmlHttpMessageConverter(); } else if (JACKSON_2_XML_PRESENT) { - this.xmlMessageConverter = new MappingJackson2XmlHttpMessageConverter(); + this.xmlConverter = new MappingJackson2XmlHttpMessageConverter(); } else if (JAXB_2_PRESENT) { - this.xmlMessageConverter = new Jaxb2RootElementHttpMessageConverter(); + this.xmlConverter = new Jaxb2RootElementHttpMessageConverter(); } } - if (this.smileMessageConverter == null) { + if (this.smileConverter == null) { if (JACKSON_SMILE_PRESENT) { - this.smileMessageConverter = new JacksonSmileHttpMessageConverter(); + this.smileConverter = new JacksonSmileHttpMessageConverter(); } else if (JACKSON_2_SMILE_PRESENT) { - this.smileMessageConverter = new MappingJackson2SmileHttpMessageConverter(); + this.smileConverter = new MappingJackson2SmileHttpMessageConverter(); } } - if (this.cborMessageConverter == null) { + if (this.cborConverter == null) { if (JACKSON_CBOR_PRESENT) { - this.cborMessageConverter = new JacksonCborHttpMessageConverter(); + this.cborConverter = new JacksonCborHttpMessageConverter(); } else if (JACKSON_2_CBOR_PRESENT) { - this.cborMessageConverter = new MappingJackson2CborHttpMessageConverter(); + this.cborConverter = new MappingJackson2CborHttpMessageConverter(); } else if (KOTLIN_SERIALIZATION_CBOR_PRESENT) { - this.cborMessageConverter = new KotlinSerializationCborHttpMessageConverter(); + this.cborConverter = new KotlinSerializationCborHttpMessageConverter(); } } - if (this.yamlMessageConverter == null) { + if (this.yamlConverter == null) { if (JACKSON_YAML_PRESENT) { - this.yamlMessageConverter = new JacksonYamlHttpMessageConverter(); + this.yamlConverter = new JacksonYamlHttpMessageConverter(); } else if (JACKSON_2_YAML_PRESENT) { - this.yamlMessageConverter = new MappingJackson2YamlHttpMessageConverter(); + this.yamlConverter = new MappingJackson2YamlHttpMessageConverter(); } } - if (this.protobufMessageConverter == null) { + if (this.protobufConverter == null) { if (KOTLIN_SERIALIZATION_PROTOBUF_PRESENT) { - this.protobufMessageConverter = new KotlinSerializationProtobufHttpMessageConverter(); + this.protobufConverter = new KotlinSerializationProtobufHttpMessageConverter(); } } if (ROME_PRESENT) { - if (this.atomMessageConverter == null) { - this.atomMessageConverter = new AtomFeedHttpMessageConverter(); + if (this.atomConverter == null) { + this.atomConverter = new AtomFeedHttpMessageConverter(); } - if (this.rssMessageConverter == null) { - this.rssMessageConverter = new RssChannelHttpMessageConverter(); + if (this.rssConverter == null) { + this.rssConverter = new RssChannelHttpMessageConverter(); } } } @@ -338,38 +350,44 @@ class DefaultHttpMessageConverters implements HttpMessageConverters { } @Override - public ClientBuilder withStringConverter(HttpMessageConverter stringMessageConverter) { - setStringMessageConverter(stringMessageConverter); + public ClientBuilder withStringConverter(HttpMessageConverter stringConverter) { + setStringConverter(stringConverter); return this; } @Override - public ClientBuilder withJsonConverter(HttpMessageConverter jsonMessageConverter) { - setJsonMessageConverter(jsonMessageConverter); + public ClientBuilder withKotlinSerializationJsonConverter(HttpMessageConverter kotlinSerializationJsonConverter) { + setKotlinSerializationJsonConverter(kotlinSerializationJsonConverter); return this; } @Override - public ClientBuilder withXmlConverter(HttpMessageConverter xmlMessageConverter) { - setXmlMessageConverter(xmlMessageConverter); + public ClientBuilder withJsonConverter(HttpMessageConverter jsonConverter) { + setJsonConverter(jsonConverter); return this; } @Override - public ClientBuilder withSmileConverter(HttpMessageConverter smileMessageConverter) { - setSmileMessageConverter(smileMessageConverter); + public ClientBuilder withXmlConverter(HttpMessageConverter xmlConverter) { + setXmlConverter(xmlConverter); return this; } @Override - public ClientBuilder withCborConverter(HttpMessageConverter cborMessageConverter) { - setCborMessageConverter(cborMessageConverter); + public ClientBuilder withSmileConverter(HttpMessageConverter smileConverter) { + setSmileConverter(smileConverter); return this; } @Override - public ClientBuilder withYamlConverter(HttpMessageConverter yamlMessageConverter) { - setYamlMessageConverter(yamlMessageConverter); + public ClientBuilder withCborConverter(HttpMessageConverter cborConverter) { + setCborConverter(cborConverter); + return this; + } + + @Override + public ClientBuilder withYamlConverter(HttpMessageConverter yamlConverter) { + setYamlConverter(yamlConverter); return this; } @@ -388,7 +406,7 @@ class DefaultHttpMessageConverters implements HttpMessageConverters { @Override public HttpMessageConverters build() { if (this.registerDefaults) { - this.resourceMessageConverter = new ResourceHttpMessageConverter(false); + this.resourceConverter = new ResourceHttpMessageConverter(false); detectMessageConverters(); } List> partConverters = new ArrayList<>(this.getCustomConverters()); @@ -396,8 +414,8 @@ class DefaultHttpMessageConverters implements HttpMessageConverters { if (this.registerDefaults) { partConverters.addAll(this.getCoreConverters()); allConverters.addAll(this.getBaseConverters()); - if (this.resourceMessageConverter != null) { - allConverters.add(this.resourceMessageConverter); + if (this.resourceConverter != null) { + allConverters.add(this.resourceConverter); } } if (!partConverters.isEmpty() || !allConverters.isEmpty()) { @@ -423,38 +441,44 @@ class DefaultHttpMessageConverters implements HttpMessageConverters { } @Override - public ServerBuilder withStringConverter(HttpMessageConverter stringMessageConverter) { - setStringMessageConverter(stringMessageConverter); + public ServerBuilder withStringConverter(HttpMessageConverter stringConverter) { + setStringConverter(stringConverter); return this; } @Override - public ServerBuilder withJsonConverter(HttpMessageConverter jsonMessageConverter) { - setJsonMessageConverter(jsonMessageConverter); + public ServerBuilder withKotlinSerializationJsonConverter(HttpMessageConverter kotlinSerializationJsonConverter) { + setKotlinSerializationJsonConverter(kotlinSerializationJsonConverter); return this; } @Override - public ServerBuilder withXmlConverter(HttpMessageConverter xmlMessageConverter) { - setXmlMessageConverter(xmlMessageConverter); + public ServerBuilder withJsonConverter(HttpMessageConverter jsonConverter) { + setJsonConverter(jsonConverter); return this; } @Override - public ServerBuilder withSmileConverter(HttpMessageConverter smileMessageConverter) { - setSmileMessageConverter(smileMessageConverter); + public ServerBuilder withXmlConverter(HttpMessageConverter xmlConverter) { + setXmlConverter(xmlConverter); return this; } @Override - public ServerBuilder withCborConverter(HttpMessageConverter cborMessageConverter) { - setCborMessageConverter(cborMessageConverter); + public ServerBuilder withSmileConverter(HttpMessageConverter smileConverter) { + setSmileConverter(smileConverter); return this; } @Override - public ServerBuilder withYamlConverter(HttpMessageConverter yamlMessageConverter) { - setYamlMessageConverter(yamlMessageConverter); + public ServerBuilder withCborConverter(HttpMessageConverter cborConverter) { + setCborConverter(cborConverter); + return this; + } + + @Override + public ServerBuilder withYamlConverter(HttpMessageConverter yamlConverter) { + setYamlConverter(yamlConverter); return this; } @@ -473,8 +497,8 @@ class DefaultHttpMessageConverters implements HttpMessageConverters { @Override public HttpMessageConverters build() { if (this.registerDefaults) { - this.resourceMessageConverter = new ResourceHttpMessageConverter(); - this.resourceRegionMessageConverter = new ResourceRegionHttpMessageConverter(); + this.resourceConverter = new ResourceHttpMessageConverter(); + this.resourceRegionConverter = new ResourceRegionHttpMessageConverter(); detectMessageConverters(); } List> partConverters = new ArrayList<>(this.getCustomConverters()); @@ -482,11 +506,11 @@ class DefaultHttpMessageConverters implements HttpMessageConverters { if (this.registerDefaults) { partConverters.addAll(this.getCoreConverters()); allConverters.addAll(this.getBaseConverters()); - if (this.resourceMessageConverter != null) { - allConverters.add(this.resourceMessageConverter); + if (this.resourceConverter != null) { + allConverters.add(this.resourceConverter); } - if (this.resourceRegionMessageConverter != null) { - allConverters.add(this.resourceRegionMessageConverter); + if (this.resourceRegionConverter != null) { + allConverters.add(this.resourceRegionConverter); } } if (!partConverters.isEmpty() || !allConverters.isEmpty()) { diff --git a/spring-web/src/main/java/org/springframework/http/converter/HttpMessageConverters.java b/spring-web/src/main/java/org/springframework/http/converter/HttpMessageConverters.java index 42d8b9797b7..bd038304549 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/HttpMessageConverters.java +++ b/spring-web/src/main/java/org/springframework/http/converter/HttpMessageConverters.java @@ -40,6 +40,7 @@ public interface HttpMessageConverters extends Iterable> *
  • {@link StringHttpMessageConverter} with the {@link java.nio.charset.StandardCharsets#ISO_8859_1} charset *
  • {@link ResourceHttpMessageConverter}, with resource streaming support disabled *
  • a Multipart converter, using all detected and custom converters for part conversion + *
  • A Kotlin Serialization converter *
  • A JSON converter *
  • A Smile converter *
  • A CBOR converter @@ -62,6 +63,7 @@ public interface HttpMessageConverters extends Iterable> *
  • {@link StringHttpMessageConverter} with the {@link java.nio.charset.StandardCharsets#ISO_8859_1} charset *
  • {@link ResourceHttpMessageConverter} *
  • {@link ResourceRegionHttpMessageConverter} + *
  • A Kotlin Serialization converter *
  • A JSON converter *
  • A Smile converter *
  • A CBOR converter @@ -94,6 +96,14 @@ public interface HttpMessageConverters extends Iterable> */ T withStringConverter(HttpMessageConverter stringMessageConverter); + /** + * Override the default String {@code HttpMessageConverter} + * with any converter supporting the Kotlin Serialization conversion for JSON. + * @param kotlinSerializationConverter the converter instance to use + * @see org.springframework.http.converter.json.KotlinSerializationJsonHttpMessageConverter + */ + T withKotlinSerializationJsonConverter(HttpMessageConverter kotlinSerializationConverter); + /** * Override the default Jackson 3.x JSON {@code HttpMessageConverter} * with any converter supporting the JSON format. diff --git a/spring-web/src/test/java/org/springframework/http/converter/DefaultHttpMessageConvertersTests.java b/spring-web/src/test/java/org/springframework/http/converter/DefaultHttpMessageConvertersTests.java index eb6eb465e8b..1aef37b005a 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/DefaultHttpMessageConvertersTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/DefaultHttpMessageConvertersTests.java @@ -31,6 +31,7 @@ import org.springframework.http.converter.cbor.JacksonCborHttpMessageConverter; import org.springframework.http.converter.feed.AtomFeedHttpMessageConverter; import org.springframework.http.converter.feed.RssChannelHttpMessageConverter; import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter; +import org.springframework.http.converter.json.KotlinSerializationJsonHttpMessageConverter; import org.springframework.http.converter.protobuf.KotlinSerializationProtobufHttpMessageConverter; import org.springframework.http.converter.smile.JacksonSmileHttpMessageConverter; import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter; @@ -63,42 +64,42 @@ class DefaultHttpMessageConvertersTests { void failsWhenStringConverterDoesNotSupportMediaType() { assertThatIllegalArgumentException() .isThrownBy(() -> HttpMessageConverters.forClient().withStringConverter(new CustomHttpMessageConverter()).build()) - .withMessage("stringMessageConverter should support 'text/plain'"); + .withMessage("stringConverter should support 'text/plain'"); } @Test void failsWhenJsonConverterDoesNotSupportMediaType() { assertThatIllegalArgumentException() .isThrownBy(() -> HttpMessageConverters.forClient().withJsonConverter(new CustomHttpMessageConverter()).build()) - .withMessage("jsonMessageConverter should support 'application/json'"); + .withMessage("jsonConverter should support 'application/json'"); } @Test void failsWhenXmlConverterDoesNotSupportMediaType() { assertThatIllegalArgumentException() .isThrownBy(() -> HttpMessageConverters.forClient().withXmlConverter(new CustomHttpMessageConverter()).build()) - .withMessage("xmlMessageConverter should support 'text/xml'"); + .withMessage("xmlConverter should support 'text/xml'"); } @Test void failsWhenSmileConverterDoesNotSupportMediaType() { assertThatIllegalArgumentException() .isThrownBy(() -> HttpMessageConverters.forClient().withSmileConverter(new CustomHttpMessageConverter()).build()) - .withMessage("smileMessageConverter should support 'application/x-jackson-smile'"); + .withMessage("smileConverter should support 'application/x-jackson-smile'"); } @Test void failsWhenCborConverterDoesNotSupportMediaType() { assertThatIllegalArgumentException() .isThrownBy(() -> HttpMessageConverters.forClient().withCborConverter(new CustomHttpMessageConverter()).build()) - .withMessage("cborMessageConverter should support 'application/cbor'"); + .withMessage("cborConverter should support 'application/cbor'"); } @Test void failsWhenYamlConverterDoesNotSupportMediaType() { assertThatIllegalArgumentException() .isThrownBy(() -> HttpMessageConverters.forClient().withYamlConverter(new CustomHttpMessageConverter()).build()) - .withMessage("yamlMessageConverter should support 'application/yaml'"); + .withMessage("yamlConverter should support 'application/yaml'"); } @@ -110,7 +111,7 @@ class DefaultHttpMessageConvertersTests { var converters = HttpMessageConverters.forClient().registerDefaults().build(); assertThat(converters).hasExactlyElementsOfTypes(ByteArrayHttpMessageConverter.class, StringHttpMessageConverter.class, ResourceHttpMessageConverter.class, - AllEncompassingFormHttpMessageConverter.class, + AllEncompassingFormHttpMessageConverter.class, KotlinSerializationJsonHttpMessageConverter.class, JacksonJsonHttpMessageConverter.class, JacksonSmileHttpMessageConverter.class, JacksonCborHttpMessageConverter.class, JacksonYamlHttpMessageConverter.class, JacksonXmlHttpMessageConverter.class, KotlinSerializationProtobufHttpMessageConverter.class, @@ -124,11 +125,11 @@ class DefaultHttpMessageConvertersTests { assertThat(multipartConverter.getPartConverters()).hasExactlyElementsOfTypes( ByteArrayHttpMessageConverter.class, StringHttpMessageConverter.class, - ResourceHttpMessageConverter.class, JacksonJsonHttpMessageConverter.class, - JacksonSmileHttpMessageConverter.class, JacksonCborHttpMessageConverter.class, - JacksonYamlHttpMessageConverter.class, JacksonXmlHttpMessageConverter.class, - KotlinSerializationProtobufHttpMessageConverter.class, AtomFeedHttpMessageConverter.class, - RssChannelHttpMessageConverter.class); + ResourceHttpMessageConverter.class, KotlinSerializationJsonHttpMessageConverter.class, + JacksonJsonHttpMessageConverter.class, JacksonSmileHttpMessageConverter.class, + JacksonCborHttpMessageConverter.class, JacksonYamlHttpMessageConverter.class, + JacksonXmlHttpMessageConverter.class, KotlinSerializationProtobufHttpMessageConverter.class, + AtomFeedHttpMessageConverter.class, RssChannelHttpMessageConverter.class); } @Test @@ -145,7 +146,7 @@ class DefaultHttpMessageConvertersTests { assertThat(converters).hasExactlyElementsOfTypes( CustomHttpMessageConverter.class, ByteArrayHttpMessageConverter.class, StringHttpMessageConverter.class, ResourceHttpMessageConverter.class, - AllEncompassingFormHttpMessageConverter.class, + AllEncompassingFormHttpMessageConverter.class, KotlinSerializationJsonHttpMessageConverter.class, JacksonJsonHttpMessageConverter.class, JacksonSmileHttpMessageConverter.class, JacksonCborHttpMessageConverter.class, JacksonYamlHttpMessageConverter.class, JacksonXmlHttpMessageConverter.class, KotlinSerializationProtobufHttpMessageConverter.class, @@ -213,7 +214,7 @@ class DefaultHttpMessageConvertersTests { assertThat(converters).hasExactlyElementsOfTypes( ByteArrayHttpMessageConverter.class, StringHttpMessageConverter.class, ResourceHttpMessageConverter.class, ResourceRegionHttpMessageConverter.class, - AllEncompassingFormHttpMessageConverter.class, + AllEncompassingFormHttpMessageConverter.class, KotlinSerializationJsonHttpMessageConverter.class, JacksonJsonHttpMessageConverter.class, JacksonSmileHttpMessageConverter.class, JacksonCborHttpMessageConverter.class, JacksonYamlHttpMessageConverter.class, JacksonXmlHttpMessageConverter.class, KotlinSerializationProtobufHttpMessageConverter.class, @@ -227,11 +228,11 @@ class DefaultHttpMessageConvertersTests { assertThat(multipartConverter.getPartConverters()).hasExactlyElementsOfTypes( ByteArrayHttpMessageConverter.class, StringHttpMessageConverter.class, - ResourceHttpMessageConverter.class, JacksonJsonHttpMessageConverter.class, - JacksonSmileHttpMessageConverter.class, JacksonCborHttpMessageConverter.class, - JacksonYamlHttpMessageConverter.class, JacksonXmlHttpMessageConverter.class, - KotlinSerializationProtobufHttpMessageConverter.class, AtomFeedHttpMessageConverter.class, - RssChannelHttpMessageConverter.class); + ResourceHttpMessageConverter.class, KotlinSerializationJsonHttpMessageConverter.class, + JacksonJsonHttpMessageConverter.class, JacksonSmileHttpMessageConverter.class, + JacksonCborHttpMessageConverter.class, JacksonYamlHttpMessageConverter.class, + JacksonXmlHttpMessageConverter.class, KotlinSerializationProtobufHttpMessageConverter.class, + AtomFeedHttpMessageConverter.class, RssChannelHttpMessageConverter.class); } @Test @@ -249,7 +250,7 @@ class DefaultHttpMessageConvertersTests { CustomHttpMessageConverter.class, ByteArrayHttpMessageConverter.class, StringHttpMessageConverter.class, ResourceHttpMessageConverter.class, ResourceRegionHttpMessageConverter.class, - AllEncompassingFormHttpMessageConverter.class, + AllEncompassingFormHttpMessageConverter.class, KotlinSerializationJsonHttpMessageConverter.class, JacksonJsonHttpMessageConverter.class, JacksonSmileHttpMessageConverter.class, JacksonCborHttpMessageConverter.class, JacksonYamlHttpMessageConverter.class, JacksonXmlHttpMessageConverter.class, KotlinSerializationProtobufHttpMessageConverter.class, diff --git a/spring-web/src/test/java/org/springframework/http/converter/json/JacksonJsonHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/json/JacksonJsonHttpMessageConverterTests.java index 556c506c0a0..1854381d1a9 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/json/JacksonJsonHttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/json/JacksonJsonHttpMessageConverterTests.java @@ -605,6 +605,12 @@ class JacksonJsonHttpMessageConverterTests { assertThat(result).contains("\"string\":\"Foo\""); } + @Test + void readEmptyAsNull() throws IOException { + MockHttpInputMessage inputMessage = new MockHttpInputMessage("null".getBytes(StandardCharsets.UTF_8)); + inputMessage.getHeaders().setContentType(new MediaType("application", "json")); + assertThat(this.converter.read(Map.class, inputMessage)).isNull(); + } interface MyInterface { diff --git a/spring-web/src/test/java/org/springframework/web/client/RestClientIntegrationTests.java b/spring-web/src/test/java/org/springframework/web/client/RestClientIntegrationTests.java index 8ac257ccbac..e34c65e70bf 100644 --- a/spring-web/src/test/java/org/springframework/web/client/RestClientIntegrationTests.java +++ b/spring-web/src/test/java/org/springframework/web/client/RestClientIntegrationTests.java @@ -25,7 +25,6 @@ import java.lang.annotation.Target; import java.net.URI; import java.net.URISyntaxException; import java.util.List; -import java.util.Map; import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Stream; @@ -339,24 +338,6 @@ class RestClientIntegrationTests { }); } - @ParameterizedRestClientTest - @SuppressWarnings({ "rawtypes", "unchecked" }) - void retrieveJsonNull(ClientHttpRequestFactory requestFactory) throws IOException { - startServer(requestFactory); - - prepareResponse(builder -> builder - .code(200) - .setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) - .body("null")); - - Map result = this.restClient.get() - .uri("/null") - .retrieve() - .body(Map.class); - - assertThat(result).isNull(); - } - @ParameterizedRestClientTest void retrieveJsonEmpty(ClientHttpRequestFactory requestFactory) throws IOException { startServer(requestFactory); diff --git a/spring-web/src/test/java/org/springframework/web/client/RestTemplateTests.java b/spring-web/src/test/java/org/springframework/web/client/RestTemplateTests.java index 0e1ae35ce5b..241b8146497 100644 --- a/spring-web/src/test/java/org/springframework/web/client/RestTemplateTests.java +++ b/spring-web/src/test/java/org/springframework/web/client/RestTemplateTests.java @@ -53,8 +53,6 @@ import org.springframework.http.converter.GenericHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.SmartHttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; -import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter; -import org.springframework.http.converter.json.KotlinSerializationJsonHttpMessageConverter; import org.springframework.util.FileCopyUtils; import org.springframework.web.util.DefaultUriBuilderFactory; @@ -108,18 +106,6 @@ class RestTemplateTests { template.setErrorHandler(errorHandler); } - @Test - void defaultMessageConvertersWithoutKotlinSerialization() { - RestTemplate restTemplate = new RestTemplate(); - List> httpMessageConverters = restTemplate.getMessageConverters(); - assertThat(httpMessageConverters).extracting("class").containsOnlyOnce( - JacksonJsonHttpMessageConverter.class - ); - assertThat(httpMessageConverters).extracting("class").doesNotContain( - KotlinSerializationJsonHttpMessageConverter.class - ); - } - @Test void constructorPreconditions() { assertThatIllegalArgumentException()