diff --git a/core/spring-boot-test/build.gradle b/core/spring-boot-test/build.gradle
index 156829f7063..8720ea9e605 100644
--- a/core/spring-boot-test/build.gradle
+++ b/core/spring-boot-test/build.gradle
@@ -27,7 +27,7 @@ dependencies {
api(project(":core:spring-boot"))
api("org.springframework:spring-test")
- optional("tools.jackson.core:jackson-databind")
+ optional("com.fasterxml.jackson.core:jackson-databind")
optional("com.google.code.gson:gson")
optional("com.jayway.jsonpath:json-path")
optional("jakarta.json.bind:jakarta.json.bind-api")
@@ -45,6 +45,7 @@ dependencies {
optional("org.seleniumhq.selenium:selenium-api")
optional("org.skyscreamer:jsonassert")
optional("org.springframework:spring-web")
+ optional("tools.jackson.core:jackson-databind")
testImplementation(project(":test-support:spring-boot-test-support"))
testImplementation("ch.qos.logback:logback-classic")
diff --git a/core/spring-boot-test/src/main/java/org/springframework/boot/test/json/Jackson2Tester.java b/core/spring-boot-test/src/main/java/org/springframework/boot/test/json/Jackson2Tester.java
new file mode 100644
index 00000000000..99cf33f8072
--- /dev/null
+++ b/core/spring-boot-test/src/main/java/org/springframework/boot/test/json/Jackson2Tester.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright 2012-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.test.json;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectReader;
+import com.fasterxml.jackson.databind.ObjectWriter;
+import com.jayway.jsonpath.Configuration;
+import com.jayway.jsonpath.spi.json.JacksonJsonProvider;
+import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider;
+import org.jspecify.annotations.Nullable;
+
+import org.springframework.beans.factory.ObjectFactory;
+import org.springframework.core.ResolvableType;
+import org.springframework.util.Assert;
+
+/**
+ * AssertJ based JSON tester backed by Jackson 2. Usually instantiated via
+ * {@link #initFields(Object, ObjectMapper)}, for example:
+ * public class ExampleObjectJsonTests {
+ *
+ * private Jackson2Tester<ExampleObject> json;
+ *
+ * @Before
+ * public void setup() {
+ * ObjectMapper objectMapper = new ObjectMapper();
+ * Jackson2Tester.initFields(this, objectMapper);
+ * }
+ *
+ * @Test
+ * public void testWriteJson() throws IOException {
+ * ExampleObject object = //...
+ * assertThat(json.write(object)).isEqualToJson("expected.json");
+ * }
+ *
+ * }
+ *
+ *
+ * See {@link AbstractJsonMarshalTester} for more details.
+ *
+ * @param the type under test
+ * @author Phillip Webb
+ * @author Madhura Bhave
+ * @author Diego Berrueta
+ * @since 4.0.0
+ * @deprecated since 4.0.0 for removal in 4.2.0 in favor of Jackson 3.
+ */
+@Deprecated(since = "4.0.0", forRemoval = true)
+public class Jackson2Tester extends AbstractJsonMarshalTester {
+
+ private final ObjectMapper objectMapper;
+
+ private @Nullable Class> view;
+
+ /**
+ * Create a new {@link Jackson2Tester} instance.
+ * @param objectMapper the Jackson object mapper
+ */
+ protected Jackson2Tester(ObjectMapper objectMapper) {
+ Assert.notNull(objectMapper, "'objectMapper' must not be null");
+ this.objectMapper = objectMapper;
+ }
+
+ /**
+ * Create a new {@link Jackson2Tester} instance.
+ * @param resourceLoadClass the source class used to load resources
+ * @param type the type under test
+ * @param objectMapper the Jackson object mapper
+ */
+ public Jackson2Tester(Class> resourceLoadClass, ResolvableType type, ObjectMapper objectMapper) {
+ this(resourceLoadClass, type, objectMapper, null);
+ }
+
+ /**
+ * Create a new {@link Jackson2Tester} instance.
+ * @param resourceLoadClass the source class used to load resources
+ * @param type the type under test
+ * @param objectMapper the Jackson object mapper
+ * @param view the JSON view
+ */
+ public Jackson2Tester(Class> resourceLoadClass, ResolvableType type, ObjectMapper objectMapper,
+ @Nullable Class> view) {
+ super(resourceLoadClass, type);
+ Assert.notNull(objectMapper, "'objectMapper' must not be null");
+ this.objectMapper = objectMapper;
+ this.view = view;
+ }
+
+ @Override
+ protected JsonContent getJsonContent(String json) {
+ Configuration configuration = Configuration.builder()
+ .jsonProvider(new JacksonJsonProvider(this.objectMapper))
+ .mappingProvider(new JacksonMappingProvider(this.objectMapper))
+ .build();
+ Class> resourceLoadClass = getResourceLoadClass();
+ Assert.state(resourceLoadClass != null, "'resourceLoadClass' must not be null");
+ return new JsonContent<>(resourceLoadClass, getType(), json, configuration);
+ }
+
+ @Override
+ protected T readObject(InputStream inputStream, ResolvableType type) throws IOException {
+ return getObjectReader(type).readValue(inputStream);
+ }
+
+ @Override
+ protected T readObject(Reader reader, ResolvableType type) throws IOException {
+ return getObjectReader(type).readValue(reader);
+ }
+
+ private ObjectReader getObjectReader(ResolvableType type) {
+ ObjectReader objectReader = this.objectMapper.readerFor(getType(type));
+ if (this.view != null) {
+ return objectReader.withView(this.view);
+ }
+ return objectReader;
+ }
+
+ @Override
+ protected String writeObject(T value, ResolvableType type) throws IOException {
+ return getObjectWriter(type).writeValueAsString(value);
+ }
+
+ private ObjectWriter getObjectWriter(ResolvableType type) {
+ ObjectWriter objectWriter = this.objectMapper.writerFor(getType(type));
+ if (this.view != null) {
+ return objectWriter.withView(this.view);
+ }
+ return objectWriter;
+ }
+
+ private JavaType getType(ResolvableType type) {
+ return this.objectMapper.constructType(type.getType());
+ }
+
+ /**
+ * Utility method to initialize {@link Jackson2Tester} fields. See
+ * {@link Jackson2Tester class-level documentation} for example usage.
+ * @param testInstance the test instance
+ * @param objectMapper the JSON mapper
+ * @see #initFields(Object, ObjectMapper)
+ */
+ public static void initFields(Object testInstance, ObjectMapper objectMapper) {
+ new Jackson2FieldInitializer().initFields(testInstance, objectMapper);
+ }
+
+ /**
+ * Utility method to initialize {@link Jackson2Tester} fields. See
+ * {@link Jackson2Tester class-level documentation} for example usage.
+ * @param testInstance the test instance
+ * @param objectMapperFactory a factory to create the object mapper
+ * @see #initFields(Object, ObjectMapper)
+ */
+ public static void initFields(Object testInstance, ObjectFactory objectMapperFactory) {
+ new Jackson2FieldInitializer().initFields(testInstance, objectMapperFactory);
+ }
+
+ /**
+ * Returns a new instance of {@link Jackson2Tester} with the view that should be used
+ * for json serialization/deserialization.
+ * @param view the view class
+ * @return the new instance
+ */
+ public Jackson2Tester forView(Class> view) {
+ Class> resourceLoadClass = getResourceLoadClass();
+ ResolvableType type = getType();
+ Assert.state(resourceLoadClass != null, "'resourceLoadClass' must not be null");
+ Assert.state(type != null, "'type' must not be null");
+ return new Jackson2Tester<>(resourceLoadClass, type, this.objectMapper, view);
+ }
+
+ /**
+ * {@link FieldInitializer} for Jackson.
+ */
+ private static class Jackson2FieldInitializer extends FieldInitializer {
+
+ protected Jackson2FieldInitializer() {
+ super(Jackson2Tester.class);
+ }
+
+ @Override
+ protected AbstractJsonMarshalTester createTester(Class> resourceLoadClass, ResolvableType type,
+ ObjectMapper marshaller) {
+ return new Jackson2Tester<>(resourceLoadClass, type, marshaller);
+ }
+
+ }
+
+}
diff --git a/core/spring-boot-test/src/main/java/org/springframework/boot/test/json/JacksonTester.java b/core/spring-boot-test/src/main/java/org/springframework/boot/test/json/JacksonTester.java
index 94f3ff93779..3870834b63b 100644
--- a/core/spring-boot-test/src/main/java/org/springframework/boot/test/json/JacksonTester.java
+++ b/core/spring-boot-test/src/main/java/org/springframework/boot/test/json/JacksonTester.java
@@ -103,6 +103,14 @@ public class JacksonTester extends AbstractJsonMarshalTester {
this(resourceLoadClass, type, jsonMapper, null);
}
+ /**
+ * Create a new {@link JacksonTester} instance.
+ * @param resourceLoadClass the source class used to load resources
+ * @param type the type under test
+ * @param jsonMapper the Jackson JSON mapper
+ * @param view the JSON view
+ * @since 4.0.0
+ */
public JacksonTester(Class> resourceLoadClass, ResolvableType type, JsonMapper jsonMapper,
@Nullable Class> view) {
super(resourceLoadClass, type);
diff --git a/core/spring-boot-test/src/test/java/org/springframework/boot/test/json/Jackson2TesterTests.java b/core/spring-boot-test/src/test/java/org/springframework/boot/test/json/Jackson2TesterTests.java
new file mode 100644
index 00000000000..e04b3b926b7
--- /dev/null
+++ b/core/spring-boot-test/src/test/java/org/springframework/boot/test/json/Jackson2TesterTests.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2012-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.test.json;
+
+import java.util.List;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.jspecify.annotations.Nullable;
+import org.junit.jupiter.api.Test;
+
+import org.springframework.core.ResolvableType;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+
+/**
+ * Tests for {@link Jackson2Tester}.
+ *
+ * @author Phillip Webb
+ * @deprecated since 4.0.0 for removal in 4.2.0 in favor of JacksonTesterTests
+ */
+@Deprecated(since = "4.0.0", forRemoval = true)
+@SuppressWarnings("removal")
+class Jackson2TesterTests extends AbstractJsonMarshalTesterTests {
+
+ @Test
+ @SuppressWarnings("NullAway") // Test null check
+ void initFieldsWhenTestIsNullShouldThrowException() {
+ assertThatIllegalArgumentException().isThrownBy(() -> Jackson2Tester.initFields(null, new ObjectMapper()))
+ .withMessageContaining("'testInstance' must not be null");
+ }
+
+ @Test
+ @SuppressWarnings("NullAway") // Test null check
+ void initFieldsWhenMarshallerIsNullShouldThrowException() {
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> Jackson2Tester.initFields(new InitFieldsTestClass(), (ObjectMapper) null))
+ .withMessageContaining("'marshaller' must not be null");
+ }
+
+ @Test
+ void initFieldsShouldSetNullFields() {
+ InitFieldsTestClass test = new InitFieldsTestClass();
+ assertThat(test.test).isNull();
+ assertThat(test.base).isNull();
+ Jackson2Tester.initFields(test, new ObjectMapper());
+ assertThat(test.test).isNotNull();
+ assertThat(test.base).isNotNull();
+ ResolvableType type = test.test.getType();
+ assertThat(type).isNotNull();
+ assertThat(type.resolve()).isEqualTo(List.class);
+ assertThat(type.resolveGeneric()).isEqualTo(ExampleObject.class);
+ }
+
+ @Override
+ protected AbstractJsonMarshalTester createTester(Class> resourceLoadClass, ResolvableType type) {
+ return new org.springframework.boot.test.json.Jackson2Tester<>(resourceLoadClass, type, new ObjectMapper());
+ }
+
+ abstract static class InitFieldsBaseClass {
+
+ public org.springframework.boot.test.json.@Nullable Jackson2Tester base;
+
+ public org.springframework.boot.test.json.Jackson2Tester baseSet = new org.springframework.boot.test.json.Jackson2Tester<>(
+ InitFieldsBaseClass.class, ResolvableType.forClass(ExampleObject.class), new ObjectMapper());
+
+ }
+
+ static class InitFieldsTestClass extends InitFieldsBaseClass {
+
+ public org.springframework.boot.test.json.@Nullable Jackson2Tester> test;
+
+ public org.springframework.boot.test.json.Jackson2Tester testSet = new org.springframework.boot.test.json.Jackson2Tester<>(
+ InitFieldsBaseClass.class, ResolvableType.forClass(ExampleObject.class), new ObjectMapper());
+
+ }
+
+}
diff --git a/module/spring-boot-jackson2/src/main/java/org/springframework/boot/jackson2/autoconfigure/Jackson2TesterAutoConfiguration.java b/module/spring-boot-jackson2/src/main/java/org/springframework/boot/jackson2/autoconfigure/Jackson2TesterAutoConfiguration.java
new file mode 100644
index 00000000000..09a5a100739
--- /dev/null
+++ b/module/spring-boot-jackson2/src/main/java/org/springframework/boot/jackson2/autoconfigure/Jackson2TesterAutoConfiguration.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2012-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.jackson2.autoconfigure;
+
+import com.fasterxml.jackson.databind.json.JsonMapper;
+
+import org.springframework.beans.factory.FactoryBean;
+import org.springframework.beans.factory.config.ConfigurableBeanFactory;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.test.autoconfigure.TestAutoConfiguration;
+import org.springframework.boot.test.autoconfigure.json.ConditionalOnJsonTesters;
+import org.springframework.boot.test.autoconfigure.json.JsonMarshalTesterRuntimeHints;
+import org.springframework.boot.test.autoconfigure.json.JsonTesterFactoryBean;
+import org.springframework.boot.test.json.GsonTester;
+import org.springframework.boot.test.json.JacksonTester;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ImportRuntimeHints;
+import org.springframework.context.annotation.Scope;
+
+/**
+ * {@link EnableAutoConfiguration Auto-configuration} for {@link GsonTester}.
+ *
+ * @author Phjllip Webb
+ * @deprecated since 4.0.0 for removal in 4.2.0 in favor of Jackson 3.
+ */
+@Deprecated(since = "4.0.0", forRemoval = true)
+@TestAutoConfiguration(after = Jackson2AutoConfiguration.class)
+@ConditionalOnJsonTesters
+@SuppressWarnings("removal")
+final class Jackson2TesterAutoConfiguration {
+
+ @Bean
+ @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
+ @ConditionalOnBean(JsonMapper.class)
+ @ImportRuntimeHints(Jackson2TesterRuntimeHints.class)
+ FactoryBean> jacksonTesterFactoryBean(JsonMapper mapper) {
+ return new JsonTesterFactoryBean<>(JacksonTester.class, mapper);
+ }
+
+ static class Jackson2TesterRuntimeHints extends JsonMarshalTesterRuntimeHints {
+
+ Jackson2TesterRuntimeHints() {
+ super(JacksonTester.class);
+ }
+
+ }
+
+}
diff --git a/module/spring-boot-jackson2/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.json.AutoConfigureJson.imports b/module/spring-boot-jackson2/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.json.AutoConfigureJson.imports
new file mode 100644
index 00000000000..83028f13a4c
--- /dev/null
+++ b/module/spring-boot-jackson2/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.json.AutoConfigureJson.imports
@@ -0,0 +1 @@
+org.springframework.boot.jackson.autoconfigure.Jackson2AutoConfiguration
diff --git a/module/spring-boot-jackson2/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.json.AutoConfigureJsonTesters.imports b/module/spring-boot-jackson2/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.json.AutoConfigureJsonTesters.imports
new file mode 100644
index 00000000000..a470a949079
--- /dev/null
+++ b/module/spring-boot-jackson2/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.json.AutoConfigureJsonTesters.imports
@@ -0,0 +1 @@
+org.springframework.boot.jackson.autoconfigure.Jackson2TesterAutoConfiguration
diff --git a/module/spring-boot-jackson2/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.json.JsonTest.includes b/module/spring-boot-jackson2/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.json.JsonTest.includes
new file mode 100644
index 00000000000..707c8d545cd
--- /dev/null
+++ b/module/spring-boot-jackson2/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.json.JsonTest.includes
@@ -0,0 +1,2 @@
+org.springframework.boot.jackson2.JsonComponent
+com.fasterxml.jackson.databind.Module
\ No newline at end of file