Change default converters ordering

This commit adds a new withKotlinSerializationCborConverter
method to HttpMessageConverters and updates DefaultHttpMessageConverters
to put JSON and CBOR Kotlin Serialization converters before
their Jackson/GSON/JSONB counterparts with their new default
behavior that only handles classes with `@Serializable` at
type or generics level.

When there is no alternative converter for the same mime type,
Kotlin Serialization converters handle all supported cases.

Closes gh-35761
This commit is contained in:
Sébastien Deleuze
2025-11-10 11:36:00 +01:00
parent d64edc01c0
commit 816cee12e5
3 changed files with 78 additions and 25 deletions
@@ -50,6 +50,7 @@ import org.springframework.util.ClassUtils;
* Default implementation for {@link HttpMessageConverters}.
*
* @author Brian Clozel
* @author Sebastien Deleuze
*/
@SuppressWarnings("removal")
class DefaultHttpMessageConverters implements HttpMessageConverters {
@@ -122,6 +123,8 @@ class DefaultHttpMessageConverters implements HttpMessageConverters {
@Nullable HttpMessageConverter<?> smileConverter;
@Nullable HttpMessageConverter<?> kotlinCborConverter;
@Nullable HttpMessageConverter<?> cborConverter;
@Nullable HttpMessageConverter<?> yamlConverter;
@@ -187,6 +190,11 @@ class DefaultHttpMessageConverters implements HttpMessageConverters {
this.smileConverter = smileConverter;
}
void setKotlinSerializationCborConverter(HttpMessageConverter<?> kotlinCborConverter) {
Assert.notNull(kotlinCborConverter, "kotlinCborConverter must not be null");
this.kotlinCborConverter = kotlinCborConverter;
}
void setCborConverter(HttpMessageConverter<?> cborConverter) {
Assert.isTrue(cborConverter.getSupportedMediaTypes().contains(MediaType.APPLICATION_CBOR),
"cborConverter should support 'application/cbor'");
@@ -230,6 +238,9 @@ class DefaultHttpMessageConverters implements HttpMessageConverters {
if (this.smileConverter != null) {
converters.add(this.smileConverter);
}
if (this.kotlinCborConverter != null) {
converters.add(this.kotlinCborConverter);
}
if (this.cborConverter != null) {
converters.add(this.cborConverter);
}
@@ -263,7 +274,12 @@ class DefaultHttpMessageConverters implements HttpMessageConverters {
}
if (this.kotlinJsonConverter == null) {
if (KOTLIN_SERIALIZATION_JSON_PRESENT) {
this.kotlinJsonConverter = new KotlinSerializationJsonHttpMessageConverter();
if (this.jsonConverter != null || JACKSON_PRESENT || JACKSON_2_PRESENT || GSON_PRESENT || JSONB_PRESENT) {
this.kotlinJsonConverter = new KotlinSerializationJsonHttpMessageConverter();
}
else {
this.kotlinJsonConverter = new KotlinSerializationJsonHttpMessageConverter(type -> true);
}
}
}
if (this.jsonConverter == null) {
@@ -302,6 +318,16 @@ class DefaultHttpMessageConverters implements HttpMessageConverters {
}
}
if (this.kotlinCborConverter == null) {
if (KOTLIN_SERIALIZATION_CBOR_PRESENT) {
if (this.cborConverter != null || JACKSON_CBOR_PRESENT || JACKSON_2_CBOR_PRESENT) {
this.kotlinCborConverter = new KotlinSerializationCborHttpMessageConverter();
}
else {
this.kotlinCborConverter = new KotlinSerializationCborHttpMessageConverter(type -> true);
}
}
}
if (this.cborConverter == null) {
if (JACKSON_CBOR_PRESENT) {
this.cborConverter = new JacksonCborHttpMessageConverter();
@@ -309,9 +335,6 @@ class DefaultHttpMessageConverters implements HttpMessageConverters {
else if (JACKSON_2_CBOR_PRESENT) {
this.cborConverter = new MappingJackson2CborHttpMessageConverter();
}
else if (KOTLIN_SERIALIZATION_CBOR_PRESENT) {
this.cborConverter = new KotlinSerializationCborHttpMessageConverter();
}
}
if (this.yamlConverter == null) {
@@ -325,7 +348,7 @@ class DefaultHttpMessageConverters implements HttpMessageConverters {
if (this.protobufConverter == null) {
if (KOTLIN_SERIALIZATION_PROTOBUF_PRESENT) {
this.protobufConverter = new KotlinSerializationProtobufHttpMessageConverter();
this.protobufConverter = new KotlinSerializationProtobufHttpMessageConverter(type -> true);
}
}
@@ -379,6 +402,12 @@ class DefaultHttpMessageConverters implements HttpMessageConverters {
return this;
}
@Override
public ClientBuilder withKotlinSerializationCborConverter(HttpMessageConverter<?> kotlinSerializationCborConverter) {
setKotlinSerializationCborConverter(kotlinSerializationCborConverter);
return this;
}
@Override
public ClientBuilder withCborConverter(HttpMessageConverter<?> cborConverter) {
setCborConverter(cborConverter);
@@ -470,6 +499,12 @@ class DefaultHttpMessageConverters implements HttpMessageConverters {
return this;
}
@Override
public ServerBuilder withKotlinSerializationCborConverter(HttpMessageConverter<?> kotlinSerializationCborConverter) {
setKotlinSerializationCborConverter(kotlinSerializationCborConverter);
return this;
}
@Override
public ServerBuilder withCborConverter(HttpMessageConverter<?> cborConverter) {
setCborConverter(cborConverter);
@@ -27,6 +27,7 @@ import java.util.function.Consumer;
* Finally, {@link Builder#configureMessageConverters(Consumer) default and custom converters can be configured}.
*
* @author Brian Clozel
* @author Sebastien Deleuze
* @since 7.0
*/
public interface HttpMessageConverters extends Iterable<HttpMessageConverter<?>> {
@@ -40,9 +41,10 @@ public interface HttpMessageConverters extends Iterable<HttpMessageConverter<?>>
* <li>{@link StringHttpMessageConverter} with the {@link java.nio.charset.StandardCharsets#ISO_8859_1} charset
* <li>{@link ResourceHttpMessageConverter}, with resource streaming support disabled
* <li>a Multipart converter, using all detected and custom converters for part conversion
* <li>A Kotlin Serialization converter
* <li>A Kotlin Serialization JSON converter
* <li>A JSON converter
* <li>A Smile converter
* <li>A Kotlin Serialization CBOR converter
* <li>A CBOR converter
* <li>A YAML converter
* <li>An XML converter
@@ -63,9 +65,10 @@ public interface HttpMessageConverters extends Iterable<HttpMessageConverter<?>>
* <li>{@link StringHttpMessageConverter} with the {@link java.nio.charset.StandardCharsets#ISO_8859_1} charset
* <li>{@link ResourceHttpMessageConverter}
* <li>{@link ResourceRegionHttpMessageConverter}
* <li>A Kotlin Serialization converter
* <li>A Kotlin Serialization JSON converter
* <li>A JSON converter
* <li>A Smile converter
* <li>A Kotlin Serialization CBOR converter
* <li>A CBOR converter
* <li>A YAML converter
* <li>An XML converter
@@ -128,6 +131,14 @@ public interface HttpMessageConverters extends Iterable<HttpMessageConverter<?>>
*/
T withSmileConverter(HttpMessageConverter<?> smileMessageConverter);
/**
* Override the default String {@code HttpMessageConverter}
* with any converter supporting the Kotlin Serialization conversion for CBOR.
* @param kotlinSerializationConverter the converter instance to use
* @see org.springframework.http.converter.cbor.KotlinSerializationCborHttpMessageConverter
*/
T withKotlinSerializationCborConverter(HttpMessageConverter<?> kotlinSerializationConverter);
/**
* Override the default Jackson 3.x CBOR {@code HttpMessageConverter}
* with any converter supporting the CBOR format.
@@ -28,6 +28,7 @@ import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.converter.cbor.JacksonCborHttpMessageConverter;
import org.springframework.http.converter.cbor.KotlinSerializationCborHttpMessageConverter;
import org.springframework.http.converter.feed.AtomFeedHttpMessageConverter;
import org.springframework.http.converter.feed.RssChannelHttpMessageConverter;
import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter;
@@ -113,9 +114,10 @@ class DefaultHttpMessageConvertersTests {
StringHttpMessageConverter.class, ResourceHttpMessageConverter.class,
AllEncompassingFormHttpMessageConverter.class, KotlinSerializationJsonHttpMessageConverter.class,
JacksonJsonHttpMessageConverter.class, JacksonSmileHttpMessageConverter.class,
JacksonCborHttpMessageConverter.class, JacksonYamlHttpMessageConverter.class,
JacksonXmlHttpMessageConverter.class, KotlinSerializationProtobufHttpMessageConverter.class,
AtomFeedHttpMessageConverter.class, RssChannelHttpMessageConverter.class);
KotlinSerializationCborHttpMessageConverter.class, JacksonCborHttpMessageConverter.class,
JacksonYamlHttpMessageConverter.class, JacksonXmlHttpMessageConverter.class,
KotlinSerializationProtobufHttpMessageConverter.class, AtomFeedHttpMessageConverter.class,
RssChannelHttpMessageConverter.class);
}
@Test
@@ -127,9 +129,10 @@ class DefaultHttpMessageConvertersTests {
ByteArrayHttpMessageConverter.class, StringHttpMessageConverter.class,
ResourceHttpMessageConverter.class, KotlinSerializationJsonHttpMessageConverter.class,
JacksonJsonHttpMessageConverter.class, JacksonSmileHttpMessageConverter.class,
JacksonCborHttpMessageConverter.class, JacksonYamlHttpMessageConverter.class,
JacksonXmlHttpMessageConverter.class, KotlinSerializationProtobufHttpMessageConverter.class,
AtomFeedHttpMessageConverter.class, RssChannelHttpMessageConverter.class);
KotlinSerializationCborHttpMessageConverter.class, JacksonCborHttpMessageConverter.class,
JacksonYamlHttpMessageConverter.class, JacksonXmlHttpMessageConverter.class,
KotlinSerializationProtobufHttpMessageConverter.class, AtomFeedHttpMessageConverter.class,
RssChannelHttpMessageConverter.class);
}
@Test
@@ -148,9 +151,10 @@ class DefaultHttpMessageConvertersTests {
StringHttpMessageConverter.class, ResourceHttpMessageConverter.class,
AllEncompassingFormHttpMessageConverter.class, KotlinSerializationJsonHttpMessageConverter.class,
JacksonJsonHttpMessageConverter.class, JacksonSmileHttpMessageConverter.class,
JacksonCborHttpMessageConverter.class, JacksonYamlHttpMessageConverter.class,
JacksonXmlHttpMessageConverter.class, KotlinSerializationProtobufHttpMessageConverter.class,
AtomFeedHttpMessageConverter.class, RssChannelHttpMessageConverter.class);
KotlinSerializationCborHttpMessageConverter.class, JacksonCborHttpMessageConverter.class,
JacksonYamlHttpMessageConverter.class, JacksonXmlHttpMessageConverter.class,
KotlinSerializationProtobufHttpMessageConverter.class, AtomFeedHttpMessageConverter.class,
RssChannelHttpMessageConverter.class);
}
@Test
@@ -216,9 +220,10 @@ class DefaultHttpMessageConvertersTests {
ResourceHttpMessageConverter.class, ResourceRegionHttpMessageConverter.class,
AllEncompassingFormHttpMessageConverter.class, KotlinSerializationJsonHttpMessageConverter.class,
JacksonJsonHttpMessageConverter.class, JacksonSmileHttpMessageConverter.class,
JacksonCborHttpMessageConverter.class, JacksonYamlHttpMessageConverter.class,
JacksonXmlHttpMessageConverter.class, KotlinSerializationProtobufHttpMessageConverter.class,
AtomFeedHttpMessageConverter.class, RssChannelHttpMessageConverter.class);
KotlinSerializationCborHttpMessageConverter.class, JacksonCborHttpMessageConverter.class,
JacksonYamlHttpMessageConverter.class, JacksonXmlHttpMessageConverter.class,
KotlinSerializationProtobufHttpMessageConverter.class, AtomFeedHttpMessageConverter.class,
RssChannelHttpMessageConverter.class);
}
@Test
@@ -230,9 +235,10 @@ class DefaultHttpMessageConvertersTests {
ByteArrayHttpMessageConverter.class, StringHttpMessageConverter.class,
ResourceHttpMessageConverter.class, KotlinSerializationJsonHttpMessageConverter.class,
JacksonJsonHttpMessageConverter.class, JacksonSmileHttpMessageConverter.class,
JacksonCborHttpMessageConverter.class, JacksonYamlHttpMessageConverter.class,
JacksonXmlHttpMessageConverter.class, KotlinSerializationProtobufHttpMessageConverter.class,
AtomFeedHttpMessageConverter.class, RssChannelHttpMessageConverter.class);
KotlinSerializationCborHttpMessageConverter.class, JacksonCborHttpMessageConverter.class,
JacksonYamlHttpMessageConverter.class, JacksonXmlHttpMessageConverter.class,
KotlinSerializationProtobufHttpMessageConverter.class, AtomFeedHttpMessageConverter.class,
RssChannelHttpMessageConverter.class);
}
@Test
@@ -252,9 +258,10 @@ class DefaultHttpMessageConvertersTests {
ResourceHttpMessageConverter.class, ResourceRegionHttpMessageConverter.class,
AllEncompassingFormHttpMessageConverter.class, KotlinSerializationJsonHttpMessageConverter.class,
JacksonJsonHttpMessageConverter.class, JacksonSmileHttpMessageConverter.class,
JacksonCborHttpMessageConverter.class, JacksonYamlHttpMessageConverter.class,
JacksonXmlHttpMessageConverter.class, KotlinSerializationProtobufHttpMessageConverter.class,
AtomFeedHttpMessageConverter.class, RssChannelHttpMessageConverter.class);
KotlinSerializationCborHttpMessageConverter.class, JacksonCborHttpMessageConverter.class,
JacksonYamlHttpMessageConverter.class, JacksonXmlHttpMessageConverter.class,
KotlinSerializationProtobufHttpMessageConverter.class, AtomFeedHttpMessageConverter.class,
RssChannelHttpMessageConverter.class);
}
@Test