Skip to content

Commit a6cf036

Browse files
committed
Revisit Kotlin Serialization integration
This commit removes the "kotlin-serialization" option from the `spring.http.converters.preferred-json-mapper` and configures the kotlin serialization http message converter ahead of the preferred JSON converter. This effectively makes Kotlin Serialization a converter that is considered first for JSON support, and then Jackson/Jsonb/Gson is considered as fallback. Closes gh-47178
1 parent 2dc80b4 commit a6cf036

File tree

5 files changed

+40
-98
lines changed

5 files changed

+40
-98
lines changed

module/spring-boot-http-converter/src/main/java/org/springframework/boot/http/converter/autoconfigure/GsonHttpMessageConvertersConfiguration.java

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ class GsonHttpMessageConvertersConfiguration {
4242

4343
@Configuration(proxyBeanMethods = false)
4444
@ConditionalOnBean(Gson.class)
45-
@Conditional(PreferGsonOrOtherJsonLibraryUnavailableCondition.class)
45+
@Conditional(PreferGsonOrJacksonAndJsonbUnavailableCondition.class)
4646
static class GsonHttpMessageConverterConfiguration {
4747

4848
@Bean
@@ -55,9 +55,9 @@ GsonHttpMessageConverter gsonHttpMessageConverter(Gson gson) {
5555

5656
}
5757

58-
private static class PreferGsonOrOtherJsonLibraryUnavailableCondition extends AnyNestedCondition {
58+
private static class PreferGsonOrJacksonAndJsonbUnavailableCondition extends AnyNestedCondition {
5959

60-
PreferGsonOrOtherJsonLibraryUnavailableCondition() {
60+
PreferGsonOrJacksonAndJsonbUnavailableCondition() {
6161
super(ConfigurationPhase.REGISTER_BEAN);
6262
}
6363

@@ -67,16 +67,16 @@ static class GsonPreferred {
6767

6868
}
6969

70-
@Conditional(OtherJsonLibrariesUnavailableCondition.class)
70+
@Conditional(JacksonAndJsonbUnavailableCondition.class)
7171
static class JacksonJsonbUnavailable {
7272

7373
}
7474

7575
}
7676

77-
private static class OtherJsonLibrariesUnavailableCondition extends NoneNestedConditions {
77+
private static class JacksonAndJsonbUnavailableCondition extends NoneNestedConditions {
7878

79-
OtherJsonLibrariesUnavailableCondition() {
79+
JacksonAndJsonbUnavailableCondition() {
8080
super(ConfigurationPhase.REGISTER_BEAN);
8181
}
8282

@@ -91,12 +91,6 @@ static class JsonbPreferred {
9191

9292
}
9393

94-
@ConditionalOnProperty(name = HttpMessageConvertersAutoConfiguration.PREFERRED_MAPPER_PROPERTY,
95-
havingValue = "kotlin-serialization")
96-
static class KotlinSerializationPreferred {
97-
98-
}
99-
10094
}
10195

10296
}

module/spring-boot-http-converter/src/main/java/org/springframework/boot/http/converter/autoconfigure/JsonbHttpMessageConvertersConfiguration.java

Lines changed: 5 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
2424
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
2525
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
26-
import org.springframework.boot.autoconfigure.condition.NoneNestedConditions;
2726
import org.springframework.context.annotation.Bean;
2827
import org.springframework.context.annotation.Conditional;
2928
import org.springframework.context.annotation.Configuration;
@@ -42,7 +41,7 @@ class JsonbHttpMessageConvertersConfiguration {
4241

4342
@Configuration(proxyBeanMethods = false)
4443
@ConditionalOnBean(Jsonb.class)
45-
@Conditional(PreferJsonbOrOtherJsonLibrariesMissingCondition.class)
44+
@Conditional(PreferJsonbOrMissingJacksonAndGsonCondition.class)
4645
static class JsonbHttpMessageConverterConfiguration {
4746

4847
@Bean
@@ -55,9 +54,9 @@ JsonbHttpMessageConverter jsonbHttpMessageConverter(Jsonb jsonb) {
5554

5655
}
5756

58-
private static class PreferJsonbOrOtherJsonLibrariesMissingCondition extends AnyNestedCondition {
57+
private static class PreferJsonbOrMissingJacksonAndGsonCondition extends AnyNestedCondition {
5958

60-
PreferJsonbOrOtherJsonLibrariesMissingCondition() {
59+
PreferJsonbOrMissingJacksonAndGsonCondition() {
6160
super(ConfigurationPhase.REGISTER_BEAN);
6261
}
6362

@@ -67,32 +66,8 @@ static class JsonbPreferred {
6766

6867
}
6968

70-
@Conditional(OtherJsonLibrariesMissingMissingCondition.class)
71-
static class OtherJsonLibrariesMissingMissing {
72-
73-
}
74-
75-
}
76-
77-
private static class OtherJsonLibrariesMissingMissingCondition extends NoneNestedConditions {
78-
79-
OtherJsonLibrariesMissingMissingCondition() {
80-
super(ConfigurationPhase.REGISTER_BEAN);
81-
}
82-
83-
@ConditionalOnBean(JacksonJsonHttpMessageConverter.class)
84-
static class JacksonAvailable {
85-
86-
}
87-
88-
@ConditionalOnBean(GsonHttpMessageConverter.class)
89-
static class GsonAvailable {
90-
91-
}
92-
93-
@ConditionalOnProperty(name = HttpMessageConvertersAutoConfiguration.PREFERRED_MAPPER_PROPERTY,
94-
havingValue = "kotlin-serialization")
95-
static class KotlinPreferred {
69+
@ConditionalOnMissingBean({ JacksonJsonHttpMessageConverter.class, GsonHttpMessageConverter.class })
70+
static class JacksonAndGsonMissing {
9671

9772
}
9873

module/spring-boot-http-converter/src/main/java/org/springframework/boot/http/converter/autoconfigure/KotlinSerializationHttpMessageConvertersConfiguration.java

Lines changed: 10 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -16,61 +16,33 @@
1616

1717
package org.springframework.boot.http.converter.autoconfigure;
1818

19+
import kotlinx.serialization.Serializable;
1920
import kotlinx.serialization.json.Json;
2021

21-
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
2222
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
2323
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
2424
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
25-
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
2625
import org.springframework.context.annotation.Bean;
27-
import org.springframework.context.annotation.Conditional;
2826
import org.springframework.context.annotation.Configuration;
29-
import org.springframework.http.converter.json.GsonHttpMessageConverter;
30-
import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter;
31-
import org.springframework.http.converter.json.JsonbHttpMessageConverter;
27+
import org.springframework.core.annotation.Order;
3228
import org.springframework.http.converter.json.KotlinSerializationJsonHttpMessageConverter;
3329

3430
/**
3531
* Configuration for HTTP message converters that use Kotlin Serialization.
3632
*
33+
* @author Brian Clozel
3734
* @author Dmitry Sulman
3835
*/
3936
@Configuration(proxyBeanMethods = false)
40-
@ConditionalOnClass(Json.class)
37+
@ConditionalOnClass({ Serializable.class, Json.class })
38+
@ConditionalOnBean(Json.class)
4139
class KotlinSerializationHttpMessageConvertersConfiguration {
4240

43-
@Configuration(proxyBeanMethods = false)
44-
@ConditionalOnBean(Json.class)
45-
@Conditional(PreferKotlinSerializationOrOtherJsonLibrariesUnavailableCondition.class)
46-
static class KotlinSerializationHttpMessageConverterConfiguration {
47-
48-
@Bean
49-
@ConditionalOnMissingBean
50-
KotlinSerializationJsonHttpMessageConverter kotlinSerializationJsonHttpMessageConverter(Json json) {
51-
return new KotlinSerializationJsonHttpMessageConverter(json);
52-
}
53-
54-
}
55-
56-
private static class PreferKotlinSerializationOrOtherJsonLibrariesUnavailableCondition extends AnyNestedCondition {
57-
58-
PreferKotlinSerializationOrOtherJsonLibrariesUnavailableCondition() {
59-
super(ConfigurationPhase.REGISTER_BEAN);
60-
}
61-
62-
@ConditionalOnProperty(name = HttpMessageConvertersAutoConfiguration.PREFERRED_MAPPER_PROPERTY,
63-
havingValue = "kotlin-serialization")
64-
static class KotlinSerializationPreferred {
65-
66-
}
67-
68-
@ConditionalOnMissingBean({ JacksonJsonHttpMessageConverter.class, JsonbHttpMessageConverter.class,
69-
GsonHttpMessageConverter.class })
70-
static class OtherJsonLibrariesUnavailable {
71-
72-
}
73-
41+
@Bean
42+
@ConditionalOnMissingBean
43+
@Order(-10) // configured ahead of JSON mappers
44+
KotlinSerializationJsonHttpMessageConverter kotlinSerializationJsonHttpMessageConverter(Json json) {
45+
return new KotlinSerializationJsonHttpMessageConverter(json);
7446
}
7547

7648
}

module/spring-boot-http-converter/src/main/resources/META-INF/additional-spring-configuration-metadata.json

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,6 @@
1919
},
2020
{
2121
"value": "jsonb"
22-
},
23-
{
24-
"value": "kotlin-serialization"
2522
}
2623
],
2724
"providers": [

module/spring-boot-http-converter/src/test/java/org/springframework/boot/http/converter/autoconfigure/HttpMessageConvertersAutoConfigurationTests.java

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.boot.http.converter.autoconfigure;
1818

1919
import java.nio.charset.StandardCharsets;
20+
import java.util.List;
2021

2122
import com.google.gson.Gson;
2223
import jakarta.json.bind.Jsonb;
@@ -131,7 +132,6 @@ void gsonCanBePreferred() {
131132
assertConverterBeanRegisteredWithHttpMessageConverters(context, GsonHttpMessageConverter.class);
132133
assertThat(context).doesNotHaveBean(JsonbHttpMessageConverter.class);
133134
assertThat(context).doesNotHaveBean(JacksonJsonHttpMessageConverter.class);
134-
assertThat(context).doesNotHaveBean(KotlinSerializationJsonHttpMessageConverter.class);
135135
});
136136
}
137137

@@ -163,7 +163,6 @@ void jsonbCanBePreferred() {
163163
assertConverterBeanRegisteredWithHttpMessageConverters(context, JsonbHttpMessageConverter.class);
164164
assertThat(context).doesNotHaveBean(GsonHttpMessageConverter.class);
165165
assertThat(context).doesNotHaveBean(JacksonJsonHttpMessageConverter.class);
166-
assertThat(context).doesNotHaveBean(KotlinSerializationJsonHttpMessageConverter.class);
167166
});
168167
}
169168

@@ -184,17 +183,15 @@ void kotlinSerializationCustomConverter() {
184183
}
185184

186185
@Test
187-
void kotlinSerializationCanBePreferred() {
188-
allOptionsRunner().withPropertyValues("spring.http.converters.preferred-json-mapper:kotlin-serialization")
189-
.run((context) -> {
190-
assertConverterBeanExists(context, KotlinSerializationJsonHttpMessageConverter.class,
191-
"kotlinSerializationJsonHttpMessageConverter");
192-
assertConverterBeanRegisteredWithHttpMessageConverters(context,
193-
KotlinSerializationJsonHttpMessageConverter.class);
194-
assertThat(context).doesNotHaveBean(GsonHttpMessageConverter.class);
195-
assertThat(context).doesNotHaveBean(JsonbHttpMessageConverter.class);
196-
assertThat(context).doesNotHaveBean(JacksonJsonHttpMessageConverter.class);
197-
});
186+
void kotlinSerializationOrderedAheadOfJsonConverter() {
187+
allOptionsRunner().run((context) -> {
188+
assertConverterBeanExists(context, KotlinSerializationJsonHttpMessageConverter.class,
189+
"kotlinSerializationJsonHttpMessageConverter");
190+
assertConverterBeanRegisteredWithHttpMessageConverters(context,
191+
KotlinSerializationJsonHttpMessageConverter.class);
192+
assertConvertersBeanRegisteredWithHttpMessageConverters(context,
193+
List.of(KotlinSerializationJsonHttpMessageConverter.class, JacksonJsonHttpMessageConverter.class));
194+
});
198195
}
199196

200197
@Test
@@ -240,7 +237,6 @@ void jacksonIsPreferredByDefault() {
240237
assertConverterBeanRegisteredWithHttpMessageConverters(context, JacksonJsonHttpMessageConverter.class);
241238
assertThat(context).doesNotHaveBean(GsonHttpMessageConverter.class);
242239
assertThat(context).doesNotHaveBean(JsonbHttpMessageConverter.class);
243-
assertThat(context).doesNotHaveBean(KotlinSerializationJsonHttpMessageConverter.class);
244240
});
245241
}
246242

@@ -251,7 +247,6 @@ void gsonIsPreferredIfJacksonIsNotAvailable() {
251247
assertConverterBeanExists(context, GsonHttpMessageConverter.class, "gsonHttpMessageConverter");
252248
assertConverterBeanRegisteredWithHttpMessageConverters(context, GsonHttpMessageConverter.class);
253249
assertThat(context).doesNotHaveBean(JsonbHttpMessageConverter.class);
254-
assertThat(context).doesNotHaveBean(KotlinSerializationJsonHttpMessageConverter.class);
255250
});
256251
}
257252

@@ -327,6 +322,15 @@ private void assertConverterBeanRegisteredWithHttpMessageConverters(AssertableAp
327322
assertThat(converters.getConverters()).contains(converter);
328323
}
329324

325+
private void assertConvertersBeanRegisteredWithHttpMessageConverters(AssertableApplicationContext context,
326+
List<Class<? extends HttpMessageConverter<?>>> types) {
327+
328+
List<? extends HttpMessageConverter<?>> converterInstances = types.stream().map(context::getBean).toList();
329+
330+
HttpMessageConverters converters = context.getBean(HttpMessageConverters.class);
331+
assertThat(converters.getConverters()).containsSubsequence(converterInstances);
332+
}
333+
330334
@Configuration(proxyBeanMethods = false)
331335
static class JacksonJsonMapperConfig {
332336

0 commit comments

Comments
 (0)