Skip to content

Commit c604eae

Browse files
committed
Move spring.mvc.converters.preferred-json-mapper to spring.http
This commit deprecates spring.mvc.converters.preferred-json-mapper and replaces it with spring.http.converters.preferred-json-mapper. If both properties are specified, the latter takes precedence. Closes gh-44925
1 parent f039c73 commit c604eae

File tree

8 files changed

+209
-28
lines changed

8 files changed

+209
-28
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright 2012-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.http;
18+
19+
import java.lang.annotation.Documented;
20+
import java.lang.annotation.ElementType;
21+
import java.lang.annotation.Retention;
22+
import java.lang.annotation.RetentionPolicy;
23+
import java.lang.annotation.Target;
24+
25+
import org.springframework.context.annotation.Conditional;
26+
27+
/**
28+
* {@link Conditional @Conditional} that matches based on the preferred JSON mapper. A
29+
* preference is expressed using the {@code spring.http.converters.preferred-json-mapper}
30+
* configuration property, falling back to the
31+
* {@code spring.mvc.converters.preferred-json-mapper} configuration property. When no
32+
* preference is expressed Jackson is preferred by default.
33+
*
34+
* @author Andy Wilkinson
35+
*/
36+
@Conditional(OnPreferredJsonMapperCondition.class)
37+
@Retention(RetentionPolicy.RUNTIME)
38+
@Target({ ElementType.TYPE, ElementType.METHOD })
39+
@Documented
40+
@interface ConditionalOnPreferredJsonMapper {
41+
42+
JsonMapper value();
43+
44+
enum JsonMapper {
45+
46+
GSON,
47+
48+
JACKSON,
49+
50+
JSONB,
51+
52+
}
53+
54+
}

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

+4-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -22,8 +22,8 @@
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.boot.autoconfigure.condition.NoneNestedConditions;
26+
import org.springframework.boot.autoconfigure.http.ConditionalOnPreferredJsonMapper.JsonMapper;
2727
import org.springframework.context.annotation.Bean;
2828
import org.springframework.context.annotation.Conditional;
2929
import org.springframework.context.annotation.Configuration;
@@ -61,8 +61,7 @@ private static class PreferGsonOrJacksonAndJsonbUnavailableCondition extends Any
6161
super(ConfigurationPhase.REGISTER_BEAN);
6262
}
6363

64-
@ConditionalOnProperty(name = HttpMessageConvertersAutoConfiguration.PREFERRED_MAPPER_PROPERTY,
65-
havingValue = "gson")
64+
@ConditionalOnPreferredJsonMapper(JsonMapper.GSON)
6665
static class GsonPreferred {
6766

6867
}
@@ -85,8 +84,7 @@ static class JacksonAvailable {
8584

8685
}
8786

88-
@ConditionalOnProperty(name = HttpMessageConvertersAutoConfiguration.PREFERRED_MAPPER_PROPERTY,
89-
havingValue = "jsonb")
87+
@ConditionalOnPreferredJsonMapper(JsonMapper.JSONB)
9088
static class JsonbPreferred {
9189

9290
}

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/HttpMessageConvertersAutoConfiguration.java

-2
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,6 @@
6666
@ImportRuntimeHints(HttpMessageConvertersAutoConfigurationRuntimeHints.class)
6767
public class HttpMessageConvertersAutoConfiguration {
6868

69-
static final String PREFERRED_MAPPER_PROPERTY = "spring.mvc.converters.preferred-json-mapper";
70-
7169
@Bean
7270
@ConditionalOnMissingBean
7371
public HttpMessageConverters messageConverters(ObjectProvider<HttpMessageConverter<?>> converters) {

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/JacksonHttpMessageConvertersConfiguration.java

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -22,7 +22,7 @@
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;
25+
import org.springframework.boot.autoconfigure.http.ConditionalOnPreferredJsonMapper.JsonMapper;
2626
import org.springframework.context.annotation.Bean;
2727
import org.springframework.context.annotation.Configuration;
2828
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
@@ -40,8 +40,7 @@ class JacksonHttpMessageConvertersConfiguration {
4040
@Configuration(proxyBeanMethods = false)
4141
@ConditionalOnClass(ObjectMapper.class)
4242
@ConditionalOnBean(ObjectMapper.class)
43-
@ConditionalOnProperty(name = HttpMessageConvertersAutoConfiguration.PREFERRED_MAPPER_PROPERTY,
44-
havingValue = "jackson", matchIfMissing = true)
43+
@ConditionalOnPreferredJsonMapper(JsonMapper.JACKSON)
4544
static class MappingJackson2HttpMessageConverterConfiguration {
4645

4746
@Bean

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

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2021 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -22,7 +22,7 @@
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;
25+
import org.springframework.boot.autoconfigure.http.ConditionalOnPreferredJsonMapper.JsonMapper;
2626
import org.springframework.context.annotation.Bean;
2727
import org.springframework.context.annotation.Conditional;
2828
import org.springframework.context.annotation.Configuration;
@@ -60,8 +60,7 @@ private static class PreferJsonbOrMissingJacksonAndGsonCondition extends AnyNest
6060
super(ConfigurationPhase.REGISTER_BEAN);
6161
}
6262

63-
@ConditionalOnProperty(name = HttpMessageConvertersAutoConfiguration.PREFERRED_MAPPER_PROPERTY,
64-
havingValue = "jsonb")
63+
@ConditionalOnPreferredJsonMapper(JsonMapper.JSONB)
6564
static class JsonbPreferred {
6665

6766
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Copyright 2012-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.http;
18+
19+
import java.util.Locale;
20+
21+
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
22+
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
23+
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
24+
import org.springframework.boot.autoconfigure.http.ConditionalOnPreferredJsonMapper.JsonMapper;
25+
import org.springframework.context.annotation.ConditionContext;
26+
import org.springframework.core.env.Environment;
27+
import org.springframework.core.type.AnnotatedTypeMetadata;
28+
29+
/**
30+
* {@link SpringBootCondition} for
31+
* {@link ConditionalOnPreferredJsonMapper @ConditionalOnPreferredJsonMapper}.
32+
*
33+
* @author Andy Wilkinson
34+
*/
35+
class OnPreferredJsonMapperCondition extends SpringBootCondition {
36+
37+
private static final String PREFERRED_MAPPER_PROPERTY = "spring.http.converters.preferred-json-mapper";
38+
39+
@Deprecated(since = "3.5.0", forRemoval = true)
40+
private static final String DEPRECATED_PREFERRED_MAPPER_PROPERTY = "spring.mvc.converters.preferred-json-mapper";
41+
42+
@Override
43+
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
44+
JsonMapper conditionMapper = metadata.getAnnotations()
45+
.get(ConditionalOnPreferredJsonMapper.class)
46+
.getEnum("value", JsonMapper.class);
47+
ConditionOutcome outcome = getMatchOutcome(context.getEnvironment(), PREFERRED_MAPPER_PROPERTY,
48+
conditionMapper);
49+
if (outcome != null) {
50+
return outcome;
51+
}
52+
outcome = getMatchOutcome(context.getEnvironment(), DEPRECATED_PREFERRED_MAPPER_PROPERTY, conditionMapper);
53+
if (outcome != null) {
54+
return outcome;
55+
}
56+
ConditionMessage message = ConditionMessage
57+
.forCondition(ConditionalOnPreferredJsonMapper.class, conditionMapper.name())
58+
.because("no property was configured and Jackson is the default");
59+
return (conditionMapper == JsonMapper.JACKSON) ? ConditionOutcome.match(message)
60+
: ConditionOutcome.noMatch(message);
61+
}
62+
63+
private ConditionOutcome getMatchOutcome(Environment environment, String key, JsonMapper conditionMapper) {
64+
String property = environment.getProperty(key);
65+
if (property == null) {
66+
return null;
67+
}
68+
JsonMapper configuredMapper = JsonMapper.valueOf(property.toUpperCase(Locale.ROOT));
69+
ConditionMessage message = ConditionMessage
70+
.forCondition(ConditionalOnPreferredJsonMapper.class, configuredMapper.name())
71+
.because("property '%s' had the value '%s'".formatted(key, property));
72+
return (configuredMapper == conditionMapper) ? ConditionOutcome.match(message)
73+
: ConditionOutcome.noMatch(message);
74+
}
75+
76+
}

spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json

+7-6
Original file line numberDiff line numberDiff line change
@@ -1615,11 +1615,8 @@
16151615
{
16161616
"name": "spring.http.converters.preferred-json-mapper",
16171617
"type": "java.lang.String",
1618-
"description": "Preferred JSON mapper to use for HTTP message conversion. By default, auto-detected according to the environment.",
1619-
"deprecation": {
1620-
"replacement": "spring.mvc.converters.preferred-json-mapper",
1621-
"level": "error"
1622-
}
1618+
"defaultValue": "jackson",
1619+
"description": "Preferred JSON mapper to use for HTTP message conversion. By default, auto-detected according to the environment. Supported values are 'jackson', 'gson', and 'jsonb'. When other json mapping libraries (such as kotlinx.serialization) are present, use a custom HttpMessageConverters bean to control the preferred mapper."
16231620
},
16241621
{
16251622
"name": "spring.http.encoding.charset",
@@ -2107,7 +2104,11 @@
21072104
"name": "spring.mvc.converters.preferred-json-mapper",
21082105
"type": "java.lang.String",
21092106
"defaultValue": "jackson",
2110-
"description": "Preferred JSON mapper to use for HTTP message conversion. By default, auto-detected according to the environment. Supported values are 'jackson', 'gson', and 'jsonb'. When other json mapping libraries (such as kotlinx.serialization) are present, use a custom HttpMessageConverters bean to control the preferred mapper."
2107+
"description": "Preferred JSON mapper to use for HTTP message conversion. By default, auto-detected according to the environment. Supported values are 'jackson', 'gson', and 'jsonb'. When other json mapping libraries (such as kotlinx.serialization) are present, use a custom HttpMessageConverters bean to control the preferred mapper.",
2108+
"deprecation": {
2109+
"replacement": "spring.http.converters.preferred-json-mapper",
2110+
"level": "error"
2111+
}
21112112
},
21122113
{
21132114
"name": "spring.mvc.date-format",

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

+62-6
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@
3232
import org.springframework.boot.autoconfigure.http.JacksonHttpMessageConvertersConfiguration.MappingJackson2HttpMessageConverterConfiguration;
3333
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
3434
import org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration;
35+
import org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener;
3536
import org.springframework.boot.autoconfigure.web.ServerProperties;
37+
import org.springframework.boot.logging.LogLevel;
3638
import org.springframework.boot.test.context.FilteredClassLoader;
3739
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
3840
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
@@ -130,12 +132,41 @@ void gsonCustomConverter() {
130132

131133
@Test
132134
void gsonCanBePreferred() {
133-
allOptionsRunner().withPropertyValues("spring.mvc.converters.preferred-json-mapper:gson").run((context) -> {
134-
assertConverterBeanExists(context, GsonHttpMessageConverter.class, "gsonHttpMessageConverter");
135-
assertConverterBeanRegisteredWithHttpMessageConverters(context, GsonHttpMessageConverter.class);
136-
assertThat(context).doesNotHaveBean(JsonbHttpMessageConverter.class);
137-
assertThat(context).doesNotHaveBean(MappingJackson2HttpMessageConverter.class);
138-
});
135+
allOptionsRunner().withInitializer(ConditionEvaluationReportLoggingListener.forLogLevel(LogLevel.INFO))
136+
.withPropertyValues("spring.http.converters.preferred-json-mapper:gson")
137+
.run((context) -> {
138+
assertConverterBeanExists(context, GsonHttpMessageConverter.class, "gsonHttpMessageConverter");
139+
assertConverterBeanRegisteredWithHttpMessageConverters(context, GsonHttpMessageConverter.class);
140+
assertThat(context).doesNotHaveBean(JsonbHttpMessageConverter.class);
141+
assertThat(context).doesNotHaveBean(MappingJackson2HttpMessageConverter.class);
142+
});
143+
}
144+
145+
@Test
146+
@Deprecated(since = "3.5.0", forRemoval = true)
147+
void gsonCanBePreferredWithDeprecatedProperty() {
148+
allOptionsRunner().withInitializer(ConditionEvaluationReportLoggingListener.forLogLevel(LogLevel.INFO))
149+
.withPropertyValues("spring.mvc.converters.preferred-json-mapper:gson")
150+
.run((context) -> {
151+
assertConverterBeanExists(context, GsonHttpMessageConverter.class, "gsonHttpMessageConverter");
152+
assertConverterBeanRegisteredWithHttpMessageConverters(context, GsonHttpMessageConverter.class);
153+
assertThat(context).doesNotHaveBean(JsonbHttpMessageConverter.class);
154+
assertThat(context).doesNotHaveBean(MappingJackson2HttpMessageConverter.class);
155+
});
156+
}
157+
158+
@Test
159+
@Deprecated(since = "3.5.0", forRemoval = true)
160+
void gsonCanBePreferredWithNonDeprecatedPropertyTakingPrecedence() {
161+
allOptionsRunner().withInitializer(ConditionEvaluationReportLoggingListener.forLogLevel(LogLevel.INFO))
162+
.withPropertyValues("spring.http.converters.preferred-json-mapper:gson",
163+
"spring.mvc.converters.preferred-json-mapper:jackson")
164+
.run((context) -> {
165+
assertConverterBeanExists(context, GsonHttpMessageConverter.class, "gsonHttpMessageConverter");
166+
assertConverterBeanRegisteredWithHttpMessageConverters(context, GsonHttpMessageConverter.class);
167+
assertThat(context).doesNotHaveBean(JsonbHttpMessageConverter.class);
168+
assertThat(context).doesNotHaveBean(MappingJackson2HttpMessageConverter.class);
169+
});
139170
}
140171

141172
@Test
@@ -169,6 +200,31 @@ void jsonbCanBePreferred() {
169200
});
170201
}
171202

203+
@Test
204+
@Deprecated(since = "3.5.0", forRemoval = true)
205+
void jsonbCanBePreferredWithDeprecatedProperty() {
206+
allOptionsRunner().withPropertyValues("spring.http.converters.preferred-json-mapper:jsonb").run((context) -> {
207+
assertConverterBeanExists(context, JsonbHttpMessageConverter.class, "jsonbHttpMessageConverter");
208+
assertConverterBeanRegisteredWithHttpMessageConverters(context, JsonbHttpMessageConverter.class);
209+
assertThat(context).doesNotHaveBean(GsonHttpMessageConverter.class);
210+
assertThat(context).doesNotHaveBean(MappingJackson2HttpMessageConverter.class);
211+
});
212+
}
213+
214+
@Test
215+
@Deprecated(since = "3.5.0", forRemoval = true)
216+
void jsonbCanBePreferredWithNonDeprecatedPropertyTakingPrecedence() {
217+
allOptionsRunner()
218+
.withPropertyValues("spring.http.converters.preferred-json-mapper:jsonb",
219+
"spring.mvc.converters.preferred-json-mapper:gson")
220+
.run((context) -> {
221+
assertConverterBeanExists(context, JsonbHttpMessageConverter.class, "jsonbHttpMessageConverter");
222+
assertConverterBeanRegisteredWithHttpMessageConverters(context, JsonbHttpMessageConverter.class);
223+
assertThat(context).doesNotHaveBean(GsonHttpMessageConverter.class);
224+
assertThat(context).doesNotHaveBean(MappingJackson2HttpMessageConverter.class);
225+
});
226+
}
227+
172228
@Test
173229
void stringDefaultConverter() {
174230
this.contextRunner.run(assertConverter(StringHttpMessageConverter.class, "stringHttpMessageConverter"));

0 commit comments

Comments
 (0)