From 22d6f6a7de325ecc317a8bdb2bbfa3fcd10c27c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Nicoll?= Date: Mon, 29 Dec 2025 13:08:01 +0100 Subject: [PATCH] Allow a custom JsonFactory to be used with JsonMapper.Builder This commit improves the auto-configuration of the JSonMapper.Builder to accept a custom JsonFactory if a bean of this type is present. Closes gh-48594 --- .../modules/how-to/pages/spring-mvc.adoc | 3 ++- .../JacksonAutoConfiguration.java | 6 ++++-- .../JacksonAutoConfigurationTests.java | 20 +++++++++++++++++++ 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/documentation/spring-boot-docs/src/docs/antora/modules/how-to/pages/spring-mvc.adoc b/documentation/spring-boot-docs/src/docs/antora/modules/how-to/pages/spring-mvc.adoc index 3f12e6491e1..49e50598457 100644 --- a/documentation/spring-boot-docs/src/docs/antora/modules/how-to/pages/spring-mvc.adoc +++ b/documentation/spring-boot-docs/src/docs/antora/modules/how-to/pages/spring-mvc.adoc @@ -106,7 +106,8 @@ This environment-based configuration is applied to the auto-configured javadoc:t To ease the migration when working on an application that previously used Jackson 2, the auto-configured `JsonMapper` can be configured to use defaults that are as close as possible to those that Spring Boot used for Jackson 2. To enable these defaults, set configprop:spring.jackson.use-jackson2-defaults[] to `true`. -The context's javadoc:tools.jackson.databind.json.JsonMapper$Builder[] can be customized by one or more javadoc:org.springframework.boot.jackson.autoconfigure.JsonMapperBuilderCustomizer[] beans. +If a javadoc:tools.jackson.core.json.JsonFactory[] bean is present, it is used by the context's javadoc:tools.jackson.databind.json.JsonMapper$Builder[]. +The builder can also be customized by one or more javadoc:org.springframework.boot.jackson.autoconfigure.JsonMapperBuilderCustomizer[] beans. Such customizer beans can be ordered (Boot's own customizer has an order of 0), letting additional customization be applied both before and after Boot's customization. Any beans of type javadoc:tools.jackson.databind.JacksonModule[] are automatically registered with the auto-configured javadoc:tools.jackson.databind.json.JsonMapper$Builder[] and are applied to any javadoc:tools.jackson.databind.json.JsonMapper[] instances that it creates. diff --git a/module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/autoconfigure/JacksonAutoConfiguration.java b/module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/autoconfigure/JacksonAutoConfiguration.java index 3969b15bd30..9ccd6cb8d73 100644 --- a/module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/autoconfigure/JacksonAutoConfiguration.java +++ b/module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/autoconfigure/JacksonAutoConfiguration.java @@ -32,6 +32,7 @@ import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.PropertyAccessor; import org.jspecify.annotations.Nullable; +import tools.jackson.core.json.JsonFactory; import tools.jackson.databind.JacksonModule; import tools.jackson.databind.ObjectMapper; import tools.jackson.databind.PropertyNamingStrategies; @@ -97,8 +98,9 @@ public final class JacksonAutoConfiguration { @Bean @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) @ConditionalOnMissingBean - JsonMapper.Builder jsonMapperBuilder(List customizers) { - JsonMapper.Builder builder = JsonMapper.builder(); + JsonMapper.Builder jsonMapperBuilder(ObjectProvider jsonFactory, + List customizers) { + JsonMapper.Builder builder = JsonMapper.builder(jsonFactory.getIfAvailable(JsonFactory::new)); customize(builder, customizers); return builder; } diff --git a/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/autoconfigure/JacksonAutoConfigurationTests.java b/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/autoconfigure/JacksonAutoConfigurationTests.java index bebaccd1122..f4228667ccc 100644 --- a/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/autoconfigure/JacksonAutoConfigurationTests.java +++ b/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/autoconfigure/JacksonAutoConfigurationTests.java @@ -34,6 +34,8 @@ import org.junit.jupiter.params.provider.EnumSource; import tools.jackson.core.JsonGenerator; import tools.jackson.core.StreamReadFeature; import tools.jackson.core.StreamWriteFeature; +import tools.jackson.core.json.JsonFactory; +import tools.jackson.core.json.JsonFactoryBuilder; import tools.jackson.core.json.JsonReadFeature; import tools.jackson.core.json.JsonWriteFeature; import tools.jackson.databind.DeserializationFeature; @@ -391,6 +393,24 @@ class JacksonAutoConfigurationTests { }); } + @Test + void defaultJsonFactoryIsRegisteredWithTheMapperBuilderWhenNoCustomFactoryExists() { + this.contextRunner.run((context) -> { + Builder jsonMapperBuilder = context.getBean(JsonMapper.Builder.class); + assertThat(jsonMapperBuilder.isEnabled(StreamReadFeature.AUTO_CLOSE_SOURCE)).isTrue(); + }); + } + + @Test + void customJsonFactoryIsRegisteredWithTheMapperBuilder() { + JsonFactory customJsonFactory = new JsonFactoryBuilder().configure(StreamReadFeature.AUTO_CLOSE_SOURCE, false) + .build(); + this.contextRunner.withBean("customJsonFactory", JsonFactory.class, () -> customJsonFactory).run((context) -> { + Builder jsonMapperBuilder = context.getBean(JsonMapper.Builder.class); + assertThat(jsonMapperBuilder.isEnabled(StreamReadFeature.AUTO_CLOSE_SOURCE)).isFalse(); + }); + } + @EnumSource @ParameterizedTest void moduleBeansAndWellKnownModulesAreRegisteredWithTheMapperBuilder(MapperType mapperType) {