From d353038c584ae715aba735161c0d4f86907b2e08 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Tue, 22 Jul 2025 19:36:19 +0100 Subject: [PATCH] Update Jackson support to require Jackson 3 Closes gh-45535 --- .../spring-boot-gradle-plugin/build.gradle | 3 +- .../testkit/PluginClasspathGradleBuild.java | 10 +- buildSrc/build.gradle | 5 +- buildSrc/config/checkstyle/checkstyle.xml | 9 +- .../boot/build/AntoraConventions.java | 23 +- .../antora/AntoraAsciidocAttributes.java | 7 +- .../boot/build/bom/ResolvedBom.java | 20 +- .../build/bom/bomr/github/StandardGitHub.java | 6 +- ...AdditionalSpringConfigurationMetadata.java | 8 +- .../CheckSpringConfigurationMetadata.java | 8 +- .../properties/ConfigurationProperties.java | 22 +- .../antora-asciidoc-attributes.properties | 14 +- .../antora/AntoraAsciidocAttributesTests.java | 10 +- .../build.gradle | 3 +- .../platform/build/BuilderMetadata.java | 12 +- .../build/BuildpackLayersMetadata.java | 2 +- .../platform/build/BuildpackMetadata.java | 2 +- .../buildpack/platform/docker/DockerApi.java | 14 +- .../docker/configuration/Credential.java | 2 +- .../DockerConfigurationMetadata.java | 16 +- ...onEncodedDockerRegistryAuthentication.java | 4 +- .../docker/transport/HttpClientTransport.java | 5 +- .../platform/docker/type/BlobReference.java | 2 +- .../platform/docker/type/ContainerConfig.java | 19 +- .../platform/docker/type/ContainerStatus.java | 4 +- .../buildpack/platform/docker/type/Image.java | 4 +- .../platform/docker/type/ImageArchive.java | 18 +- .../docker/type/ImageArchiveIndex.java | 2 +- .../docker/type/ImageArchiveManifest.java | 2 +- .../platform/docker/type/ImageConfig.java | 6 +- .../platform/docker/type/Manifest.java | 2 +- .../platform/docker/type/ManifestList.java | 2 +- .../buildpack/platform/json/JsonStream.java | 16 +- .../buildpack/platform/json/MappedObject.java | 9 +- .../platform/json/SharedObjectMapper.java | 23 +- .../platform/build/LifecycleTests.java | 9 +- ...ckerRegistryConfigAuthenticationTests.java | 2 +- .../docker/type/ImageArchiveIndexTests.java | 6 +- .../type/ImageArchiveManifestTests.java | 9 +- .../docker/type/ImageConfigTests.java | 15 +- .../docker/type/ManifestListTests.java | 6 +- .../platform/docker/type/ManifestTests.java | 8 +- .../platform/json/AbstractJsonTests.java | 2 +- .../platform/json/JsonStreamTests.java | 4 +- .../platform/json/MappedObjectTests.java | 2 +- .../json/SharedObjectMapperTests.java | 21 +- core/spring-boot-autoconfigure/build.gradle | 2 +- .../NonAspectJAopAutoConfigurationTests.java | 16 +- ...eanConditionTypeDeductionFailureTests.java | 2 +- core/spring-boot-docker-compose/build.gradle | 3 +- .../boot/docker/compose/core/DockerJson.java | 20 +- .../ConnectionNamePredicateTests.java | 1 + core/spring-boot-test/build.gradle | 2 +- .../boot/test/json/JacksonTester.java | 192 +++++++++-- .../json/JacksonTesterIntegrationTests.java | 15 +- .../boot/test/json/JacksonTesterTests.java | 14 +- core/spring-boot/build.gradle | 2 +- .../boot/json/JacksonJsonParser.java | 4 +- .../boot/json/JacksonRuntimeHints.java | 56 ---- .../boot/json/JsonParserFactory.java | 2 +- .../resources/META-INF/spring/aot.factories | 1 - .../boot/json/JacksonJsonParserTests.java | 8 +- .../boot/json/JacksonRuntimeHintsTests.java | 64 ---- .../AbstractStructuredLoggingTests.java | 16 +- .../log4j2/Log4J2LoggingSystemTests.java | 12 +- .../AbstractStructuredLoggingTests.java | 16 +- .../boot/web/error/ErrorTests.java | 5 +- .../AbstractEndpointDocumentationTests.java | 52 ++- ...EnvironmentEndpointDocumentationTests.java | 35 +- .../SessionsEndpointDocumentationTests.java | 2 - .../antora/modules/ROOT/pages/redirect.adoc | 4 +- .../modules/how-to/pages/spring-mvc.adoc | 64 ++-- .../reference/pages/features/json.adoc | 18 +- .../testing/spring-boot-applications.adoc | 6 +- .../reference/pages/web/spring-hateoas.adoc | 6 +- .../MyJsonComponent.java | 34 +- .../object/MyJsonComponent.java | 31 +- .../MyJsonComponent.kt | 34 +- .../object/MyJsonComponent.kt | 33 +- gradle.properties | 3 +- .../build.gradle | 1 + .../MetricsEndpointWebIntegrationTests.java | 5 +- .../ActiveMQAutoConfigurationTests.java | 14 + .../build.gradle | 4 +- .../JacksonEndpointAutoConfiguration.java | 29 +- .../jmx/JmxEndpointAutoConfiguration.java | 2 +- ...JacksonEndpointAutoConfigurationTests.java | 42 +-- module/spring-boot-actuator/build.gradle | 4 +- ...ConfigurationPropertiesReportEndpoint.java | 71 ++--- .../endpoint/OperationResponseBody.java | 2 +- ...ectMapper.java => EndpointJsonMapper.java} | 13 +- .../JacksonJmxOperationResponseMapper.java | 4 +- .../boot/actuate/audit/AuditEventTests.java | 6 +- ...gurationPropertiesReportEndpointTests.java | 2 +- ...acksonJmxOperationResponseMapperTests.java | 4 +- .../CompositeHealthDescriptorTests.java | 6 +- .../IndicatedHealthDescriptorTests.java | 6 +- .../health/SystemHealthDescriptorTests.java | 6 +- module/spring-boot-couchbase/build.gradle | 1 + .../CouchbaseAutoConfigurationTests.java | 10 +- .../RepositoryRestMvcAutoConfiguration.java | 9 +- .../SpringBootRepositoryRestConfigurer.java | 27 +- ...positoryRestMvcAutoConfigurationTests.java | 11 +- ...ticsearchClientAutoConfigurationTests.java | 1 + module/spring-boot-graphql-test/build.gradle | 2 +- .../GraphQlRSocketAutoConfiguration.java | 8 +- module/spring-boot-hateoas/build.gradle | 2 +- .../HypermediaAutoConfiguration.java | 4 +- .../HypermediaAutoConfigurationTests.java | 7 +- module/spring-boot-health/build.gradle | 2 +- .../boot/health/contributor/HealthTests.java | 4 +- .../boot/health/contributor/StatusTests.java | 2 +- .../CodecsAutoConfiguration.java | 21 +- .../CodecsAutoConfigurationTests.java | 14 +- .../spring-boot-http-converter/build.gradle | 4 +- ...sonHttpMessageConvertersConfiguration.java | 4 +- ...sonHttpMessageConvertersConfiguration.java | 35 +- ...onbHttpMessageConvertersConfiguration.java | 5 +- ...ssageConvertersAutoConfigurationTests.java | 99 +++--- .../HttpMessageConvertersTests.java | 31 +- module/spring-boot-jackson/build.gradle | 10 +- .../boot/jackson/JsonComponent.java | 28 +- .../boot/jackson/JsonComponentModule.java | 30 +- .../boot/jackson/JsonMixinModule.java | 3 +- ...izer.java => ObjectValueDeserializer.java} | 45 +-- ...alizer.java => ObjectValueSerializer.java} | 37 +-- .../JacksonAutoConfiguration.java | 218 +++++-------- .../JacksonBackgroundPreinitializer.java | 8 +- .../autoconfigure/JacksonProperties.java | 30 +- ....java => JsonMapperBuilderCustomizer.java} | 17 +- .../jackson/JsonComponentModuleTests.java | 46 ++- .../boot/jackson/JsonMixinModuleTests.java | 17 +- .../boot/jackson/NameAndAgeJsonComponent.java | 29 +- .../jackson/NameAndAgeJsonKeyComponent.java | 22 +- .../jackson/NameAndCareerJsonComponent.java | 24 +- ...java => ObjectValueDeserializerTests.java} | 30 +- ...s.java => ObjectValueSerializerTests.java} | 15 +- .../JacksonAutoConfigurationTests.java | 301 +++++++----------- .../boot/jackson/types/NameAndAge.java | 6 +- module/spring-boot-jdbc/build.gradle | 2 +- .../DataSourceJsonSerializationTests.java | 46 ++- .../build.gradle | 2 +- module/spring-boot-rsocket/build.gradle | 2 +- .../RSocketStrategiesAutoConfiguration.java | 35 +- ...ocketStrategiesAutoConfigurationTests.java | 22 +- ...ocketWebSocketNettyRouteProviderTests.java | 11 +- .../build.gradle | 2 +- .../OAuth2ClientPropertiesMapperTests.java | 7 +- .../build.gradle | 6 +- ...2ResourceServerAutoConfigurationTests.java | 7 +- ...2ResourceServerAutoConfigurationTests.java | 7 +- .../spring-boot-security-saml2/build.gradle | 2 +- module/spring-boot-session/build.gradle | 1 - .../build.gradle | 1 - .../GraphQlTesterAutoConfiguration.java | 16 +- .../json/JsonTestersAutoConfiguration.java | 8 +- .../json/app/ExampleJsonComponent.java | 31 +- module/spring-boot-webflux/build.gradle | 3 +- ...ndpointManagementContextConfiguration.java | 40 ++- .../web/WebFluxEndpointIntegrationTests.java | 40 ++- module/spring-boot-webmvc/build.gradle | 2 +- ...ndpointManagementContextConfiguration.java | 40 +-- .../web/WebMvcEndpointIntegrationTests.java | 40 ++- .../WebSocketMessagingAutoConfiguration.java | 18 +- ...SocketMessagingAutoConfigurationTests.java | 8 +- .../spring-boot-dependencies/build.gradle | 12 +- .../src/main/resources/application.properties | 4 +- ...icationIsolatedObjectMapperFalseTests.java | 10 +- ...licationIsolatedObjectMapperTrueTests.java | 7 +- .../SampleHateoasApplicationTests.java | 8 +- .../web/secure/AbstractErrorPageTests.java | 6 +- ...AbstractUnauthenticatedErrorPageTests.java | 14 +- .../src/main/resources/application.properties | 3 +- ...ctuatorIsolatedObjectMapperFalseTests.java | 11 +- ...ActuatorIsolatedObjectMapperTrueTests.java | 7 +- .../spring-boot-starter-jackson/build.gradle | 4 - 176 files changed, 1420 insertions(+), 1678 deletions(-) delete mode 100644 core/spring-boot/src/main/java/org/springframework/boot/json/JacksonRuntimeHints.java delete mode 100644 core/spring-boot/src/test/java/org/springframework/boot/json/JacksonRuntimeHintsTests.java rename module/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jackson/{EndpointObjectMapper.java => EndpointJsonMapper.java} (80%) rename module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/{JsonObjectDeserializer.java => ObjectValueDeserializer.java} (78%) rename module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/{JsonObjectSerializer.java => ObjectValueSerializer.java} (53%) rename module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/autoconfigure/{Jackson2ObjectMapperBuilderCustomizer.java => JsonMapperBuilderCustomizer.java} (62%) rename module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/{JsonObjectDeserializerTests.java => ObjectValueDeserializerTests.java} (90%) rename module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/{JsonObjectSerializerTests.java => ObjectValueSerializerTests.java} (73%) diff --git a/build-plugin/spring-boot-gradle-plugin/build.gradle b/build-plugin/spring-boot-gradle-plugin/build.gradle index 2c50523635a..d2e7602d515 100644 --- a/build-plugin/spring-boot-gradle-plugin/build.gradle +++ b/build-plugin/spring-boot-gradle-plugin/build.gradle @@ -59,8 +59,6 @@ dependencies { testImplementation(project(":test-support:spring-boot-gradle-test-support")) testImplementation(project(":test-support:spring-boot-test-support")) - testImplementation("com.fasterxml.jackson.core:jackson-databind") - testImplementation("com.fasterxml.jackson.module:jackson-module-parameter-names") testImplementation("com.tngtech.archunit:archunit-junit5:1.4.0") testImplementation("net.java.dev.jna:jna-platform") testImplementation("org.apache.commons:commons-compress") @@ -70,6 +68,7 @@ dependencies { testImplementation("org.jetbrains.kotlin:kotlin-compiler-runner:$kotlinVersion") testImplementation("org.jetbrains.kotlin:kotlin-daemon-client:$kotlinVersion") testImplementation("org.tomlj:tomlj:1.0.0") + testImplementation("tools.jackson.core:jackson-databind") } repositories { diff --git a/build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/testkit/PluginClasspathGradleBuild.java b/build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/testkit/PluginClasspathGradleBuild.java index 667ca269e68..2653b0ef506 100644 --- a/build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/testkit/PluginClasspathGradleBuild.java +++ b/build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/testkit/PluginClasspathGradleBuild.java @@ -22,9 +22,6 @@ import java.util.ArrayList; import java.util.List; import com.fasterxml.jackson.annotation.JsonView; -import com.fasterxml.jackson.core.Versioned; -import com.fasterxml.jackson.databind.Module; -import com.fasterxml.jackson.module.paramnames.ParameterNamesModule; import com.sun.jna.Platform; import io.spring.gradle.dependencymanagement.DependencyManagementPlugin; import org.antlr.v4.runtime.Lexer; @@ -39,6 +36,8 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinCompilerPluginSupportPlugin; import org.jetbrains.kotlin.project.model.LanguageSettings; import org.jetbrains.kotlin.tooling.core.KotlinToolingVersion; import org.tomlj.Toml; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.JacksonModule; import org.springframework.asm.ClassVisitor; import org.springframework.boot.buildpack.platform.build.BuildRequest; @@ -103,9 +102,8 @@ public class PluginClasspathGradleBuild extends GradleBuild { classpath.add(new File(pathOfJarContaining(HttpClientConnectionManager.class))); classpath.add(new File(pathOfJarContaining(HttpRequest.class))); classpath.add(new File(pathOfJarContaining(HttpVersionPolicy.class))); - classpath.add(new File(pathOfJarContaining(Module.class))); - classpath.add(new File(pathOfJarContaining(Versioned.class))); - classpath.add(new File(pathOfJarContaining(ParameterNamesModule.class))); + classpath.add(new File(pathOfJarContaining(JacksonModule.class))); + classpath.add(new File(pathOfJarContaining(JsonParser.class))); classpath.add(new File(pathOfJarContaining("com.github.openjson.JSONObject"))); classpath.add(new File(pathOfJarContaining(JsonView.class))); classpath.add(new File(pathOfJarContaining(Platform.class))); diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index 1ef3cefeafe..4edeefd6537 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -43,9 +43,9 @@ dependencies { checkstyle("com.puppycrawl.tools:checkstyle:${checkstyle.toolVersion}") checkstyle("io.spring.javaformat:spring-javaformat-checkstyle:${javaFormatVersion}") - implementation(platform("com.fasterxml.jackson:jackson-bom:${jacksonVersion}")) + implementation(platform("com.fasterxml.jackson:jackson-bom:${jackson2Version}")) + implementation(platform("tools.jackson:jackson-bom:${jacksonVersion}")) implementation(platform("org.springframework:spring-framework-bom:${springFrameworkVersion}")) - implementation("com.fasterxml.jackson.core:jackson-databind") implementation("com.github.node-gradle:gradle-node-plugin:3.5.1") implementation("com.gradle:develocity-gradle-plugin:3.17.2") implementation("com.tngtech.archunit:archunit:1.4.1") @@ -65,6 +65,7 @@ dependencies { implementation("org.springframework:spring-web") implementation("org.yaml:snakeyaml:${snakeYamlVersion}") implementation("io.spring.gradle.nullability:nullability-plugin:${nullabilityPluginVersion}") + implementation("tools.jackson.core:jackson-databind") testImplementation(platform("org.junit:junit-bom:${junitJupiterVersion}")) testImplementation("org.assertj:assertj-core:${assertjVersion}") diff --git a/buildSrc/config/checkstyle/checkstyle.xml b/buildSrc/config/checkstyle/checkstyle.xml index 1ad50d8fcb8..8abf498ddd1 100644 --- a/buildSrc/config/checkstyle/checkstyle.xml +++ b/buildSrc/config/checkstyle/checkstyle.xml @@ -6,4 +6,11 @@ - \ No newline at end of file + + + + + + + diff --git a/buildSrc/src/main/java/org/springframework/boot/build/AntoraConventions.java b/buildSrc/src/main/java/org/springframework/boot/build/AntoraConventions.java index 2e236392483..7d4fa7eff8d 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/AntoraConventions.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/AntoraConventions.java @@ -17,8 +17,6 @@ package org.springframework.boot.build; import java.io.File; -import java.io.IOException; -import java.io.UncheckedIOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -26,7 +24,6 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import com.fasterxml.jackson.databind.ObjectMapper; import com.github.gradle.node.NodeExtension; import com.github.gradle.node.npm.task.NpmInstallTask; import io.spring.gradle.antora.GenerateAntoraYmlPlugin; @@ -44,6 +41,7 @@ import org.gradle.api.provider.Provider; import org.gradle.api.tasks.Copy; import org.gradle.api.tasks.TaskContainer; import org.gradle.api.tasks.TaskProvider; +import tools.jackson.databind.ObjectMapper; import org.springframework.boot.build.antora.AntoraAsciidocAttributes; import org.springframework.boot.build.antora.GenerateAntoraPlaybook; @@ -198,18 +196,13 @@ public class AntoraConventions { } private String getUiBundleUrl(Project project) { - try { - File packageJson = project.getRootProject().file("antora/package.json"); - ObjectMapper objectMapper = new ObjectMapper(); - Map json = objectMapper.readerFor(Map.class).readValue(packageJson); - Map config = (json != null) ? (Map) json.get("config") : null; - String url = (config != null) ? (String) config.get("ui-bundle-url") : null; - Assert.state(StringUtils.hasText(url.toString()), "package.json has not ui-bundle-url config"); - return url; - } - catch (IOException ex) { - throw new UncheckedIOException(ex); - } + File packageJson = project.getRootProject().file("antora/package.json"); + ObjectMapper objectMapper = new ObjectMapper(); + Map json = objectMapper.readerFor(Map.class).readValue(packageJson); + Map config = (json != null) ? (Map) json.get("config") : null; + String url = (config != null) ? (String) config.get("ui-bundle-url") : null; + Assert.state(StringUtils.hasText(url.toString()), "package.json has not ui-bundle-url config"); + return url; } private void configureNodeExtension(Project project, NodeExtension nodeExtension) { diff --git a/buildSrc/src/main/java/org/springframework/boot/build/antora/AntoraAsciidocAttributes.java b/buildSrc/src/main/java/org/springframework/boot/build/antora/AntoraAsciidocAttributes.java index 3603c4d833c..9ee20abd90c 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/antora/AntoraAsciidocAttributes.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/antora/AntoraAsciidocAttributes.java @@ -162,10 +162,9 @@ public class AntoraAsciidocAttributes { attributes.put("version-native-build-tools", (String) this.projectProperties.get("nativeBuildToolsVersion")); attributes.put("version-graal", (String) this.projectProperties.get("graalVersion")); addDependencyVersion(attributes, "jackson-annotations", "com.fasterxml.jackson.core:jackson-annotations"); - addDependencyVersion(attributes, "jackson-core", "com.fasterxml.jackson.core:jackson-core"); - addDependencyVersion(attributes, "jackson-databind", "com.fasterxml.jackson.core:jackson-databind"); - addDependencyVersion(attributes, "jackson-dataformat-xml", - "com.fasterxml.jackson.dataformat:jackson-dataformat-xml"); + addDependencyVersion(attributes, "jackson-core", "tools.jackson.core:jackson-core"); + addDependencyVersion(attributes, "jackson-databind", "tools.jackson.core:jackson-databind"); + addDependencyVersion(attributes, "jackson-dataformat-xml", "tools.jackson.dataformat:jackson-dataformat-xml"); addSpringDataDependencyVersion(attributes, internal, "spring-data-commons"); addSpringDataDependencyVersion(attributes, internal, "spring-data-couchbase"); addSpringDataDependencyVersion(attributes, internal, "spring-data-cassandra"); diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/ResolvedBom.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/ResolvedBom.java index 011bdadfb0b..b28f8227361 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/bom/ResolvedBom.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/ResolvedBom.java @@ -25,10 +25,8 @@ import java.net.URI; import java.util.List; import com.fasterxml.jackson.annotation.JsonInclude.Include; -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; /** * A resolved bom. @@ -42,10 +40,9 @@ public record ResolvedBom(Id id, List libraries) { private static final ObjectMapper objectMapper; static { - ObjectMapper mapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT) - .setDefaultPropertyInclusion(Include.NON_EMPTY); - mapper.configOverride(List.class).setSetterInfo(JsonSetter.Value.forValueNulls(Nulls.AS_EMPTY)); - objectMapper = mapper; + objectMapper = JsonMapper.builder() + .changeDefaultPropertyInclusion((value) -> value.withContentInclusion(Include.NON_EMPTY)) + .build(); } public static ResolvedBom readFrom(File file) { @@ -58,12 +55,7 @@ public record ResolvedBom(Id id, List libraries) { } public void writeTo(Writer writer) { - try { - objectMapper.writeValue(writer, this); - } - catch (IOException ex) { - throw new UncheckedIOException(ex); - } + objectMapper.writeValue(writer, this); } public record ResolvedLibrary(String name, String version, String versionProperty, List managedDependencies, diff --git a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/github/StandardGitHub.java b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/github/StandardGitHub.java index c3d46431c07..331cce148bf 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/github/StandardGitHub.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/bom/bomr/github/StandardGitHub.java @@ -19,9 +19,8 @@ package org.springframework.boot.build.bom.bomr.github; import java.util.Base64; import java.util.Collections; -import com.fasterxml.jackson.databind.ObjectMapper; - import org.springframework.http.MediaType; +import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.DefaultUriBuilderFactory; import org.springframework.web.util.UriTemplateHandler; @@ -61,8 +60,7 @@ final class StandardGitHub implements GitHub { @SuppressWarnings({ "deprecation", "removal" }) private RestTemplate createRestTemplate() { - return new RestTemplate(Collections.singletonList( - new org.springframework.http.converter.json.MappingJackson2HttpMessageConverter(new ObjectMapper()))); + return new RestTemplate(Collections.singletonList(new JacksonJsonHttpMessageConverter())); } } diff --git a/buildSrc/src/main/java/org/springframework/boot/build/context/properties/CheckAdditionalSpringConfigurationMetadata.java b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/CheckAdditionalSpringConfigurationMetadata.java index f2f1edec889..0d9642fef94 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/context/properties/CheckAdditionalSpringConfigurationMetadata.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/CheckAdditionalSpringConfigurationMetadata.java @@ -28,9 +28,6 @@ import java.util.Iterator; import java.util.List; import java.util.Map; -import com.fasterxml.jackson.core.JsonParseException; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.ObjectMapper; import org.gradle.api.file.FileTree; import org.gradle.api.file.RegularFileProperty; import org.gradle.api.tasks.InputFiles; @@ -40,6 +37,7 @@ import org.gradle.api.tasks.PathSensitivity; import org.gradle.api.tasks.SourceTask; import org.gradle.api.tasks.TaskAction; import org.gradle.api.tasks.VerificationException; +import tools.jackson.databind.ObjectMapper; /** * {@link SourceTask} that checks additional Spring configuration metadata files. @@ -65,7 +63,7 @@ public abstract class CheckAdditionalSpringConfigurationMetadata extends SourceT } @TaskAction - void check() throws JsonParseException, IOException { + void check() throws IOException { Report report = createReport(); File reportFile = getReportLocation().get().getAsFile(); Files.write(reportFile.toPath(), report, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); @@ -76,7 +74,7 @@ public abstract class CheckAdditionalSpringConfigurationMetadata extends SourceT } @SuppressWarnings("unchecked") - private Report createReport() throws IOException, JsonParseException, JsonMappingException { + private Report createReport() { ObjectMapper objectMapper = new ObjectMapper(); Report report = new Report(); for (File file : getSource().getFiles()) { diff --git a/buildSrc/src/main/java/org/springframework/boot/build/context/properties/CheckSpringConfigurationMetadata.java b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/CheckSpringConfigurationMetadata.java index 7e86a41a8f8..7334a96627b 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/context/properties/CheckSpringConfigurationMetadata.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/CheckSpringConfigurationMetadata.java @@ -26,9 +26,6 @@ import java.util.Iterator; import java.util.List; import java.util.Map; -import com.fasterxml.jackson.core.JsonParseException; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.ObjectMapper; import org.gradle.api.DefaultTask; import org.gradle.api.file.RegularFileProperty; import org.gradle.api.provider.ListProperty; @@ -40,6 +37,7 @@ import org.gradle.api.tasks.PathSensitivity; import org.gradle.api.tasks.SourceTask; import org.gradle.api.tasks.TaskAction; import org.gradle.api.tasks.VerificationException; +import tools.jackson.databind.ObjectMapper; /** * {@link SourceTask} that checks {@code spring-configuration-metadata.json} files. @@ -65,7 +63,7 @@ public abstract class CheckSpringConfigurationMetadata extends DefaultTask { public abstract ListProperty getExclusions(); @TaskAction - void check() throws JsonParseException, IOException { + void check() throws IOException { Report report = createReport(); File reportFile = getReportLocation().get().getAsFile(); Files.write(reportFile.toPath(), report, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); @@ -76,7 +74,7 @@ public abstract class CheckSpringConfigurationMetadata extends DefaultTask { } @SuppressWarnings("unchecked") - private Report createReport() throws IOException, JsonParseException, JsonMappingException { + private Report createReport() { ObjectMapper objectMapper = new ObjectMapper(); File file = getMetadataLocation().get().getAsFile(); Report report = new Report(this.projectRoot.relativize(file.toPath())); diff --git a/buildSrc/src/main/java/org/springframework/boot/build/context/properties/ConfigurationProperties.java b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/ConfigurationProperties.java index 29a11141999..55496ef41b7 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/context/properties/ConfigurationProperties.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/ConfigurationProperties.java @@ -17,7 +17,6 @@ package org.springframework.boot.build.context.properties; import java.io.File; -import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; @@ -25,7 +24,7 @@ import java.util.List; import java.util.Map; import java.util.stream.Stream; -import com.fasterxml.jackson.databind.ObjectMapper; +import tools.jackson.databind.ObjectMapper; /** * Configuration properties read from one or more @@ -56,20 +55,15 @@ final class ConfigurationProperties { @SuppressWarnings("unchecked") static ConfigurationProperties fromFiles(Iterable files) { - try { - ObjectMapper objectMapper = new ObjectMapper(); - List properties = new ArrayList<>(); - for (File file : files) { - Map json = objectMapper.readValue(file, Map.class); - for (Map property : (List>) json.get("properties")) { - properties.add(ConfigurationProperty.fromJsonProperties(property)); - } + ObjectMapper objectMapper = new ObjectMapper(); + List properties = new ArrayList<>(); + for (File file : files) { + Map json = objectMapper.readValue(file, Map.class); + for (Map property : (List>) json.get("properties")) { + properties.add(ConfigurationProperty.fromJsonProperties(property)); } - return new ConfigurationProperties(properties); - } - catch (IOException ex) { - throw new RuntimeException("Failed to load configuration metadata", ex); } + return new ConfigurationProperties(properties); } } diff --git a/buildSrc/src/main/resources/org/springframework/boot/build/antora/antora-asciidoc-attributes.properties b/buildSrc/src/main/resources/org/springframework/boot/build/antora/antora-asciidoc-attributes.properties index d7e566ffe03..8aa3beecdd2 100644 --- a/buildSrc/src/main/resources/org/springframework/boot/build/antora/antora-asciidoc-attributes.properties +++ b/buildSrc/src/main/resources/org/springframework/boot/build/antora/antora-asciidoc-attributes.properties @@ -77,12 +77,13 @@ url-spring-data-rest-site=https://spring.io/projects/spring-data-rest url-spring-data-rest-javadoc=https://docs.spring.io/spring-data/rest/docs/{dotxversion-spring-data-rest}/api url-spring-data-site=https://spring.io/projects/spring-data url-jackson-annotations-javadoc=https://javadoc.io/doc/com.fasterxml.jackson.core/jackson-annotations/{version-jackson-annotations} -url-jackson-core-javadoc=https://javadoc.io/doc/com.fasterxml.jackson.core/jackson-core/{version-jackson-core} -url-jackson-databind-javadoc=https://javadoc.io/doc/com.fasterxml.jackson.core/jackson-databind/{version-jackson-databind} -url-jackson-dataformat-xml-javadoc=https://javadoc.io/doc/com.fasterxml.jackson.dataformat/jackson-dataformat-xml/{version-jackson-dataformat-xml} +url-jackson-core-javadoc=https://javadoc.io/doc/tools.jackson.core/jackson-core/{version-jackson-core} +url-jackson-databind-javadoc=https://javadoc.io/doc/tools.jackson.core/jackson-databind/{version-jackson-databind} +url-jackson-dataformat-xml-javadoc=https://javadoc.io/doc/tools.jackson.dataformat/jackson-dataformat-xml/{version-jackson-dataformat-xml} # === Javadoc Locations === +javadoc-location-com-fasterxml-jackson-annotation={url-jackson-annotations-javadoc} javadoc-location-org-apache-pulsar-client-api={url-pulsar-client-api-javadoc} javadoc-location-org-apache-pulsar-reactive-client-api={url-pulsar-client-reactive-api-javadoc} javadoc-location-org-springframework-data-cassandra={url-spring-data-cassandra-javadoc} @@ -99,10 +100,9 @@ javadoc-location-org-springframework-data-neo4j={url-spring-data-neo4j-javadoc} javadoc-location-org-springframework-data-r2dbc={url-spring-data-r2dbc-javadoc} javadoc-location-org-springframework-data-redis={url-spring-data-redis-javadoc} javadoc-location-org-springframework-data-rest={url-spring-data-rest-javadoc} -javadoc-location-com-fasterxml-jackson-annotation={url-jackson-annotations-javadoc} -javadoc-location-com-fasterxml-jackson-core={url-jackson-core-javadoc} -javadoc-location-com-fasterxml-jackson-databind={url-jackson-databind-javadoc} -javadoc-location-com-fasterxml-jackson-dataformat-xml={url-jackson-dataformat-xml-javadoc} +javadoc-location-tools-jackson-core={url-jackson-core-javadoc} +javadoc-location-tools-jackson-databind={url-jackson-databind-javadoc} +javadoc-location-tools-jackson-dataformat-xml={url-jackson-dataformat-xml-javadoc} # === API References === diff --git a/buildSrc/src/test/java/org/springframework/boot/build/antora/AntoraAsciidocAttributesTests.java b/buildSrc/src/test/java/org/springframework/boot/build/antora/AntoraAsciidocAttributesTests.java index 99b37878158..9b51c7d4001 100644 --- a/buildSrc/src/test/java/org/springframework/boot/build/antora/AntoraAsciidocAttributesTests.java +++ b/buildSrc/src/test/java/org/springframework/boot/build/antora/AntoraAsciidocAttributesTests.java @@ -280,12 +280,12 @@ class AntoraAsciidocAttributesTests { addMockTestcontainersVersion(versions, "rabbitmq", version); addMockTestcontainersVersion(versions, "redpanda", version); addMockTestcontainersVersion(versions, "r2dbc", version); - addMockJacksonCoreVersion(versions, "jackson-annotations", version); + addMockJackson2CoreVersion(versions, "jackson-annotations", version); addMockJacksonCoreVersion(versions, "jackson-core", version); addMockJacksonCoreVersion(versions, "jackson-databind", version); versions.put("org.apache.pulsar:pulsar-client-api", version); versions.put("org.apache.pulsar:pulsar-client-reactive-api", version); - versions.put("com.fasterxml.jackson.dataformat:jackson-dataformat-xml", version); + versions.put("tools.jackson.dataformat:jackson-dataformat-xml", version); return versions; } @@ -297,8 +297,12 @@ class AntoraAsciidocAttributesTests { versions.put("org.testcontainers:" + artifactId, version); } - private void addMockJacksonCoreVersion(Map versions, String artifactId, String version) { + private void addMockJackson2CoreVersion(Map versions, String artifactId, String version) { versions.put("com.fasterxml.jackson.core:" + artifactId, version); } + private void addMockJacksonCoreVersion(Map versions, String artifactId, String version) { + versions.put("tools.jackson.core:" + artifactId, version); + } + } diff --git a/buildpack/spring-boot-buildpack-platform/build.gradle b/buildpack/spring-boot-buildpack-platform/build.gradle index f1c2d13b57b..a0f7eae75ee 100644 --- a/buildpack/spring-boot-buildpack-platform/build.gradle +++ b/buildpack/spring-boot-buildpack-platform/build.gradle @@ -28,13 +28,12 @@ dependencies { dockerTestRuntimeOnly("org.testcontainers:testcontainers") - implementation("com.fasterxml.jackson.core:jackson-databind") - implementation("com.fasterxml.jackson.module:jackson-module-parameter-names") implementation("net.java.dev.jna:jna-platform") implementation("org.apache.commons:commons-compress") implementation("org.apache.httpcomponents.client5:httpclient5") implementation("org.springframework:spring-core") implementation("org.tomlj:tomlj:1.0.0") + implementation("tools.jackson.core:jackson-databind") testImplementation(project(":test-support:spring-boot-test-support")) } diff --git a/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuilderMetadata.java b/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuilderMetadata.java index 7796fcef532..57758d9c80e 100644 --- a/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuilderMetadata.java +++ b/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuilderMetadata.java @@ -23,10 +23,10 @@ import java.util.Collections; import java.util.List; import java.util.function.Consumer; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; import org.jspecify.annotations.Nullable; +import tools.jackson.core.JacksonException; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.node.ObjectNode; import org.springframework.boot.buildpack.platform.docker.type.Image; import org.springframework.boot.buildpack.platform.docker.type.ImageConfig; @@ -152,7 +152,7 @@ class BuilderMetadata extends MappedObject { String json = SharedObjectMapper.get().writeValueAsString(getNode()); update.withLabel(LABEL_NAME, json); } - catch (JsonProcessingException ex) { + catch (JacksonException ex) { throw new IllegalStateException(ex); } } @@ -239,7 +239,7 @@ class BuilderMetadata extends MappedObject { RunImage(JsonNode node) { super(node, MethodHandles.lookup()); this.image = extractImage(); - this.mirrors = childrenAt("/mirrors", JsonNode::asText); + this.mirrors = childrenAt("/mirrors", JsonNode::asString); } private String extractImage() { @@ -352,7 +352,7 @@ class BuilderMetadata extends MappedObject { private final ObjectNode copy; private Update(BuilderMetadata source) { - this.copy = source.getNode().deepCopy(); + this.copy = (ObjectNode) source.getNode().deepCopy(); } private BuilderMetadata run(Consumer update) { diff --git a/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildpackLayersMetadata.java b/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildpackLayersMetadata.java index 3fdaaec007b..a76b824b9b5 100644 --- a/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildpackLayersMetadata.java +++ b/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildpackLayersMetadata.java @@ -21,8 +21,8 @@ import java.lang.invoke.MethodHandles; import java.util.HashMap; import java.util.Map; -import com.fasterxml.jackson.databind.JsonNode; import org.jspecify.annotations.Nullable; +import tools.jackson.databind.JsonNode; import org.springframework.boot.buildpack.platform.docker.type.Image; import org.springframework.boot.buildpack.platform.docker.type.ImageConfig; diff --git a/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildpackMetadata.java b/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildpackMetadata.java index 06745c37df7..aae597b00be 100644 --- a/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildpackMetadata.java +++ b/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/build/BuildpackMetadata.java @@ -19,8 +19,8 @@ package org.springframework.boot.buildpack.platform.build; import java.io.IOException; import java.lang.invoke.MethodHandles; -import com.fasterxml.jackson.databind.JsonNode; import org.jspecify.annotations.Nullable; +import tools.jackson.databind.JsonNode; import org.springframework.boot.buildpack.platform.docker.type.Image; import org.springframework.boot.buildpack.platform.docker.type.ImageConfig; diff --git a/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/DockerApi.java b/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/DockerApi.java index 6e4ac7111ae..a08789a94e9 100644 --- a/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/DockerApi.java +++ b/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/DockerApi.java @@ -17,6 +17,7 @@ package org.springframework.boot.buildpack.platform.docker; import java.io.IOException; +import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; import java.util.Arrays; @@ -286,10 +287,13 @@ public class DockerApi { listener.onStart(); try { try (Response response = http().post(loadUri, "application/x-tar", archive::writeTo)) { - jsonStream().get(response.getContent(), LoadImageUpdateEvent.class, (event) -> { - streamListener.onUpdate(event); - listener.onUpdate(event); - }); + InputStream content = response.getContent(); + if (content != null) { + jsonStream().get(content, LoadImageUpdateEvent.class, (event) -> { + streamListener.onUpdate(event); + listener.onUpdate(event); + }); + } } streamListener.assertValidResponseReceived(); } @@ -395,7 +399,7 @@ public class DockerApi { : buildUrl("/containers/create"); try (Response response = http().post(createUri, "application/json", config::writeTo)) { return ContainerReference - .of(SharedObjectMapper.get().readTree(response.getContent()).at("/Id").asText()); + .of(SharedObjectMapper.get().readTree(response.getContent()).at("/Id").asString()); } } diff --git a/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/Credential.java b/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/Credential.java index 7248eeeff52..48f4b1e93b1 100644 --- a/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/Credential.java +++ b/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/Credential.java @@ -18,8 +18,8 @@ package org.springframework.boot.buildpack.platform.docker.configuration; import java.lang.invoke.MethodHandles; -import com.fasterxml.jackson.databind.JsonNode; import org.jspecify.annotations.Nullable; +import tools.jackson.databind.JsonNode; import org.springframework.boot.buildpack.platform.json.MappedObject; import org.springframework.util.Assert; diff --git a/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfigurationMetadata.java b/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfigurationMetadata.java index 44c8e5c9c49..3ffbbcc59d4 100644 --- a/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfigurationMetadata.java +++ b/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerConfigurationMetadata.java @@ -28,10 +28,10 @@ import java.util.HexFormat; import java.util.Map; import java.util.function.Supplier; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.NullNode; import org.jspecify.annotations.Nullable; +import tools.jackson.core.JacksonException; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.node.NullNode; import org.springframework.boot.buildpack.platform.json.MappedObject; import org.springframework.boot.buildpack.platform.json.SharedObjectMapper; @@ -120,7 +120,7 @@ final class DockerConfigurationMetadata { try { return DockerConfig.fromJson(readPathContent(path)); } - catch (JsonProcessingException ex) { + catch (JacksonException ex) { throw new IllegalStateException("Error parsing Docker configuration file '" + path + "'", ex); } } @@ -142,7 +142,7 @@ final class DockerConfigurationMetadata { } return context; } - catch (JsonProcessingException ex) { + catch (JacksonException ex) { throw new IllegalStateException("Error parsing Docker context metadata file '" + metaPath + "'", ex); } } @@ -181,7 +181,7 @@ final class DockerConfigurationMetadata { super(node, MethodHandles.lookup()); this.currentContext = valueAt("/currentContext", String.class); this.credsStore = valueAt("/credsStore", String.class); - this.credHelpers = mapAt("/credHelpers", JsonNode::textValue); + this.credHelpers = mapAt("/credHelpers", JsonNode::stringValue); this.auths = mapAt("/auths", Auth::new); } @@ -201,7 +201,7 @@ final class DockerConfigurationMetadata { return this.auths; } - static DockerConfig fromJson(String json) throws JsonProcessingException { + static DockerConfig fromJson(String json) { return new DockerConfig(SharedObjectMapper.get().readTree(json)); } @@ -285,7 +285,7 @@ final class DockerConfigurationMetadata { return new DockerContext(this.getNode(), tlsPath); } - static DockerContext fromJson(String json) throws JsonProcessingException { + static DockerContext fromJson(String json) { return new DockerContext(SharedObjectMapper.get().readTree(json), null); } diff --git a/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/JsonEncodedDockerRegistryAuthentication.java b/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/JsonEncodedDockerRegistryAuthentication.java index a6bf998a426..56c5da30907 100644 --- a/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/JsonEncodedDockerRegistryAuthentication.java +++ b/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/configuration/JsonEncodedDockerRegistryAuthentication.java @@ -19,8 +19,8 @@ package org.springframework.boot.buildpack.platform.docker.configuration; import java.util.Base64; import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.core.JsonProcessingException; import org.jspecify.annotations.Nullable; +import tools.jackson.core.JacksonException; import org.springframework.boot.buildpack.platform.json.SharedObjectMapper; @@ -44,7 +44,7 @@ class JsonEncodedDockerRegistryAuthentication implements DockerRegistryAuthentic try { this.authHeader = Base64.getUrlEncoder().encodeToString(SharedObjectMapper.get().writeValueAsBytes(this)); } - catch (JsonProcessingException ex) { + catch (JacksonException ex) { throw new IllegalStateException("Error creating Docker registry authentication header", ex); } } diff --git a/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/HttpClientTransport.java b/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/HttpClientTransport.java index 6201160ed2c..0431a28c3be 100644 --- a/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/HttpClientTransport.java +++ b/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/transport/HttpClientTransport.java @@ -38,6 +38,7 @@ import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.io.entity.AbstractHttpEntity; import org.jspecify.annotations.Nullable; +import tools.jackson.core.JacksonException; import org.springframework.boot.buildpack.platform.io.Content; import org.springframework.boot.buildpack.platform.io.IOConsumer; @@ -196,7 +197,7 @@ abstract class HttpClientTransport implements HttpTransport { try { return SharedObjectMapper.get().readValue(content, Errors.class); } - catch (IOException ex) { + catch (JacksonException ex) { return null; } } @@ -209,7 +210,7 @@ abstract class HttpClientTransport implements HttpTransport { Message message = SharedObjectMapper.get().readValue(content, Message.class); return (message.getMessage() != null) ? message : null; } - catch (IOException ex) { + catch (JacksonException ex) { return null; } } diff --git a/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/BlobReference.java b/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/BlobReference.java index 6f40653e3c6..3d7cd1e0885 100644 --- a/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/BlobReference.java +++ b/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/BlobReference.java @@ -18,7 +18,7 @@ package org.springframework.boot.buildpack.platform.docker.type; import java.lang.invoke.MethodHandles; -import com.fasterxml.jackson.databind.JsonNode; +import tools.jackson.databind.JsonNode; import org.springframework.boot.buildpack.platform.json.MappedObject; import org.springframework.util.Assert; diff --git a/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ContainerConfig.java b/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ContainerConfig.java index 241f9264686..64bafb9b65a 100644 --- a/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ContainerConfig.java +++ b/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ContainerConfig.java @@ -26,10 +26,10 @@ import java.util.List; import java.util.Map; import java.util.function.Consumer; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.fasterxml.jackson.databind.node.ObjectNode; import org.jspecify.annotations.Nullable; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.node.ArrayNode; +import tools.jackson.databind.node.ObjectNode; import org.springframework.boot.buildpack.platform.json.SharedObjectMapper; import org.springframework.util.Assert; @@ -51,7 +51,7 @@ public class ContainerConfig { ContainerConfig(@Nullable String user, ImageReference image, String command, List args, Map labels, List bindings, Map env, @Nullable String networkMode, - List securityOptions) throws IOException { + List securityOptions) { Assert.notNull(image, "'image' must not be null"); Assert.hasText(command, "'command' must not be empty"); ObjectMapper objectMapper = SharedObjectMapper.get(); @@ -135,14 +135,9 @@ public class ContainerConfig { private ContainerConfig run(Consumer update) { update.accept(this); - try { - Assert.state(this.command != null, "'command' must not be null"); - return new ContainerConfig(this.user, this.image, this.command, this.args, this.labels, this.bindings, - this.env, this.networkMode, this.securityOptions); - } - catch (IOException ex) { - throw new IllegalStateException(ex); - } + Assert.state(this.command != null, "'command' must not be null"); + return new ContainerConfig(this.user, this.image, this.command, this.args, this.labels, this.bindings, + this.env, this.networkMode, this.securityOptions); } /** diff --git a/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ContainerStatus.java b/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ContainerStatus.java index caa02007bd1..5737f26def6 100644 --- a/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ContainerStatus.java +++ b/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ContainerStatus.java @@ -20,9 +20,9 @@ import java.io.IOException; import java.io.InputStream; import java.lang.invoke.MethodHandles; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.NullNode; import org.jspecify.annotations.Nullable; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.node.NullNode; import org.springframework.boot.buildpack.platform.json.MappedObject; import org.springframework.util.Assert; diff --git a/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/Image.java b/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/Image.java index 42930a2526d..0ad0c67d5d7 100644 --- a/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/Image.java +++ b/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/Image.java @@ -23,8 +23,8 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; -import com.fasterxml.jackson.databind.JsonNode; import org.jspecify.annotations.Nullable; +import tools.jackson.databind.JsonNode; import org.springframework.boot.buildpack.platform.json.MappedObject; import org.springframework.util.StringUtils; @@ -54,7 +54,7 @@ public class Image extends MappedObject { Image(JsonNode node) { super(node, MethodHandles.lookup()); - this.digests = childrenAt("/RepoDigests", JsonNode::asText); + this.digests = childrenAt("/RepoDigests", JsonNode::asString); this.config = new ImageConfig(getNode().at("/Config")); this.layers = extractLayers(valueAt("/RootFS/Layers", String[].class)); this.os = valueAt("/Os", String.class); diff --git a/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageArchive.java b/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageArchive.java index df181d35528..415bc21403c 100644 --- a/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageArchive.java +++ b/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageArchive.java @@ -29,11 +29,11 @@ import java.util.Collections; import java.util.List; import java.util.function.Consumer; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.fasterxml.jackson.databind.node.ObjectNode; import org.jspecify.annotations.Nullable; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.node.ArrayNode; +import tools.jackson.databind.node.ObjectNode; import org.springframework.boot.buildpack.platform.io.Content; import org.springframework.boot.buildpack.platform.io.IOConsumer; @@ -173,11 +173,11 @@ public class ImageArchive implements TarArchive { private ObjectNode createConfig(List writtenLayers) { ObjectNode config = this.objectMapper.createObjectNode(); config.set("Config", this.imageConfig.getNodeCopy()); - config.set("Created", config.textNode(getCreatedDate())); + config.set("Created", config.stringNode(getCreatedDate())); config.set("History", createHistory(writtenLayers)); - config.set("Os", config.textNode(this.os)); - config.set("Architecture", config.textNode(this.architecture)); - config.set("Variant", config.textNode(this.variant)); + config.set("Os", config.stringNode(this.os)); + config.set("Architecture", config.stringNode(this.architecture)); + config.set("Variant", config.stringNode(this.variant)); config.set("RootFS", createRootFs(writtenLayers)); return config; } @@ -212,7 +212,7 @@ public class ImageArchive implements TarArchive { private ArrayNode createManifest(String config, List writtenLayers) { ArrayNode manifest = this.objectMapper.createArrayNode(); ObjectNode entry = manifest.addObject(); - entry.set("Config", entry.textNode(config)); + entry.set("Config", entry.stringNode(config)); entry.set("Layers", getManifestLayers(writtenLayers)); if (this.tag != null) { entry.set("RepoTags", entry.arrayNode().add(this.tag.toString())); diff --git a/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageArchiveIndex.java b/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageArchiveIndex.java index f18efb88411..94106c7a607 100644 --- a/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageArchiveIndex.java +++ b/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageArchiveIndex.java @@ -21,7 +21,7 @@ import java.io.InputStream; import java.lang.invoke.MethodHandles; import java.util.List; -import com.fasterxml.jackson.databind.JsonNode; +import tools.jackson.databind.JsonNode; import org.springframework.boot.buildpack.platform.json.MappedObject; import org.springframework.util.Assert; diff --git a/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageArchiveManifest.java b/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageArchiveManifest.java index 1bf6569c518..5d8563e115f 100644 --- a/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageArchiveManifest.java +++ b/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageArchiveManifest.java @@ -22,7 +22,7 @@ import java.lang.invoke.MethodHandles; import java.util.Collections; import java.util.List; -import com.fasterxml.jackson.databind.JsonNode; +import tools.jackson.databind.JsonNode; import org.springframework.boot.buildpack.platform.json.MappedObject; diff --git a/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageConfig.java b/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageConfig.java index bfb1d34ab79..069b2ec9215 100644 --- a/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageConfig.java +++ b/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageConfig.java @@ -22,9 +22,9 @@ import java.util.LinkedHashMap; import java.util.Map; import java.util.function.Consumer; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; import org.jspecify.annotations.Nullable; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.node.ObjectNode; import org.springframework.boot.buildpack.platform.json.MappedObject; @@ -111,7 +111,7 @@ public class ImageConfig extends MappedObject { private final ObjectNode copy; private Update(ImageConfig source) { - this.copy = source.getNode().deepCopy(); + this.copy = (ObjectNode) source.getNode().deepCopy(); } private ImageConfig run(Consumer update) { diff --git a/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/Manifest.java b/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/Manifest.java index 77738525d71..2a44062e53d 100644 --- a/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/Manifest.java +++ b/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/Manifest.java @@ -21,8 +21,8 @@ import java.io.InputStream; import java.lang.invoke.MethodHandles; import java.util.List; -import com.fasterxml.jackson.databind.JsonNode; import org.jspecify.annotations.Nullable; +import tools.jackson.databind.JsonNode; import org.springframework.boot.buildpack.platform.json.MappedObject; import org.springframework.util.Assert; diff --git a/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ManifestList.java b/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ManifestList.java index 8dab9fb3203..fb6a31fc1e4 100644 --- a/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ManifestList.java +++ b/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ManifestList.java @@ -22,8 +22,8 @@ import java.lang.invoke.MethodHandles; import java.util.List; import java.util.stream.Stream; -import com.fasterxml.jackson.databind.JsonNode; import org.jspecify.annotations.Nullable; +import tools.jackson.databind.JsonNode; import org.springframework.boot.buildpack.platform.json.MappedObject; import org.springframework.util.Assert; diff --git a/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/json/JsonStream.java b/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/json/JsonStream.java index bae1a9ab6c7..e326dc6b59a 100644 --- a/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/json/JsonStream.java +++ b/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/json/JsonStream.java @@ -20,12 +20,11 @@ import java.io.IOException; import java.io.InputStream; import java.util.function.Consumer; -import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ObjectNode; import org.jspecify.annotations.Nullable; +import tools.jackson.core.JsonParser; +import tools.jackson.core.JsonToken; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.node.ObjectNode; /** * Utility class that allows JSON to be parsed and processed as it's received. @@ -64,8 +63,7 @@ public class JsonStream { * @throws IOException on IO error */ public void get(InputStream content, Class type, Consumer consumer) throws IOException { - JsonFactory jsonFactory = this.objectMapper.getFactory(); - try (JsonParser parser = jsonFactory.createParser(content)) { + try (JsonParser parser = this.objectMapper.createParser(content)) { while (!parser.isClosed()) { JsonToken token = parser.nextToken(); if (token != null && token != JsonToken.END_OBJECT) { @@ -79,9 +77,9 @@ public class JsonStream { } @SuppressWarnings("unchecked") - private @Nullable T read(JsonParser parser, Class type) throws IOException { + private @Nullable T read(JsonParser parser, Class type) { if (ObjectNode.class.isAssignableFrom(type)) { - ObjectNode node = this.objectMapper.readTree(parser); + ObjectNode node = (ObjectNode) this.objectMapper.readTree(parser); if (node == null || node.isMissingNode() || node.isEmpty()) { return null; } diff --git a/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/json/MappedObject.java b/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/json/MappedObject.java index ad3494a4273..9c2a6e3f51d 100644 --- a/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/json/MappedObject.java +++ b/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/json/MappedObject.java @@ -30,9 +30,10 @@ import java.util.List; import java.util.Map; import java.util.function.Function; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; import org.jspecify.annotations.Nullable; +import tools.jackson.core.JacksonException; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.ObjectMapper; import org.springframework.util.Assert; import org.springframework.util.StreamUtils; @@ -111,7 +112,7 @@ public class MappedObject { return Collections.emptyList(); } List children = new ArrayList<>(); - node.elements().forEachRemaining((childNode) -> children.add(factory.apply(childNode))); + node.values().forEach((childNode) -> children.add(factory.apply(childNode))); return Collections.unmodifiableList(children); } @@ -146,7 +147,7 @@ public class MappedObject { try { return SharedObjectMapper.get().treeToValue(result, type); } - catch (IOException ex) { + catch (JacksonException ex) { throw new IllegalStateException(ex); } } diff --git a/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/json/SharedObjectMapper.java b/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/json/SharedObjectMapper.java index 9527a3893a0..c16d48a4c9a 100644 --- a/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/json/SharedObjectMapper.java +++ b/buildpack/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/json/SharedObjectMapper.java @@ -16,11 +16,12 @@ package org.springframework.boot.buildpack.platform.json; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.PropertyNamingStrategies; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.module.paramnames.ParameterNamesModule; +import tools.jackson.core.json.JsonWriteFeature; +import tools.jackson.databind.DeserializationFeature; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.PropertyNamingStrategies; +import tools.jackson.databind.SerializationFeature; +import tools.jackson.databind.json.JsonMapper; /** * Provides access to a shared pre-configured {@link ObjectMapper}. @@ -33,12 +34,12 @@ public final class SharedObjectMapper { private static final ObjectMapper INSTANCE; static { - ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.registerModule(new ParameterNamesModule()); - objectMapper.enable(SerializationFeature.INDENT_OUTPUT); - objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - objectMapper.setPropertyNamingStrategy(PropertyNamingStrategies.LOWER_CAMEL_CASE); - INSTANCE = objectMapper; + INSTANCE = JsonMapper.builder() + .enable(SerializationFeature.INDENT_OUTPUT) + .disable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS) + .disable(JsonWriteFeature.ESCAPE_FORWARD_SLASHES) + .propertyNamingStrategy(PropertyNamingStrategies.LOWER_CAMEL_CASE) + .build(); } private SharedObjectMapper() { diff --git a/buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/LifecycleTests.java b/buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/LifecycleTests.java index c395af596f2..89d24e2a3ac 100644 --- a/buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/LifecycleTests.java +++ b/buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/LifecycleTests.java @@ -26,9 +26,6 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ArrayNode; import com.sun.jna.Platform; import org.json.JSONException; import org.json.JSONObject; @@ -37,6 +34,8 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.mockito.stubbing.Answer; import org.skyscreamer.jsonassert.JSONAssert; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.node.ArrayNode; import org.springframework.boot.buildpack.platform.docker.DockerApi; import org.springframework.boot.buildpack.platform.docker.DockerApi.ContainerApi; @@ -482,7 +481,7 @@ class LifecycleTests { return (invocation) -> { ContainerConfig config = invocation.getArgument(0, ContainerConfig.class); ArrayNode command = getCommand(config); - String name = command.get(0).asText().substring(1).replaceAll("/", "-"); + String name = command.get(0).asString().substring(1).replaceAll("/", "-"); this.configs.put(name, config); if (invocation.getArguments().length > 2) { this.content.put(name, invocation.getArgument(2, ContainerContent.class)); @@ -491,7 +490,7 @@ class LifecycleTests { }; } - private ArrayNode getCommand(ContainerConfig config) throws JsonProcessingException { + private ArrayNode getCommand(ContainerConfig config) { JsonNode node = SharedObjectMapper.get().readTree(config.toString()); return (ArrayNode) node.at("/Cmd"); } diff --git a/buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerRegistryConfigAuthenticationTests.java b/buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerRegistryConfigAuthenticationTests.java index fcd63906522..53664283536 100644 --- a/buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerRegistryConfigAuthenticationTests.java +++ b/buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/configuration/DockerRegistryConfigAuthenticationTests.java @@ -24,10 +24,10 @@ import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; -import com.fasterxml.jackson.core.type.TypeReference; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import tools.jackson.core.type.TypeReference; import org.springframework.boot.buildpack.platform.docker.type.ImageReference; import org.springframework.boot.buildpack.platform.json.SharedObjectMapper; diff --git a/buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageArchiveIndexTests.java b/buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageArchiveIndexTests.java index e66e5affcc0..a08459bf3cf 100644 --- a/buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageArchiveIndexTests.java +++ b/buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageArchiveIndexTests.java @@ -16,8 +16,6 @@ package org.springframework.boot.buildpack.platform.docker.type; -import java.io.IOException; - import org.junit.jupiter.api.Test; import org.springframework.boot.buildpack.platform.json.AbstractJsonTests; @@ -32,7 +30,7 @@ import static org.assertj.core.api.Assertions.assertThat; class ImageArchiveIndexTests extends AbstractJsonTests { @Test - void loadJson() throws IOException { + void loadJson() { String content = getContentAsString("image-archive-index.json"); ImageArchiveIndex index = getIndex(content); assertThat(index.getSchemaVersion()).isEqualTo(2); @@ -43,7 +41,7 @@ class ImageArchiveIndexTests extends AbstractJsonTests { .isEqualTo("sha256:3bbe02431d8e5124ffe816ec27bf6508b50edd1d10218be1a03e799a186b9004"); } - private ImageArchiveIndex getIndex(String content) throws IOException { + private ImageArchiveIndex getIndex(String content) { return new ImageArchiveIndex(getObjectMapper().readTree(content)); } diff --git a/buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageArchiveManifestTests.java b/buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageArchiveManifestTests.java index b2124906d89..bad5d4a8fa4 100644 --- a/buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageArchiveManifestTests.java +++ b/buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageArchiveManifestTests.java @@ -16,7 +16,6 @@ package org.springframework.boot.buildpack.platform.docker.type; -import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -35,7 +34,7 @@ import static org.assertj.core.api.Assertions.assertThat; class ImageArchiveManifestTests extends AbstractJsonTests { @Test - void getLayersReturnsLayers() throws Exception { + void getLayersReturnsLayers() { String content = getContentAsString("image-archive-manifest.json"); ImageArchiveManifest manifest = getManifest(content); List expectedLayers = new ArrayList<>(); @@ -49,7 +48,7 @@ class ImageArchiveManifestTests extends AbstractJsonTests { } @Test - void getLayersWithNoLayersReturnsEmptyList() throws Exception { + void getLayersWithNoLayersReturnsEmptyList() { String content = "[{\"Layers\": []}]"; ImageArchiveManifest manifest = getManifest(content); assertThat(manifest.getEntries()).hasSize(1); @@ -57,13 +56,13 @@ class ImageArchiveManifestTests extends AbstractJsonTests { } @Test - void getLayersWithEmptyManifestReturnsEmptyList() throws Exception { + void getLayersWithEmptyManifestReturnsEmptyList() { String content = "[]"; ImageArchiveManifest manifest = getManifest(content); assertThat(manifest.getEntries()).isEmpty(); } - private ImageArchiveManifest getManifest(String content) throws IOException { + private ImageArchiveManifest getManifest(String content) { return new ImageArchiveManifest(getObjectMapper().readTree(content)); } diff --git a/buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageConfigTests.java b/buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageConfigTests.java index 8bea587eb61..6ccfe13d4b1 100644 --- a/buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageConfigTests.java +++ b/buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageConfigTests.java @@ -16,7 +16,6 @@ package org.springframework.boot.buildpack.platform.docker.type; -import java.io.IOException; import java.util.Map; import org.junit.jupiter.api.Test; @@ -35,7 +34,7 @@ import static org.assertj.core.api.Assertions.entry; class ImageConfigTests extends AbstractJsonTests { @Test - void getEnvContainsParsedValues() throws Exception { + void getEnvContainsParsedValues() { ImageConfig imageConfig = getImageConfig(); Map env = imageConfig.getEnv(); assertThat(env).contains(entry("PATH", "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"), @@ -44,28 +43,28 @@ class ImageConfigTests extends AbstractJsonTests { } @Test - void whenConfigHasNoEnvThenImageConfigEnvIsEmpty() throws Exception { + void whenConfigHasNoEnvThenImageConfigEnvIsEmpty() { ImageConfig imageConfig = getMinimalImageConfig(); Map env = imageConfig.getEnv(); assertThat(env).isEmpty(); } @Test - void whenConfigHasNoLabelsThenImageConfigLabelsIsEmpty() throws Exception { + void whenConfigHasNoLabelsThenImageConfigLabelsIsEmpty() { ImageConfig imageConfig = getMinimalImageConfig(); Map env = imageConfig.getLabels(); assertThat(env).isEmpty(); } @Test - void getLabelsReturnsLabels() throws Exception { + void getLabelsReturnsLabels() { ImageConfig imageConfig = getImageConfig(); Map labels = imageConfig.getLabels(); assertThat(labels).hasSize(4).contains(entry("io.buildpacks.stack.id", "org.cloudfoundry.stacks.cflinuxfs3")); } @Test - void updateWithLabelUpdatesLabels() throws Exception { + void updateWithLabelUpdatesLabels() { ImageConfig imageConfig = getImageConfig(); ImageConfig updatedImageConfig = imageConfig .copy((update) -> update.withLabel("io.buildpacks.stack.id", "test")); @@ -74,11 +73,11 @@ class ImageConfigTests extends AbstractJsonTests { assertThat(updatedImageConfig.getLabels()).hasSize(4).contains(entry("io.buildpacks.stack.id", "test")); } - private ImageConfig getImageConfig() throws IOException { + private ImageConfig getImageConfig() { return new ImageConfig(getObjectMapper().readTree(getContent("image-config.json"))); } - private ImageConfig getMinimalImageConfig() throws IOException { + private ImageConfig getMinimalImageConfig() { return new ImageConfig(getObjectMapper().readTree(getContent("minimal-image-config.json"))); } diff --git a/buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ManifestListTests.java b/buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ManifestListTests.java index d7d214e1dbe..59fec9275b1 100644 --- a/buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ManifestListTests.java +++ b/buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ManifestListTests.java @@ -16,8 +16,6 @@ package org.springframework.boot.buildpack.platform.docker.type; -import java.io.IOException; - import org.junit.jupiter.api.Test; import org.springframework.boot.buildpack.platform.json.AbstractJsonTests; @@ -32,7 +30,7 @@ import static org.assertj.core.api.Assertions.assertThat; class ManifestListTests extends AbstractJsonTests { @Test - void loadJsonFromDistributionManifestList() throws IOException { + void loadJsonFromDistributionManifestList() { String content = getContentAsString("distribution-manifest-list.json"); ManifestList manifestList = getManifestList(content); assertThat(manifestList.getSchemaVersion()).isEqualTo(2); @@ -40,7 +38,7 @@ class ManifestListTests extends AbstractJsonTests { assertThat(manifestList.getManifests()).hasSize(2); } - private ManifestList getManifestList(String content) throws IOException { + private ManifestList getManifestList(String content) { return new ManifestList(getObjectMapper().readTree(content)); } diff --git a/buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ManifestTests.java b/buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ManifestTests.java index 673cc557479..de948f30192 100644 --- a/buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ManifestTests.java +++ b/buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ManifestTests.java @@ -16,8 +16,6 @@ package org.springframework.boot.buildpack.platform.docker.type; -import java.io.IOException; - import org.junit.jupiter.api.Test; import org.springframework.boot.buildpack.platform.json.AbstractJsonTests; @@ -32,7 +30,7 @@ import static org.assertj.core.api.Assertions.assertThat; class ManifestTests extends AbstractJsonTests { @Test - void loadJsonFromDistributionManifest() throws IOException { + void loadJsonFromDistributionManifest() { String content = getContentAsString("distribution-manifest.json"); Manifest manifestList = getManifest(content); assertThat(manifestList.getSchemaVersion()).isEqualTo(2); @@ -41,7 +39,7 @@ class ManifestTests extends AbstractJsonTests { } @Test - void loadJsonFromImageManifest() throws IOException { + void loadJsonFromImageManifest() { String content = getContentAsString("image-manifest.json"); Manifest manifestList = getManifest(content); assertThat(manifestList.getSchemaVersion()).isEqualTo(2); @@ -49,7 +47,7 @@ class ManifestTests extends AbstractJsonTests { assertThat(manifestList.getLayers()).hasSize(1); } - private Manifest getManifest(String content) throws IOException { + private Manifest getManifest(String content) { return new Manifest(getObjectMapper().readTree(content)); } diff --git a/buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/json/AbstractJsonTests.java b/buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/json/AbstractJsonTests.java index 035d6e381ce..b0b70e96ea5 100644 --- a/buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/json/AbstractJsonTests.java +++ b/buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/json/AbstractJsonTests.java @@ -24,7 +24,7 @@ import java.io.UncheckedIOException; import java.nio.charset.StandardCharsets; import java.util.stream.Collectors; -import com.fasterxml.jackson.databind.ObjectMapper; +import tools.jackson.databind.ObjectMapper; import static org.assertj.core.api.Assertions.assertThat; diff --git a/buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/json/JsonStreamTests.java b/buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/json/JsonStreamTests.java index 2bfbab4a090..7db3598b643 100644 --- a/buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/json/JsonStreamTests.java +++ b/buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/json/JsonStreamTests.java @@ -20,8 +20,8 @@ import java.util.ArrayList; import java.util.List; import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.databind.node.ObjectNode; import org.junit.jupiter.api.Test; +import tools.jackson.databind.node.ObjectNode; import static org.assertj.core.api.Assertions.assertThat; @@ -44,7 +44,7 @@ class JsonStreamTests extends AbstractJsonTests { List result = new ArrayList<>(); this.jsonStream.get(getContent("stream.json"), result::add); assertThat(result).hasSize(595); - assertThat(result.get(594).toString()) + assertThat(result.get(594).get("status").asString()) .contains("Status: Downloaded newer image for paketo-buildpacks/cnb:base"); } diff --git a/buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/json/MappedObjectTests.java b/buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/json/MappedObjectTests.java index edea6c1b135..2d5d9068d92 100644 --- a/buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/json/MappedObjectTests.java +++ b/buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/json/MappedObjectTests.java @@ -20,8 +20,8 @@ import java.io.IOException; import java.io.InputStream; import java.lang.invoke.MethodHandles; -import com.fasterxml.jackson.databind.JsonNode; import org.junit.jupiter.api.Test; +import tools.jackson.databind.JsonNode; import org.springframework.boot.buildpack.platform.json.MappedObjectTests.TestMappedObject.Person; diff --git a/buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/json/SharedObjectMapperTests.java b/buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/json/SharedObjectMapperTests.java index 447fbc402ae..3ce2d61685a 100644 --- a/buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/json/SharedObjectMapperTests.java +++ b/buildpack/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/json/SharedObjectMapperTests.java @@ -16,12 +16,11 @@ package org.springframework.boot.buildpack.platform.json; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.PropertyNamingStrategies; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.module.paramnames.ParameterNamesModule; import org.junit.jupiter.api.Test; +import tools.jackson.databind.DeserializationFeature; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.PropertyNamingStrategies; +import tools.jackson.databind.SerializationFeature; import static org.assertj.core.api.Assertions.assertThat; @@ -36,14 +35,14 @@ class SharedObjectMapperTests { void getReturnsConfiguredObjectMapper() { ObjectMapper mapper = SharedObjectMapper.get(); assertThat(mapper).isNotNull(); - assertThat(mapper.getRegisteredModuleIds()).contains(new ParameterNamesModule().getTypeId()); - assertThat(SerializationFeature.INDENT_OUTPUT - .enabledIn(mapper.getSerializationConfig().getSerializationFeatures())).isTrue(); + assertThat( + SerializationFeature.INDENT_OUTPUT.enabledIn(mapper.serializationConfig().getSerializationFeatures())) + .isTrue(); assertThat(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES - .enabledIn(mapper.getDeserializationConfig().getDeserializationFeatures())).isFalse(); - assertThat(mapper.getSerializationConfig().getPropertyNamingStrategy()) + .enabledIn(mapper.deserializationConfig().getDeserializationFeatures())).isFalse(); + assertThat(mapper.serializationConfig().getPropertyNamingStrategy()) .isEqualTo(PropertyNamingStrategies.LOWER_CAMEL_CASE); - assertThat(mapper.getDeserializationConfig().getPropertyNamingStrategy()) + assertThat(mapper.deserializationConfig().getPropertyNamingStrategy()) .isEqualTo(PropertyNamingStrategies.LOWER_CAMEL_CASE); } diff --git a/core/spring-boot-autoconfigure/build.gradle b/core/spring-boot-autoconfigure/build.gradle index 3a45eca49a4..8382548e338 100644 --- a/core/spring-boot-autoconfigure/build.gradle +++ b/core/spring-boot-autoconfigure/build.gradle @@ -45,10 +45,10 @@ dependencies { testImplementation(project(":test-support:spring-boot-test-support")) testImplementation(testFixtures(project(":core:spring-boot"))) testImplementation("ch.qos.logback:logback-classic") - testImplementation("com.fasterxml.jackson.core:jackson-databind") testImplementation("io.projectreactor:reactor-core") testImplementation("org.springframework:spring-context-support") testImplementation("org.springframework.security:spring-security-config") + testImplementation("tools.jackson.core:jackson-databind") testRuntimeOnly("com.github.ben-manes.caffeine:caffeine") testRuntimeOnly("org.springframework:spring-webflux") diff --git a/core/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/aop/NonAspectJAopAutoConfigurationTests.java b/core/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/aop/NonAspectJAopAutoConfigurationTests.java index a6f5cfb47d4..8922fce059a 100644 --- a/core/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/aop/NonAspectJAopAutoConfigurationTests.java +++ b/core/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/aop/NonAspectJAopAutoConfigurationTests.java @@ -22,6 +22,8 @@ import org.springframework.aop.config.AopConfigUtils; import org.springframework.aop.framework.autoproxy.AutoProxyUtils; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener; +import org.springframework.boot.logging.LogLevel; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.testsupport.classpath.ClassPathExclusions; @@ -40,16 +42,18 @@ class NonAspectJAopAutoConfigurationTests { @Test void whenAspectJIsAbsentAndProxyTargetClassIsEnabledProxyCreatorBeanIsDefined() { - this.contextRunner.run((context) -> { - BeanDefinition defaultProxyConfig = context.getBeanFactory() - .getBeanDefinition(AutoProxyUtils.DEFAULT_PROXY_CONFIG_BEAN_NAME); - assertThat(defaultProxyConfig.getPropertyValues().get("proxyTargetClass")).isEqualTo(Boolean.TRUE); - }); + this.contextRunner.withInitializer(ConditionEvaluationReportLoggingListener.forLogLevel(LogLevel.INFO)) + .run((context) -> { + BeanDefinition defaultProxyConfig = context.getBeanFactory() + .getBeanDefinition(AutoProxyUtils.DEFAULT_PROXY_CONFIG_BEAN_NAME); + assertThat(defaultProxyConfig.getPropertyValues().get("proxyTargetClass")).isEqualTo(Boolean.TRUE); + }); } @Test void whenAspectJIsAbsentAndProxyTargetClassIsDisabledNoProxyCreatorBeanIsDefined() { - this.contextRunner.withPropertyValues("spring.aop.proxy-target-class:false") + this.contextRunner.withInitializer(ConditionEvaluationReportLoggingListener.forLogLevel(LogLevel.INFO)) + .withPropertyValues("spring.aop.proxy-target-class:false") .run((context) -> assertThat(context).doesNotHaveBean(AutoProxyUtils.DEFAULT_PROXY_CONFIG_BEAN_NAME) .doesNotHaveBean(AopConfigUtils.AUTO_PROXY_CREATOR_BEAN_NAME)); } diff --git a/core/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/OnBeanConditionTypeDeductionFailureTests.java b/core/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/OnBeanConditionTypeDeductionFailureTests.java index 1d309b260a6..33b8d21a899 100644 --- a/core/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/OnBeanConditionTypeDeductionFailureTests.java +++ b/core/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/OnBeanConditionTypeDeductionFailureTests.java @@ -16,8 +16,8 @@ package org.springframework.boot.autoconfigure.condition; -import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; +import tools.jackson.databind.ObjectMapper; import org.springframework.boot.autoconfigure.condition.OnBeanCondition.BeanTypeDeductionException; import org.springframework.boot.testsupport.classpath.ClassPathExclusions; diff --git a/core/spring-boot-docker-compose/build.gradle b/core/spring-boot-docker-compose/build.gradle index 451b698b5b8..beba50a452d 100644 --- a/core/spring-boot-docker-compose/build.gradle +++ b/core/spring-boot-docker-compose/build.gradle @@ -28,8 +28,7 @@ description = "Spring Boot Docker Compose" dependencies { api(project(":core:spring-boot-autoconfigure")) - implementation("com.fasterxml.jackson.core:jackson-databind") - implementation("com.fasterxml.jackson.module:jackson-module-parameter-names") + implementation("tools.jackson.core:jackson-databind") dockerTestImplementation(project(":test-support:spring-boot-docker-test-support")) diff --git a/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerJson.java b/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerJson.java index 0039a3186af..3ec245dfc77 100644 --- a/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerJson.java +++ b/core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerJson.java @@ -16,16 +16,14 @@ package org.springframework.boot.docker.compose.core; -import java.io.IOException; import java.util.List; import java.util.Locale; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.JavaType; -import com.fasterxml.jackson.databind.MapperFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.json.JsonMapper; -import com.fasterxml.jackson.module.paramnames.ParameterNamesModule; +import tools.jackson.databind.DeserializationFeature; +import tools.jackson.databind.JavaType; +import tools.jackson.databind.MapperFeature; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; /** * Support class used to handle JSON returned from the {@link DockerCli}. @@ -40,7 +38,6 @@ final class DockerJson { .defaultLocale(Locale.ENGLISH) .enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES) .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) - .addModule(new ParameterNamesModule()) .build(); private DockerJson() { @@ -74,12 +71,7 @@ final class DockerJson { } private static T deserialize(String json, JavaType type) { - try { - return objectMapper.readValue(json.trim(), type); - } - catch (IOException ex) { - throw new DockerOutputParseException(json, ex); - } + return objectMapper.readValue(json.trim(), type); } } diff --git a/core/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/ConnectionNamePredicateTests.java b/core/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/ConnectionNamePredicateTests.java index bc527a4747a..16f11ac79c8 100644 --- a/core/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/ConnectionNamePredicateTests.java +++ b/core/spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/ConnectionNamePredicateTests.java @@ -64,6 +64,7 @@ class ConnectionNamePredicateTests { assertThat(predicateOf("redis")).accepts(sourceOf("myhost.com/library/redis")); assertThat(predicateOf("redis")).accepts(sourceOf("myhost.com:8080/library/redis")); assertThat(predicateOf("redis")).rejects(sourceOf("internalhost:8080/redis")); + assertThat(predicateOf("redis")).accepts(sourceOf("docker.my-company.com/library/redis:latest")); } @Test diff --git a/core/spring-boot-test/build.gradle b/core/spring-boot-test/build.gradle index d313430a47c..9bd3a3d4f17 100644 --- a/core/spring-boot-test/build.gradle +++ b/core/spring-boot-test/build.gradle @@ -27,7 +27,6 @@ dependencies { api(project(":core:spring-boot")) api("org.springframework:spring-test") - optional("com.fasterxml.jackson.core:jackson-databind") optional("com.google.code.gson:gson") optional("com.jayway.jsonpath:json-path") optional("io.projectreactor.netty:reactor-netty-http") @@ -44,6 +43,7 @@ dependencies { optional("org.springframework:spring-web") optional("org.springframework:spring-webflux") optional("org.springframework.graphql:spring-graphql-test") + 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/JacksonTester.java b/core/spring-boot-test/src/main/java/org/springframework/boot/test/json/JacksonTester.java index def26ddc435..b2cfbc0e80e 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 @@ -18,16 +18,29 @@ package org.springframework.boot.test.json; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.io.Reader; +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; -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 com.jayway.jsonpath.InvalidJsonException; +import com.jayway.jsonpath.TypeRef; +import com.jayway.jsonpath.spi.json.AbstractJsonProvider; +import com.jayway.jsonpath.spi.mapper.MappingException; +import com.jayway.jsonpath.spi.mapper.MappingProvider; import org.jspecify.annotations.Nullable; +import tools.jackson.core.JacksonException; +import tools.jackson.core.JsonGenerator; +import tools.jackson.databind.JavaType; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.ObjectReader; +import tools.jackson.databind.ObjectWriter; +import tools.jackson.databind.json.JsonMapper; import org.springframework.beans.factory.ObjectFactory; import org.springframework.core.ResolvableType; @@ -35,14 +48,14 @@ import org.springframework.util.Assert; /** * AssertJ based JSON tester backed by Jackson. Usually instantiated via - * {@link #initFields(Object, ObjectMapper)}, for example:
+ * {@link #initFields(Object, JsonMapper)}, for example: 
  * public class ExampleObjectJsonTests {
  *
  *     private JacksonTester<ExampleObject> json;
  *
  *     @Before
  *     public void setup() {
- *         ObjectMapper objectMapper = new ObjectMapper();
+ *         JsonMapper jsonMapper = new JsonMapper();
  *         JacksonTester.initFields(this, objectMapper);
  *     }
  *
@@ -65,42 +78,44 @@ import org.springframework.util.Assert;
  */
 public class JacksonTester extends AbstractJsonMarshalTester {
 
-	private final ObjectMapper objectMapper;
+	private final JsonMapper jsonMapper;
 
 	private @Nullable Class view;
 
 	/**
 	 * Create a new {@link JacksonTester} instance.
-	 * @param objectMapper the Jackson object mapper
+	 * @param jsonMapper the Jackson JSON mapper
+	 * @since 4.0.0
 	 */
-	protected JacksonTester(ObjectMapper objectMapper) {
-		Assert.notNull(objectMapper, "'objectMapper' must not be null");
-		this.objectMapper = objectMapper;
+	protected JacksonTester(JsonMapper jsonMapper) {
+		Assert.notNull(jsonMapper, "'objectMapper' must not be null");
+		this.jsonMapper = jsonMapper;
 	}
 
 	/**
 	 * Create a new {@link JacksonTester} instance.
 	 * @param resourceLoadClass the source class used to load resources
 	 * @param type the type under test
-	 * @param objectMapper the Jackson object mapper
+	 * @param jsonMapper the Jackson JSON mapper
+	 * @since 4.0.0
 	 */
-	public JacksonTester(Class resourceLoadClass, ResolvableType type, ObjectMapper objectMapper) {
-		this(resourceLoadClass, type, objectMapper, null);
+	public JacksonTester(Class resourceLoadClass, ResolvableType type, JsonMapper jsonMapper) {
+		this(resourceLoadClass, type, jsonMapper, null);
 	}
 
-	public JacksonTester(Class resourceLoadClass, ResolvableType type, ObjectMapper objectMapper,
+	public JacksonTester(Class resourceLoadClass, ResolvableType type, JsonMapper jsonMapper,
 			@Nullable Class view) {
 		super(resourceLoadClass, type);
-		Assert.notNull(objectMapper, "'objectMapper' must not be null");
-		this.objectMapper = objectMapper;
+		Assert.notNull(jsonMapper, "'jsonMapper' must not be null");
+		this.jsonMapper = jsonMapper;
 		this.view = view;
 	}
 
 	@Override
 	protected JsonContent getJsonContent(String json) {
 		Configuration configuration = Configuration.builder()
-			.jsonProvider(new JacksonJsonProvider(this.objectMapper))
-			.mappingProvider(new JacksonMappingProvider(this.objectMapper))
+			.jsonProvider(new JacksonJsonProvider(this.jsonMapper))
+			.mappingProvider(new JacksonMappingProvider(this.jsonMapper))
 			.build();
 		Class resourceLoadClass = getResourceLoadClass();
 		Assert.state(resourceLoadClass != null, "'resourceLoadClass' must not be null");
@@ -118,7 +133,7 @@ public class JacksonTester extends AbstractJsonMarshalTester {
 	}
 
 	private ObjectReader getObjectReader(ResolvableType type) {
-		ObjectReader objectReader = this.objectMapper.readerFor(getType(type));
+		ObjectReader objectReader = this.jsonMapper.readerFor(getType(type));
 		if (this.view != null) {
 			return objectReader.withView(this.view);
 		}
@@ -131,7 +146,7 @@ public class JacksonTester extends AbstractJsonMarshalTester {
 	}
 
 	private ObjectWriter getObjectWriter(ResolvableType type) {
-		ObjectWriter objectWriter = this.objectMapper.writerFor(getType(type));
+		ObjectWriter objectWriter = this.jsonMapper.writerFor(getType(type));
 		if (this.view != null) {
 			return objectWriter.withView(this.view);
 		}
@@ -139,29 +154,31 @@ public class JacksonTester extends AbstractJsonMarshalTester {
 	}
 
 	private JavaType getType(ResolvableType type) {
-		return this.objectMapper.constructType(type.getType());
+		return this.jsonMapper.constructType(type.getType());
 	}
 
 	/**
 	 * Utility method to initialize {@link JacksonTester} fields. See {@link JacksonTester
 	 * class-level documentation} for example usage.
 	 * @param testInstance the test instance
-	 * @param objectMapper the object mapper
-	 * @see #initFields(Object, ObjectMapper)
+	 * @param jsonMapper the JSON mapper
+	 * @since 4.0.0
+	 * @see #initFields(Object, JsonMapper)
 	 */
-	public static void initFields(Object testInstance, ObjectMapper objectMapper) {
-		new JacksonFieldInitializer().initFields(testInstance, objectMapper);
+	public static void initFields(Object testInstance, JsonMapper jsonMapper) {
+		new JacksonFieldInitializer().initFields(testInstance, jsonMapper);
 	}
 
 	/**
 	 * Utility method to initialize {@link JacksonTester} fields. See {@link JacksonTester
 	 * class-level documentation} for example usage.
 	 * @param testInstance the test instance
-	 * @param objectMapperFactory a factory to create the object mapper
-	 * @see #initFields(Object, ObjectMapper)
+	 * @param jsonMapperFactory a factory to create the JSON mapper
+	 * @since 4.0.0
+	 * @see #initFields(Object, JsonMapper)
 	 */
-	public static void initFields(Object testInstance, ObjectFactory objectMapperFactory) {
-		new JacksonFieldInitializer().initFields(testInstance, objectMapperFactory);
+	public static void initFields(Object testInstance, ObjectFactory jsonMapperFactory) {
+		new JacksonFieldInitializer().initFields(testInstance, jsonMapperFactory);
 	}
 
 	/**
@@ -175,13 +192,13 @@ public class JacksonTester extends AbstractJsonMarshalTester {
 		ResolvableType type = getType();
 		Assert.state(resourceLoadClass != null, "'resourceLoadClass' must not be null");
 		Assert.state(type != null, "'type' must not be null");
-		return new JacksonTester<>(resourceLoadClass, type, this.objectMapper, view);
+		return new JacksonTester<>(resourceLoadClass, type, this.jsonMapper, view);
 	}
 
 	/**
 	 * {@link FieldInitializer} for Jackson.
 	 */
-	private static class JacksonFieldInitializer extends FieldInitializer {
+	private static class JacksonFieldInitializer extends FieldInitializer {
 
 		protected JacksonFieldInitializer() {
 			super(JacksonTester.class);
@@ -189,10 +206,115 @@ public class JacksonTester extends AbstractJsonMarshalTester {
 
 		@Override
 		protected AbstractJsonMarshalTester createTester(Class resourceLoadClass, ResolvableType type,
-				ObjectMapper marshaller) {
+				JsonMapper marshaller) {
 			return new JacksonTester<>(resourceLoadClass, type, marshaller);
 		}
 
 	}
 
+	private static final class JacksonJsonProvider extends AbstractJsonProvider {
+
+		private final ObjectMapper objectMapper;
+
+		private final ObjectReader objectReader;
+
+		private JacksonJsonProvider(ObjectMapper objectMapper) {
+			this.objectMapper = objectMapper;
+			this.objectReader = objectMapper.reader().forType(Object.class);
+		}
+
+		@Override
+		public Object parse(String json) throws InvalidJsonException {
+			try {
+				return this.objectReader.readValue(json);
+			}
+			catch (JacksonException ex) {
+				throw new InvalidJsonException(ex, json);
+			}
+		}
+
+		@Override
+		public Object parse(byte[] json) throws InvalidJsonException {
+			try {
+				return this.objectReader.readValue(json);
+			}
+			catch (JacksonException ex) {
+				throw new InvalidJsonException(ex, new String(json, StandardCharsets.UTF_8));
+			}
+		}
+
+		@Override
+		public Object parse(InputStream jsonStream, String charset) throws InvalidJsonException {
+			try {
+				return this.objectReader.readValue(new InputStreamReader(jsonStream, charset));
+			}
+			catch (UnsupportedEncodingException | JacksonException ex) {
+				throw new InvalidJsonException(ex);
+			}
+		}
+
+		@Override
+		public String toJson(Object obj) {
+			StringWriter writer = new StringWriter();
+			try (JsonGenerator generator = this.objectMapper.createGenerator(writer)) {
+				this.objectMapper.writeValue(generator, obj);
+			}
+			catch (JacksonException ex) {
+				throw new InvalidJsonException(ex);
+			}
+			return writer.toString();
+		}
+
+		@Override
+		public List createArray() {
+			return new LinkedList<>();
+		}
+
+		@Override
+		public Object createMap() {
+			return new LinkedHashMap();
+		}
+
+	}
+
+	private static final class JacksonMappingProvider implements MappingProvider {
+
+		private final ObjectMapper objectMapper;
+
+		private JacksonMappingProvider(ObjectMapper objectMapper) {
+			this.objectMapper = objectMapper;
+		}
+
+		@Override
+		public  @Nullable T map(Object source, Class targetType, Configuration configuration) {
+			if (source == null) {
+				return null;
+			}
+			try {
+				return this.objectMapper.convertValue(source, targetType);
+			}
+			catch (Exception ex) {
+				throw new MappingException(ex);
+			}
+
+		}
+
+		@Override
+		@SuppressWarnings("unchecked")
+		public  @Nullable T map(Object source, final TypeRef targetType, Configuration configuration) {
+			if (source == null) {
+				return null;
+			}
+			JavaType type = this.objectMapper.getTypeFactory().constructType(targetType.getType());
+			try {
+				return (T) this.objectMapper.convertValue(source, type);
+			}
+			catch (Exception ex) {
+				throw new MappingException(ex);
+			}
+
+		}
+
+	}
+
 }
diff --git a/core/spring-boot-test/src/test/java/org/springframework/boot/test/json/JacksonTesterIntegrationTests.java b/core/spring-boot-test/src/test/java/org/springframework/boot/test/json/JacksonTesterIntegrationTests.java
index 7852fc59fac..13208b3965d 100644
--- a/core/spring-boot-test/src/test/java/org/springframework/boot/test/json/JacksonTesterIntegrationTests.java
+++ b/core/spring-boot-test/src/test/java/org/springframework/boot/test/json/JacksonTesterIntegrationTests.java
@@ -22,11 +22,10 @@ import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 
-import com.fasterxml.jackson.databind.MapperFeature;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.json.JsonMapper;
 import org.assertj.core.api.InstanceOfAssertFactories;
 import org.junit.jupiter.api.Test;
+import tools.jackson.databind.MapperFeature;
+import tools.jackson.databind.json.JsonMapper;
 
 import org.springframework.core.io.ByteArrayResource;
 
@@ -55,14 +54,14 @@ class JacksonTesterIntegrationTests {
 
 	@Test
 	void typicalTest() throws Exception {
-		JacksonTester.initFields(this, new ObjectMapper());
+		JacksonTester.initFields(this, new JsonMapper());
 		String example = JSON;
 		assertThat(this.simpleJson.parse(example).getObject().getName()).isEqualTo("Spring");
 	}
 
 	@Test
 	void typicalListTest() throws Exception {
-		JacksonTester.initFields(this, new ObjectMapper());
+		JacksonTester.initFields(this, new JsonMapper());
 		String example = "[" + JSON + "]";
 		assertThat(this.listJson.parse(example)).asInstanceOf(InstanceOfAssertFactories.LIST).hasSize(1);
 		assertThat(this.listJson.parse(example).getObject().get(0).getName()).isEqualTo("Spring");
@@ -70,7 +69,7 @@ class JacksonTesterIntegrationTests {
 
 	@Test
 	void typicalMapTest() throws Exception {
-		JacksonTester.initFields(this, new ObjectMapper());
+		JacksonTester.initFields(this, new JsonMapper());
 		Map map = new LinkedHashMap<>();
 		map.put("a", 1);
 		map.put("b", 2);
@@ -79,7 +78,7 @@ class JacksonTesterIntegrationTests {
 
 	@Test
 	void stringLiteral() throws Exception {
-		JacksonTester.initFields(this, new ObjectMapper());
+		JacksonTester.initFields(this, new JsonMapper());
 		String stringWithSpecialCharacters = "myString";
 		assertThat(this.stringJson.write(stringWithSpecialCharacters)).extractingJsonPathStringValue("@")
 			.isEqualTo(stringWithSpecialCharacters);
@@ -87,7 +86,7 @@ class JacksonTesterIntegrationTests {
 
 	@Test
 	void parseSpecialCharactersTest() throws Exception {
-		JacksonTester.initFields(this, new ObjectMapper());
+		JacksonTester.initFields(this, new JsonMapper());
 		// Confirms that the handling of special characters is symmetrical between
 		// the serialization (through the JacksonTester) and the parsing (through
 		// json-path). By default json-path uses SimpleJson as its parser, which has a
diff --git a/core/spring-boot-test/src/test/java/org/springframework/boot/test/json/JacksonTesterTests.java b/core/spring-boot-test/src/test/java/org/springframework/boot/test/json/JacksonTesterTests.java
index 9917c08bd74..de1d17c7ca1 100644
--- a/core/spring-boot-test/src/test/java/org/springframework/boot/test/json/JacksonTesterTests.java
+++ b/core/spring-boot-test/src/test/java/org/springframework/boot/test/json/JacksonTesterTests.java
@@ -18,8 +18,8 @@ package org.springframework.boot.test.json;
 
 import java.util.List;
 
-import com.fasterxml.jackson.databind.ObjectMapper;
 import org.junit.jupiter.api.Test;
+import tools.jackson.databind.json.JsonMapper;
 
 import org.springframework.core.ResolvableType;
 
@@ -35,14 +35,14 @@ class JacksonTesterTests extends AbstractJsonMarshalTesterTests {
 
 	@Test
 	void initFieldsWhenTestIsNullShouldThrowException() {
-		assertThatIllegalArgumentException().isThrownBy(() -> JacksonTester.initFields(null, new ObjectMapper()))
+		assertThatIllegalArgumentException().isThrownBy(() -> JacksonTester.initFields(null, new JsonMapper()))
 			.withMessageContaining("'testInstance' must not be null");
 	}
 
 	@Test
 	void initFieldsWhenMarshallerIsNullShouldThrowException() {
 		assertThatIllegalArgumentException()
-			.isThrownBy(() -> JacksonTester.initFields(new InitFieldsTestClass(), (ObjectMapper) null))
+			.isThrownBy(() -> JacksonTester.initFields(new InitFieldsTestClass(), (JsonMapper) null))
 			.withMessageContaining("'marshaller' must not be null");
 	}
 
@@ -51,7 +51,7 @@ class JacksonTesterTests extends AbstractJsonMarshalTesterTests {
 		InitFieldsTestClass test = new InitFieldsTestClass();
 		assertThat(test.test).isNull();
 		assertThat(test.base).isNull();
-		JacksonTester.initFields(test, new ObjectMapper());
+		JacksonTester.initFields(test, new JsonMapper());
 		assertThat(test.test).isNotNull();
 		assertThat(test.base).isNotNull();
 		assertThat(test.test.getType().resolve()).isEqualTo(List.class);
@@ -60,7 +60,7 @@ class JacksonTesterTests extends AbstractJsonMarshalTesterTests {
 
 	@Override
 	protected AbstractJsonMarshalTester createTester(Class resourceLoadClass, ResolvableType type) {
-		return new JacksonTester<>(resourceLoadClass, type, new ObjectMapper());
+		return new JacksonTester<>(resourceLoadClass, type, new JsonMapper());
 	}
 
 	abstract static class InitFieldsBaseClass {
@@ -68,7 +68,7 @@ class JacksonTesterTests extends AbstractJsonMarshalTesterTests {
 		public JacksonTester base;
 
 		public JacksonTester baseSet = new JacksonTester<>(InitFieldsBaseClass.class,
-				ResolvableType.forClass(ExampleObject.class), new ObjectMapper());
+				ResolvableType.forClass(ExampleObject.class), new JsonMapper());
 
 	}
 
@@ -77,7 +77,7 @@ class JacksonTesterTests extends AbstractJsonMarshalTesterTests {
 		public JacksonTester> test;
 
 		public JacksonTester testSet = new JacksonTester<>(InitFieldsBaseClass.class,
-				ResolvableType.forClass(ExampleObject.class), new ObjectMapper());
+				ResolvableType.forClass(ExampleObject.class), new JsonMapper());
 
 	}
 
diff --git a/core/spring-boot/build.gradle b/core/spring-boot/build.gradle
index 44cb7ba5066..a32fd204b66 100644
--- a/core/spring-boot/build.gradle
+++ b/core/spring-boot/build.gradle
@@ -33,7 +33,6 @@ dependencies {
 	api("org.springframework:spring-context")
 
 	optional("ch.qos.logback:logback-classic")
-	optional("com.fasterxml.jackson.core:jackson-databind")
 	optional("com.google.code.gson:gson")
 	optional("io.projectreactor:reactor-core")
 	optional("jakarta.servlet:jakarta.servlet-api")
@@ -49,6 +48,7 @@ dependencies {
 	optional("org.springframework:spring-test")
 	optional("org.springframework:spring-web")
 	optional("org.yaml:snakeyaml")
+	optional("tools.jackson.core:jackson-databind")
 
 	testFixturesCompileOnly(project(":test-support:spring-boot-test-support"))
 
diff --git a/core/spring-boot/src/main/java/org/springframework/boot/json/JacksonJsonParser.java b/core/spring-boot/src/main/java/org/springframework/boot/json/JacksonJsonParser.java
index 2d72f2b45dc..7187254f607 100644
--- a/core/spring-boot/src/main/java/org/springframework/boot/json/JacksonJsonParser.java
+++ b/core/spring-boot/src/main/java/org/springframework/boot/json/JacksonJsonParser.java
@@ -19,9 +19,9 @@ package org.springframework.boot.json;
 import java.util.List;
 import java.util.Map;
 
-import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import org.jspecify.annotations.Nullable;
+import tools.jackson.core.type.TypeReference;
+import tools.jackson.databind.ObjectMapper;
 
 /**
  * Thin wrapper to adapt Jackson 2 {@link ObjectMapper} to {@link JsonParser}.
diff --git a/core/spring-boot/src/main/java/org/springframework/boot/json/JacksonRuntimeHints.java b/core/spring-boot/src/main/java/org/springframework/boot/json/JacksonRuntimeHints.java
deleted file mode 100644
index 5fefc1c4dc5..00000000000
--- a/core/spring-boot/src/main/java/org/springframework/boot/json/JacksonRuntimeHints.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * 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.json;
-
-import com.fasterxml.jackson.databind.ser.std.ClassSerializer;
-import com.fasterxml.jackson.databind.ser.std.FileSerializer;
-import com.fasterxml.jackson.databind.ser.std.StdJdkSerializers.AtomicBooleanSerializer;
-import com.fasterxml.jackson.databind.ser.std.StdJdkSerializers.AtomicIntegerSerializer;
-import com.fasterxml.jackson.databind.ser.std.StdJdkSerializers.AtomicLongSerializer;
-import com.fasterxml.jackson.databind.ser.std.TokenBufferSerializer;
-import org.jspecify.annotations.Nullable;
-
-import org.springframework.aot.hint.MemberCategory;
-import org.springframework.aot.hint.ReflectionHints;
-import org.springframework.aot.hint.RuntimeHints;
-import org.springframework.aot.hint.RuntimeHintsRegistrar;
-import org.springframework.aot.hint.TypeHint;
-import org.springframework.aot.hint.TypeReference;
-import org.springframework.util.ClassUtils;
-
-/**
- * {@link RuntimeHintsRegistrar} implementation for Jackson.
- *
- * @author Moritz Halbritter
- */
-class JacksonRuntimeHints implements RuntimeHintsRegistrar {
-
-	@Override
-	public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
-		if (!ClassUtils.isPresent("com.fasterxml.jackson.databind.ser.BasicSerializerFactory", classLoader)) {
-			return;
-		}
-		registerSerializers(hints.reflection());
-	}
-
-	private void registerSerializers(ReflectionHints hints) {
-		hints.registerTypes(TypeReference.listOf(AtomicBooleanSerializer.class, AtomicIntegerSerializer.class,
-				AtomicLongSerializer.class, FileSerializer.class, ClassSerializer.class, TokenBufferSerializer.class),
-				TypeHint.builtWith(MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS));
-	}
-
-}
diff --git a/core/spring-boot/src/main/java/org/springframework/boot/json/JsonParserFactory.java b/core/spring-boot/src/main/java/org/springframework/boot/json/JsonParserFactory.java
index 9bd37bbe223..5c3eee2c491 100644
--- a/core/spring-boot/src/main/java/org/springframework/boot/json/JsonParserFactory.java
+++ b/core/spring-boot/src/main/java/org/springframework/boot/json/JsonParserFactory.java
@@ -35,7 +35,7 @@ public abstract class JsonParserFactory {
 	 * @return a {@link JsonParser}
 	 */
 	public static JsonParser getJsonParser() {
-		if (ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", null)) {
+		if (ClassUtils.isPresent("tools.jackson.databind.ObjectMapper", null)) {
 			return new JacksonJsonParser();
 		}
 		if (ClassUtils.isPresent("com.google.gson.Gson", null)) {
diff --git a/core/spring-boot/src/main/resources/META-INF/spring/aot.factories b/core/spring-boot/src/main/resources/META-INF/spring/aot.factories
index 61390a75beb..4859f625c1e 100644
--- a/core/spring-boot/src/main/resources/META-INF/spring/aot.factories
+++ b/core/spring-boot/src/main/resources/META-INF/spring/aot.factories
@@ -5,7 +5,6 @@ org.springframework.boot.WebApplicationType$WebApplicationTypeRuntimeHints,\
 org.springframework.boot.context.config.ConfigDataLocationRuntimeHints,\
 org.springframework.boot.context.config.ConfigDataPropertiesRuntimeHints,\
 org.springframework.boot.env.PropertySourceRuntimeHints,\
-org.springframework.boot.json.JacksonRuntimeHints,\
 org.springframework.boot.logging.java.JavaLoggingSystemRuntimeHints,\
 org.springframework.boot.logging.logback.LogbackRuntimeHints,\
 org.springframework.boot.logging.structured.ElasticCommonSchemaProperties$ElasticCommonSchemaPropertiesRuntimeHints,\
diff --git a/core/spring-boot/src/test/java/org/springframework/boot/json/JacksonJsonParserTests.java b/core/spring-boot/src/test/java/org/springframework/boot/json/JacksonJsonParserTests.java
index 7c5d010dfcb..02609793dec 100644
--- a/core/spring-boot/src/test/java/org/springframework/boot/json/JacksonJsonParserTests.java
+++ b/core/spring-boot/src/test/java/org/springframework/boot/json/JacksonJsonParserTests.java
@@ -16,12 +16,10 @@
 
 package org.springframework.boot.json;
 
-import java.io.IOException;
-
-import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
+import tools.jackson.core.type.TypeReference;
+import tools.jackson.databind.ObjectMapper;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
@@ -43,7 +41,7 @@ class JacksonJsonParserTests extends AbstractJsonParserTests {
 
 	@Test
 	@SuppressWarnings("unchecked")
-	void instanceWithSpecificObjectMapper() throws IOException {
+	void instanceWithSpecificObjectMapper() {
 		ObjectMapper objectMapper = spy(new ObjectMapper());
 		new JacksonJsonParser(objectMapper).parseMap("{}");
 		then(objectMapper).should().readValue(eq("{}"), any(TypeReference.class));
diff --git a/core/spring-boot/src/test/java/org/springframework/boot/json/JacksonRuntimeHintsTests.java b/core/spring-boot/src/test/java/org/springframework/boot/json/JacksonRuntimeHintsTests.java
deleted file mode 100644
index c3e0073df18..00000000000
--- a/core/spring-boot/src/test/java/org/springframework/boot/json/JacksonRuntimeHintsTests.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * 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.json;
-
-import java.util.Set;
-import java.util.stream.Stream;
-
-import com.fasterxml.jackson.databind.ser.std.ClassSerializer;
-import com.fasterxml.jackson.databind.ser.std.FileSerializer;
-import com.fasterxml.jackson.databind.ser.std.StdJdkSerializers.AtomicBooleanSerializer;
-import com.fasterxml.jackson.databind.ser.std.StdJdkSerializers.AtomicIntegerSerializer;
-import com.fasterxml.jackson.databind.ser.std.StdJdkSerializers.AtomicLongSerializer;
-import com.fasterxml.jackson.databind.ser.std.TokenBufferSerializer;
-import org.junit.jupiter.api.Test;
-
-import org.springframework.aot.hint.MemberCategory;
-import org.springframework.aot.hint.ReflectionHints;
-import org.springframework.aot.hint.RuntimeHints;
-import org.springframework.aot.hint.TypeHint;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-/**
- * Tests for {@link JacksonRuntimeHints}.
- *
- * @author Moritz Halbritter
- */
-class JacksonRuntimeHintsTests {
-
-	@Test
-	void shouldRegisterSerializerConstructors() {
-		ReflectionHints hints = registerHints();
-		Stream
-			.of(AtomicBooleanSerializer.class, AtomicIntegerSerializer.class, AtomicLongSerializer.class,
-					FileSerializer.class, ClassSerializer.class, TokenBufferSerializer.class)
-			.forEach((serializer) -> {
-				TypeHint typeHint = hints.getTypeHint(serializer);
-				assertThat(typeHint).withFailMessage(() -> "No hints found for serializer " + serializer).isNotNull();
-				Set memberCategories = typeHint.getMemberCategories();
-				assertThat(memberCategories).containsExactly(MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS);
-			});
-	}
-
-	private ReflectionHints registerHints() {
-		RuntimeHints hints = new RuntimeHints();
-		new JacksonRuntimeHints().registerHints(hints, getClass().getClassLoader());
-		return hints.reflection();
-	}
-
-}
diff --git a/core/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/AbstractStructuredLoggingTests.java b/core/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/AbstractStructuredLoggingTests.java
index da4c590e247..2e52f2230d3 100644
--- a/core/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/AbstractStructuredLoggingTests.java
+++ b/core/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/AbstractStructuredLoggingTests.java
@@ -20,16 +20,14 @@ import java.time.Instant;
 import java.util.HashMap;
 import java.util.Map;
 
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.core.impl.MutableLogEvent;
 import org.apache.logging.log4j.message.SimpleMessage;
-import org.assertj.core.api.Assertions;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
+import tools.jackson.core.type.TypeReference;
+import tools.jackson.databind.ObjectMapper;
 
 import org.springframework.boot.logging.structured.MockStructuredLoggingJsonMembersCustomizerBuilder;
 import org.springframework.boot.logging.structured.StructuredLoggingJsonMembersCustomizer;
@@ -79,14 +77,8 @@ abstract class AbstractStructuredLoggingTests {
 	}
 
 	protected Map deserialize(String json) {
-		try {
-			return OBJECT_MAPPER.readValue(json, new TypeReference<>() {
-			});
-		}
-		catch (JsonProcessingException ex) {
-			Assertions.fail("Failed to deserialize JSON: " + json, ex);
-			return null;
-		}
+		return OBJECT_MAPPER.readValue(json, new TypeReference<>() {
+		});
 	}
 
 }
diff --git a/core/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystemTests.java b/core/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystemTests.java
index cd6770f1757..60ff4bd735b 100644
--- a/core/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystemTests.java
+++ b/core/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystemTests.java
@@ -34,7 +34,6 @@ import java.util.logging.Handler;
 import java.util.logging.Level;
 import java.util.stream.Stream;
 
-import com.fasterxml.jackson.databind.ObjectMapper;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.apache.logging.log4j.LogManager;
@@ -333,15 +332,16 @@ class Log4J2LoggingSystemTests extends AbstractLoggingSystemTests {
 				// No classes, only XML
 				Arguments.of(Collections.emptyList(), List.of(".xml")),
 				// Log4j Core 2
-				Arguments.of(List.of(JsonConfigurationFactory.class.getName(), ObjectMapper.class.getName()),
-						List.of(".json", ".jsn", ".xml")),
+				Arguments.of(List.of(JsonConfigurationFactory.class.getName(),
+						"com.fasterxml.jackson.databind.ObjectMapper"), List.of(".json", ".jsn", ".xml")),
 				Arguments.of(List.of(PropertiesConfigurationFactory.class.getName(),
 						PropertiesConfigurationBuilder.class.getName()), List.of(".properties", ".xml")),
 				Arguments.of(List.of(YamlConfigurationFactory.class.getName(),
 						"com.fasterxml.jackson.dataformat.yaml.YAMLMapper"), List.of(".yaml", ".yml", ".xml")),
-				Arguments.of(List.of(JsonConfigurationFactory.class.getName(), ObjectMapper.class.getName(),
-						PropertiesConfigurationFactory.class.getName(), PropertiesConfigurationBuilder.class.getName(),
-						YamlConfigurationFactory.class.getName(), "com.fasterxml.jackson.dataformat.yaml.YAMLMapper"),
+				Arguments.of(List.of(JsonConfigurationFactory.class.getName(),
+						"com.fasterxml.jackson.databind.ObjectMapper", PropertiesConfigurationFactory.class.getName(),
+						PropertiesConfigurationBuilder.class.getName(), YamlConfigurationFactory.class.getName(),
+						"com.fasterxml.jackson.dataformat.yaml.YAMLMapper"),
 						List.of(".properties", ".yaml", ".yml", ".json", ".jsn", ".xml")),
 				// Log4j Core 3
 				Arguments.of(List.of(JsonConfigurationFactory.class.getName(),
diff --git a/core/spring-boot/src/test/java/org/springframework/boot/logging/logback/AbstractStructuredLoggingTests.java b/core/spring-boot/src/test/java/org/springframework/boot/logging/logback/AbstractStructuredLoggingTests.java
index e6f97e6b7d5..f6f2cdddb7f 100644
--- a/core/spring-boot/src/test/java/org/springframework/boot/logging/logback/AbstractStructuredLoggingTests.java
+++ b/core/spring-boot/src/test/java/org/springframework/boot/logging/logback/AbstractStructuredLoggingTests.java
@@ -26,10 +26,6 @@ import ch.qos.logback.classic.Level;
 import ch.qos.logback.classic.pattern.ThrowableProxyConverter;
 import ch.qos.logback.classic.spi.LoggingEvent;
 import ch.qos.logback.classic.spi.ThrowableProxy;
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import org.assertj.core.api.Assertions;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.extension.ExtendWith;
@@ -38,6 +34,8 @@ import org.mockito.junit.jupiter.MockitoExtension;
 import org.slf4j.Marker;
 import org.slf4j.event.KeyValuePair;
 import org.slf4j.helpers.BasicMarkerFactory;
+import tools.jackson.core.type.TypeReference;
+import tools.jackson.databind.ObjectMapper;
 
 import org.springframework.boot.logging.structured.MockStructuredLoggingJsonMembersCustomizerBuilder;
 import org.springframework.boot.logging.structured.StructuredLoggingJsonMembersCustomizer;
@@ -122,14 +120,8 @@ abstract class AbstractStructuredLoggingTests {
 	}
 
 	protected Map deserialize(String json) {
-		try {
-			return OBJECT_MAPPER.readValue(json, new TypeReference<>() {
-			});
-		}
-		catch (JsonProcessingException ex) {
-			Assertions.fail("Failed to deserialize JSON: " + json, ex);
-			return null;
-		}
+		return OBJECT_MAPPER.readValue(json, new TypeReference<>() {
+		});
 	}
 
 }
diff --git a/core/spring-boot/src/test/java/org/springframework/boot/web/error/ErrorTests.java b/core/spring-boot/src/test/java/org/springframework/boot/web/error/ErrorTests.java
index 7fcac82a9be..8738d6ace75 100644
--- a/core/spring-boot/src/test/java/org/springframework/boot/web/error/ErrorTests.java
+++ b/core/spring-boot/src/test/java/org/springframework/boot/web/error/ErrorTests.java
@@ -18,9 +18,8 @@ package org.springframework.boot.web.error;
 
 import java.util.List;
 
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import org.junit.jupiter.api.Test;
+import tools.jackson.databind.ObjectMapper;
 
 import org.springframework.context.MessageSourceResolvable;
 import org.springframework.context.support.DefaultMessageSourceResolvable;
@@ -46,7 +45,7 @@ class ErrorTests {
 	}
 
 	@Test
-	void errorCauseDoesNotAppearInJson() throws JsonProcessingException {
+	void errorCauseDoesNotAppearInJson() {
 		String json = new ObjectMapper()
 			.writeValueAsString(Error.wrapIfNecessary(List.of(new CustomMessageSourceResolvable("code"))));
 		assertThat(json).doesNotContain("some detail");
diff --git a/documentation/spring-boot-actuator-docs/src/test/java/org/springframework/boot/actuate/docs/AbstractEndpointDocumentationTests.java b/documentation/spring-boot-actuator-docs/src/test/java/org/springframework/boot/actuate/docs/AbstractEndpointDocumentationTests.java
index 06c1c64928c..db8036e89c6 100644
--- a/documentation/spring-boot-actuator-docs/src/test/java/org/springframework/boot/actuate/docs/AbstractEndpointDocumentationTests.java
+++ b/documentation/spring-boot-actuator-docs/src/test/java/org/springframework/boot/actuate/docs/AbstractEndpointDocumentationTests.java
@@ -16,15 +16,14 @@
 
 package org.springframework.boot.actuate.docs;
 
-import java.io.IOException;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.function.Predicate;
 import java.util.stream.Stream;
 
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.SerializationFeature;
+import tools.jackson.databind.SerializationFeature;
+import tools.jackson.databind.json.JsonMapper;
 
 import org.springframework.beans.BeansException;
 import org.springframework.beans.factory.config.BeanPostProcessor;
@@ -32,7 +31,7 @@ import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfi
 import org.springframework.boot.actuate.autoconfigure.endpoint.jackson.JacksonEndpointAutoConfiguration;
 import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;
 import org.springframework.boot.actuate.docs.AbstractEndpointDocumentationTests.BaseDocumentationConfiguration;
-import org.springframework.boot.actuate.endpoint.jackson.EndpointObjectMapper;
+import org.springframework.boot.actuate.endpoint.jackson.EndpointJsonMapper;
 import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
 import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
 import org.springframework.boot.http.converter.autoconfigure.HttpMessageConvertersAutoConfiguration;
@@ -77,29 +76,24 @@ public abstract class AbstractEndpointDocumentationTests {
 	@SuppressWarnings("unchecked")
 	protected  OperationPreprocessor limit(Predicate filter, String... keys) {
 		return new ContentModifyingOperationPreprocessor((content, mediaType) -> {
-			ObjectMapper objectMapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT);
-			try {
-				Map payload = objectMapper.readValue(content, Map.class);
-				Object target = payload;
-				Map parent = null;
-				for (String key : keys) {
-					if (!(target instanceof Map)) {
-						throw new IllegalStateException();
-					}
-					parent = (Map) target;
-					target = parent.get(key);
+			JsonMapper objectMapper = JsonMapper.builder().enable(SerializationFeature.INDENT_OUTPUT).build();
+			Map payload = objectMapper.readValue(content, Map.class);
+			Object target = payload;
+			Map parent = null;
+			for (String key : keys) {
+				if (!(target instanceof Map)) {
+					throw new IllegalStateException();
 				}
-				if (target instanceof Map) {
-					parent.put(keys[keys.length - 1], select((Map) target, filter));
-				}
-				else {
-					parent.put(keys[keys.length - 1], select((List) target, filter));
-				}
-				return objectMapper.writeValueAsBytes(payload);
+				parent = (Map) target;
+				target = parent.get(key);
 			}
-			catch (IOException ex) {
-				throw new IllegalStateException(ex);
+			if (target instanceof Map) {
+				parent.put(keys[keys.length - 1], select((Map) target, filter));
 			}
+			else {
+				parent.put(keys[keys.length - 1], select((List) target, filter));
+			}
+			return objectMapper.writeValueAsBytes(payload);
 		});
 	}
 
@@ -135,14 +129,16 @@ public abstract class AbstractEndpointDocumentationTests {
 	static class BaseDocumentationConfiguration {
 
 		@Bean
-		static BeanPostProcessor endpointObjectMapperBeanPostProcessor() {
+		static BeanPostProcessor endpointJsonMapperBeanPostProcessor() {
 			return new BeanPostProcessor() {
 
 				@Override
 				public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
-					if (bean instanceof EndpointObjectMapper) {
-						return (EndpointObjectMapper) () -> ((EndpointObjectMapper) bean).get()
-							.enable(SerializationFeature.INDENT_OUTPUT);
+					if (bean instanceof EndpointJsonMapper) {
+						return (EndpointJsonMapper) () -> ((EndpointJsonMapper) bean).get()
+							.rebuild()
+							.enable(SerializationFeature.INDENT_OUTPUT)
+							.build();
 					}
 					return bean;
 				}
diff --git a/documentation/spring-boot-actuator-docs/src/test/java/org/springframework/boot/actuate/docs/env/EnvironmentEndpointDocumentationTests.java b/documentation/spring-boot-actuator-docs/src/test/java/org/springframework/boot/actuate/docs/env/EnvironmentEndpointDocumentationTests.java
index cad6f3de110..7406b6d8c6a 100644
--- a/documentation/spring-boot-actuator-docs/src/test/java/org/springframework/boot/actuate/docs/env/EnvironmentEndpointDocumentationTests.java
+++ b/documentation/spring-boot-actuator-docs/src/test/java/org/springframework/boot/actuate/docs/env/EnvironmentEndpointDocumentationTests.java
@@ -16,7 +16,6 @@
 
 package org.springframework.boot.actuate.docs.env;
 
-import java.io.IOException;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -24,9 +23,10 @@ import java.util.Set;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.SerializationFeature;
 import org.junit.jupiter.api.Test;
+import tools.jackson.databind.ObjectMapper;
+import tools.jackson.databind.SerializationFeature;
+import tools.jackson.databind.json.JsonMapper;
 
 import org.springframework.boot.actuate.docs.MockMvcEndpointDocumentationTests;
 import org.springframework.boot.actuate.endpoint.Show;
@@ -116,24 +116,19 @@ class EnvironmentEndpointDocumentationTests extends MockMvcEndpointDocumentation
 
 	@SuppressWarnings("unchecked")
 	private byte[] filterProperties(byte[] content, MediaType mediaType) {
-		ObjectMapper objectMapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT);
-		try {
-			Map payload = objectMapper.readValue(content, Map.class);
-			List> propertySources = (List>) payload.get("propertySources");
-			for (Map propertySource : propertySources) {
-				Map properties = (Map) propertySource.get("properties");
-				Set filteredKeys = properties.keySet()
-					.stream()
-					.filter(this::retainKey)
-					.limit(3)
-					.collect(Collectors.toSet());
-				properties.keySet().retainAll(filteredKeys);
-			}
-			return objectMapper.writeValueAsBytes(payload);
-		}
-		catch (IOException ex) {
-			throw new IllegalStateException(ex);
+		ObjectMapper objectMapper = JsonMapper.builder().enable(SerializationFeature.INDENT_OUTPUT).build();
+		Map payload = objectMapper.readValue(content, Map.class);
+		List> propertySources = (List>) payload.get("propertySources");
+		for (Map propertySource : propertySources) {
+			Map properties = (Map) propertySource.get("properties");
+			Set filteredKeys = properties.keySet()
+				.stream()
+				.filter(this::retainKey)
+				.limit(3)
+				.collect(Collectors.toSet());
+			properties.keySet().retainAll(filteredKeys);
 		}
+		return objectMapper.writeValueAsBytes(payload);
 	}
 
 	private boolean retainKey(String key) {
diff --git a/documentation/spring-boot-actuator-docs/src/test/java/org/springframework/boot/actuate/docs/session/SessionsEndpointDocumentationTests.java b/documentation/spring-boot-actuator-docs/src/test/java/org/springframework/boot/actuate/docs/session/SessionsEndpointDocumentationTests.java
index d1ee3e18ae3..7eacf46cb4f 100644
--- a/documentation/spring-boot-actuator-docs/src/test/java/org/springframework/boot/actuate/docs/session/SessionsEndpointDocumentationTests.java
+++ b/documentation/spring-boot-actuator-docs/src/test/java/org/springframework/boot/actuate/docs/session/SessionsEndpointDocumentationTests.java
@@ -34,7 +34,6 @@ import org.springframework.restdocs.payload.FieldDescriptor;
 import org.springframework.session.FindByIndexNameSessionRepository;
 import org.springframework.session.MapSession;
 import org.springframework.session.Session;
-import org.springframework.test.context.TestPropertySource;
 import org.springframework.test.context.bean.override.mockito.MockitoBean;
 
 import static org.assertj.core.api.Assertions.assertThat;
@@ -51,7 +50,6 @@ import static org.springframework.restdocs.request.RequestDocumentation.queryPar
  *
  * @author Andy Wilkinson
  */
-@TestPropertySource(properties = "spring.jackson.serialization.write-dates-as-timestamps=false")
 class SessionsEndpointDocumentationTests extends MockMvcEndpointDocumentationTests {
 
 	private static final Session sessionOne = createSession(Instant.now().minusSeconds(60 * 60 * 12),
diff --git a/documentation/spring-boot-docs/src/docs/antora/modules/ROOT/pages/redirect.adoc b/documentation/spring-boot-docs/src/docs/antora/modules/ROOT/pages/redirect.adoc
index 59281f86ec0..4a8efab2fbc 100644
--- a/documentation/spring-boot-docs/src/docs/antora/modules/ROOT/pages/redirect.adoc
+++ b/documentation/spring-boot-docs/src/docs/antora/modules/ROOT/pages/redirect.adoc
@@ -634,8 +634,8 @@
 * xref:how-to:security.adoc#howto.security.switch-off-spring-boot-configuration[#howto.security.switch-off-spring-boot-configuration]
 * xref:how-to:security.adoc#howto.security[#howto-security]
 * xref:how-to:security.adoc#howto.security[#howto.security]
-* xref:how-to:spring-mvc.adoc#howto.spring-mvc.customize-jackson-objectmapper[#howto-customize-the-jackson-objectmapper]
-* xref:how-to:spring-mvc.adoc#howto.spring-mvc.customize-jackson-objectmapper[#howto.spring-mvc.customize-jackson-objectmapper]
+* xref:how-to:spring-mvc.adoc#howto.spring-mvc.customize-jackson-jsonmapper[#howto-customize-the-jackson-objectmapper]
+* xref:how-to:spring-mvc.adoc#howto.spring-mvc.customize-jackson-jsonmapper[#howto.spring-mvc.customize-jackson-objectmapper]
 * xref:how-to:spring-mvc.adoc#howto.spring-mvc.customize-responsebody-rendering[#howto-customize-the-responsebody-rendering]
 * xref:how-to:spring-mvc.adoc#howto.spring-mvc.customize-responsebody-rendering[#howto.spring-mvc.customize-responsebody-rendering]
 * xref:how-to:spring-mvc.adoc#howto.spring-mvc.customize-view-resolvers[#howto-customize-view-resolvers]
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 26a4e712779..937dbfae1bb 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
@@ -10,11 +10,11 @@ This section answers common questions about Spring MVC and Spring Boot.
 [[howto.spring-mvc.write-json-rest-service]]
 == Write a JSON REST Service
 
-Any Spring javadoc:org.springframework.web.bind.annotation.RestController[format=annotation] in a Spring Boot application should render JSON response by default as long as Jackson2 is on the classpath, as shown in the following example:
+Any Spring javadoc:org.springframework.web.bind.annotation.RestController[format=annotation] in a Spring Boot application should render JSON response by default as long as Jackson 3 is on the classpath, as shown in the following example:
 
 include-code::MyController[]
 
-As long as `MyThing` can be serialized by Jackson2 (true for a normal POJO or Groovy object), then `http://localhost:8080/thing` serves a JSON representation of it by default.
+As long as `MyThing` can be serialized by Jackson 3 (true for a normal POJO or Groovy object), then `http://localhost:8080/thing` serves a JSON representation of it by default.
 Note that, in a browser, you might sometimes see XML responses, because browsers tend to send accept headers that prefer XML.
 
 
@@ -29,7 +29,7 @@ To use the Jackson XML renderer, add the following dependency to your project:
 [source,xml]
 ----
 
-	com.fasterxml.jackson.dataformat
+	tools.jackson.dataformat
 	jackson-dataformat-xml
 
 ----
@@ -52,53 +52,41 @@ NOTE: To get the server to render XML instead of JSON, you might have to send an
 
 
 
-[[howto.spring-mvc.customize-jackson-objectmapper]]
-== Customize the Jackson ObjectMapper
+[[howto.spring-mvc.customize-jackson-jsonmapper]]
+== Customize the Jackson JsonMapper
 
-Spring MVC (client and server side) uses javadoc:org.springframework.boot.http.converter.autoconfigure.HttpMessageConverters[] to negotiate content conversion in an HTTP exchange.
-If Jackson is on the classpath, you already get the default converter(s) provided by javadoc:org.springframework.http.converter.json.Jackson2ObjectMapperBuilder[], an instance of which is auto-configured for you.
-
-The javadoc:com.fasterxml.jackson.databind.ObjectMapper[] (or javadoc:com.fasterxml.jackson.dataformat.xml.XmlMapper[] for Jackson XML converter) instance (created by default) has the following customized properties:
-
-* `MapperFeature.DEFAULT_VIEW_INCLUSION` is disabled
-* `DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES` is disabled
-* `SerializationFeature.WRITE_DATES_AS_TIMESTAMPS` is disabled
-* `SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS` is disabled
-
-Spring Boot also has some features to make it easier to customize this behavior.
-
-You can configure the javadoc:com.fasterxml.jackson.databind.ObjectMapper[] and javadoc:com.fasterxml.jackson.dataformat.xml.XmlMapper[] instances by using the environment.
+You can configure the javadoc:tools.jackson.databind.JsonMapper[] by using the environment.
 Jackson provides an extensive suite of on/off features that can be used to configure various aspects of its processing.
 These features are described in several enums (in Jackson) that map onto properties in the environment:
 
 |===
 | Enum | Property | Values
 
-| javadoc:com.fasterxml.jackson.databind.cfg.EnumFeature[]
+| javadoc:tools.jackson.databind.cfg.EnumFeature[]
 | `spring.jackson.datatype.enum.`
 | `true`, `false`
 
-| javadoc:com.fasterxml.jackson.databind.cfg.JsonNodeFeature[]
+| javadoc:tools.jackson.databind.cfg.JsonNodeFeature[]
 | `spring.jackson.datatype.json-node.`
 | `true`, `false`
 
-| javadoc:com.fasterxml.jackson.databind.DeserializationFeature[]
+| javadoc:tools.jackson.databind.DeserializationFeature[]
 | `spring.jackson.deserialization.`
 | `true`, `false`
 
-| javadoc:com.fasterxml.jackson.core.JsonGenerator$Feature[]
-| `spring.jackson.generator.`
-| `true`, `false`
-
-| javadoc:com.fasterxml.jackson.databind.MapperFeature[]
+| javadoc:tools.jackson.databind.MapperFeature[]
 | `spring.jackson.mapper.`
 | `true`, `false`
 
-| javadoc:com.fasterxml.jackson.core.JsonParser$Feature[]
-| `spring.jackson.parser.`
+| javadoc:tools.jackson.core.JsonReadFeature[]
+| `spring.jackson.read.`
 | `true`, `false`
 
-| javadoc:com.fasterxml.jackson.databind.SerializationFeature[]
+| javadoc:tools.jackson.core.JsonWriteFeature[]
+| `spring.jackson.write.`
+| `true`, `false`
+
+| javadoc:tools.jackson.databind.SerializationFeature[]
 | `spring.jackson.serialization.`
 | `true`, `false`
 
@@ -110,21 +98,19 @@ These features are described in several enums (in Jackson) that map onto propert
 For example, to enable pretty print, set `spring.jackson.serialization.indent_output=true`.
 Note that, thanks to the use of xref:reference:features/external-config.adoc#features.external-config.typesafe-configuration-properties.relaxed-binding[relaxed binding], the case of `indent_output` does not have to match the case of the corresponding enum constant, which is `INDENT_OUTPUT`.
 
-This environment-based configuration is applied to the auto-configured javadoc:org.springframework.http.converter.json.Jackson2ObjectMapperBuilder[] bean and applies to any mappers created by using the builder, including the auto-configured javadoc:com.fasterxml.jackson.databind.ObjectMapper[] bean.
+This environment-based configuration is applied to the auto-configured javadoc:tools.jackson.databind.json.JsonMapper.Builder[] bean and applies to any mappers created by using the builder, including the auto-configured javadoc:tools.jackson.databind.json.JsonMapper[] bean.
 
-The context's javadoc:org.springframework.http.converter.json.Jackson2ObjectMapperBuilder[] can be customized by one or more javadoc:org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer[] beans.
+The context's javadoc:tools.jackson.databind.json.JsonMapper.Builder[] can be customized by one or more javadoc:org.springframework.boot.autoconfigure.jackson.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:com.fasterxml.jackson.databind.Module[] are automatically registered with the auto-configured javadoc:org.springframework.http.converter.json.Jackson2ObjectMapperBuilder[] and are applied to any javadoc:com.fasterxml.jackson.databind.ObjectMapper[] instances that it creates.
+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.
 This provides a global mechanism for contributing custom modules when you add new features to your application.
 
-NOTE: If you wish to register additional modules programmatically using a javadoc:org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer[], make sure to use the `modulesToInstall` method that takes a consumer as the other variants are not additive.
+If you want to replace the default javadoc:tools.jackson.databind.json.JsonMapper[] completely, either define a javadoc:org.springframework.context.annotation.Bean[format=annotation] of that type or, if you prefer the builder-based approach, define a javadoc:tools.jackson.databind.json.JsonMapper.Builder[] javadoc:org.springframework.context.annotation.Bean[format=annotation].
+When defining an javadoc:tools.jackson.databind.json.JsonMapper[] bean, marking it as javadoc:org.springframework.context.annotation.Primary[format=annotation] is recommended as the auto-configuration's javadoc:tools.jackson.databind.json.JsonMapper[] that it will replace is javadoc:org.springframework.context.annotation.Primary[format=annotation].
+Note that, in either case, doing so disables all auto-configuration of the javadoc:tools.jackson.databind.json.JsonMapper[].
 
-If you want to replace the default javadoc:com.fasterxml.jackson.databind.ObjectMapper[] completely, either define a javadoc:org.springframework.context.annotation.Bean[format=annotation] of that type or, if you prefer the builder-based approach, define a javadoc:org.springframework.http.converter.json.Jackson2ObjectMapperBuilder[] javadoc:org.springframework.context.annotation.Bean[format=annotation].
-When defining an javadoc:com.fasterxml.jackson.databind.ObjectMapper[] bean, marking it as javadoc:org.springframework.context.annotation.Primary[format=annotation] is recommended as the auto-configuration's javadoc:com.fasterxml.jackson.databind.ObjectMapper[] that it will replace is javadoc:org.springframework.context.annotation.Primary[format=annotation].
-Note that, in either case, doing so disables all auto-configuration of the javadoc:com.fasterxml.jackson.databind.ObjectMapper[].
-
-If you provide any javadoc:java.beans.Beans[format=annotation] of type javadoc:org.springframework.http.converter.json.MappingJackson2HttpMessageConverter[], they replace the default value in the MVC configuration.
+If you provide any javadoc:java.beans.Beans[format=annotation] of type javadoc:org.springframework.http.converter.json.JacksonJsonHttpMessageConverter[], they replace the default value in the MVC configuration.
 Also, a convenience bean of type javadoc:org.springframework.boot.http.converter.autoconfigure.HttpMessageConverters[] is provided (and is always available if you use the default MVC configuration).
 It has some useful methods to access the default and user-enhanced message converters.
 
@@ -137,7 +123,7 @@ See the xref:spring-mvc.adoc#howto.spring-mvc.customize-responsebody-rendering[]
 
 Spring uses javadoc:org.springframework.boot.http.converter.autoconfigure.HttpMessageConverters[] to render javadoc:org.springframework.web.bind.annotation.ResponseBody[format=annotation] (or responses from javadoc:org.springframework.web.bind.annotation.RestController[format=annotation]).
 You can contribute additional converters by adding beans of the appropriate type in a Spring Boot context.
-If a bean you add is of a type that would have been included by default anyway (such as javadoc:org.springframework.http.converter.json.MappingJackson2HttpMessageConverter[] for JSON conversions), it replaces the default value.
+If a bean you add is of a type that would have been included by default anyway (such as javadoc:org.springframework.http.converter.json.JacksonJsonHttpMessageConverter[] for JSON conversions), it replaces the default value.
 A convenience bean of type javadoc:org.springframework.boot.http.converter.autoconfigure.HttpMessageConverters[] is provided and is always available if you use the default MVC configuration.
 It has some useful methods to access the default and user-enhanced message converters (For example, it can be useful if you want to manually inject them into a custom javadoc:org.springframework.web.client.RestTemplate[]).
 
diff --git a/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/features/json.adoc b/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/features/json.adoc
index 3fd663dd886..1dad5c0d1a8 100644
--- a/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/features/json.adoc
+++ b/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/features/json.adoc
@@ -4,7 +4,7 @@
 Spring Boot provides integration with three JSON mapping libraries:
 
 - Gson
-- Jackson
+- Jackson 3
 - JSON-B
 
 Jackson is the preferred and default library.
@@ -15,18 +15,18 @@ Jackson is the preferred and default library.
 == Jackson
 
 Auto-configuration for Jackson is provided and Jackson is part of `spring-boot-starter-json`.
-When Jackson is on the classpath an javadoc:com.fasterxml.jackson.databind.ObjectMapper[] bean is automatically configured.
-Several configuration properties are provided for xref:how-to:spring-mvc.adoc#howto.spring-mvc.customize-jackson-objectmapper[customizing the configuration of the javadoc:com.fasterxml.jackson.databind.ObjectMapper[]].
+When Jackson is on the classpath a javadoc:tools.jackson.databind.json.JsonMapper[] bean is automatically configured.
+Several configuration properties are provided for xref:how-to:spring-mvc.adoc#howto.spring-mvc.customize-jackson-jsonmapper[customizing the configuration of the javadoc:tools.jackson.databind.json.JsonMapper[]].
 
 
 
 [[features.json.jackson.custom-serializers-and-deserializers]]
 === Custom Serializers and Deserializers
 
-If you use Jackson to serialize and deserialize JSON data, you might want to write your own javadoc:com.fasterxml.jackson.databind.JsonSerializer[] and javadoc:com.fasterxml.jackson.databind.JsonDeserializer[] classes.
+If you use Jackson to serialize and deserialize JSON data, you might want to write your own javadoc:tools.jackson.databind.ValueSerializer[] and javadoc:tools.jackson.databind.ValueDeserializer[] classes.
 Custom serializers are usually https://github.com/FasterXML/jackson-docs/wiki/JacksonHowToCustomSerializers[registered with Jackson through a module], but Spring Boot provides an alternative javadoc:org.springframework.boot.jackson.JsonComponent[format=annotation] annotation that makes it easier to directly register Spring Beans.
 
-You can use the javadoc:org.springframework.boot.jackson.JsonComponent[format=annotation] annotation directly on javadoc:com.fasterxml.jackson.databind.JsonSerializer[], javadoc:com.fasterxml.jackson.databind.JsonDeserializer[] or javadoc:com.fasterxml.jackson.databind.KeyDeserializer[] implementations.
+You can use the javadoc:org.springframework.boot.jackson.JsonComponent[format=annotation] annotation directly on javadoc:tools.jackson.databind.ValueSerializer[], javadoc:tools.jackson.databind.ValueDeserializer[] or javadoc:tools.jackson.databind.KeyDeserializer[] implementations.
 You can also use it on classes that contain serializers/deserializers as inner classes, as shown in the following example:
 
 include-code::MyJsonComponent[]
@@ -34,10 +34,10 @@ include-code::MyJsonComponent[]
 All javadoc:org.springframework.boot.jackson.JsonComponent[format=annotation] beans in the javadoc:org.springframework.context.ApplicationContext[] are automatically registered with Jackson.
 Because javadoc:org.springframework.boot.jackson.JsonComponent[format=annotation] is meta-annotated with javadoc:org.springframework.stereotype.Component[format=annotation], the usual component-scanning rules apply.
 
-Spring Boot also provides javadoc:org.springframework.boot.jackson.JsonObjectSerializer[] and javadoc:org.springframework.boot.jackson.JsonObjectDeserializer[] base classes that provide useful alternatives to the standard Jackson versions when serializing objects.
-See javadoc:org.springframework.boot.jackson.JsonObjectSerializer[] and javadoc:org.springframework.boot.jackson.JsonObjectDeserializer[] in the API documentation for details.
+Spring Boot also provides javadoc:org.springframework.boot.jackson.ObjectValueSerializer[] and javadoc:org.springframework.boot.jackson.ObjectValueDeserializer[] base classes that provide useful alternatives to the standard Jackson versions when serializing objects.
+See javadoc:org.springframework.boot.jackson.ObjectValueSerializer[] and javadoc:org.springframework.boot.jackson.ObjectValueDeserializer[] in the API documentation for details.
 
-The example above can be rewritten to use javadoc:org.springframework.boot.jackson.JsonObjectSerializer[] and javadoc:org.springframework.boot.jackson.JsonObjectDeserializer[] as follows:
+The example above can be rewritten to use javadoc:org.springframework.boot.jackson.ObjectValueSerializer[] and javadoc:org.springframework.boot.jackson.ObjectValueDeserializer[] as follows:
 
 include-code::object/MyJsonComponent[]
 
@@ -47,7 +47,7 @@ include-code::object/MyJsonComponent[]
 === Mixins
 
 Jackson has support for mixins that can be used to mix additional annotations into those already declared on a target class.
-Spring Boot's Jackson auto-configuration will scan your application's packages for classes annotated with javadoc:org.springframework.boot.jackson.JsonMixin[format=annotation] and register them with the auto-configured javadoc:com.fasterxml.jackson.databind.ObjectMapper[].
+Spring Boot's Jackson auto-configuration will scan your application's packages for classes annotated with javadoc:org.springframework.boot.jackson.JsonMixin[format=annotation] and register them with the auto-configured javadoc:tools.jackson.databind.json.JsonMapper[].
 The registration is performed by Spring Boot's javadoc:org.springframework.boot.jackson.JsonMixinModule[].
 
 
diff --git a/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/spring-boot-applications.adoc b/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/spring-boot-applications.adoc
index e8384aa83de..330f1d92227 100644
--- a/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/spring-boot-applications.adoc
+++ b/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/testing/spring-boot-applications.adoc
@@ -281,7 +281,7 @@ You can use this combination if you are not interested in "`slicing`" your appli
 To test that object JSON serialization and deserialization is working as expected, you can use the javadoc:org.springframework.boot.test.autoconfigure.json.JsonTest[format=annotation] annotation.
 javadoc:org.springframework.boot.test.autoconfigure.json.JsonTest[format=annotation] auto-configures the available supported JSON mapper, which can be one of the following libraries:
 
-* Jackson javadoc:com.fasterxml.jackson.databind.ObjectMapper[], any javadoc:org.springframework.boot.jackson.JsonComponent[format=annotation] beans and any Jackson javadoc:com.fasterxml.jackson.databind.Module[]
+* Jackson javadoc:tools.jackson.databind.JsonMapper[], any javadoc:org.springframework.boot.jackson.JsonComponent[format=annotation] beans and any Jackson javadoc:tools.jackson.databind.JacksonModule[]
 * `Gson`
 * `Jsonb`
 
@@ -317,7 +317,7 @@ javadoc:org.springframework.boot.context.properties.EnableConfigurationPropertie
 
 TIP: A list of the auto-configuration settings that are enabled by javadoc:org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest[format=annotation] can be xref:appendix:test-auto-configuration/index.adoc[found in the appendix].
 
-TIP: If you need to register extra components, such as the Jackson javadoc:com.fasterxml.jackson.databind.Module[], you can import additional configuration classes by using javadoc:org.springframework.context.annotation.Import[format=annotation] on your test.
+TIP: If you need to register extra components, such as a javadoc:tools.jackson.databind.JacksonModule[], you can import additional configuration classes by using javadoc:org.springframework.context.annotation.Import[format=annotation] on your test.
 
 Often, javadoc:org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest[format=annotation] is limited to a single controller and is used in combination with javadoc:org.springframework.test.context.bean.override.mockito.MockitoBean[format=annotation] to provide mock implementations for required collaborators.
 
@@ -361,7 +361,7 @@ javadoc:org.springframework.boot.context.properties.EnableConfigurationPropertie
 
 TIP: A list of the auto-configurations that are enabled by javadoc:org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest[format=annotation] can be xref:appendix:test-auto-configuration/index.adoc[found in the appendix].
 
-TIP: If you need to register extra components, such as Jackson javadoc:com.fasterxml.jackson.databind.Module[], you can import additional configuration classes using javadoc:org.springframework.context.annotation.Import[format=annotation] on your test.
+TIP: If you need to register extra components, such as a javadoc:tools.jackson.databind.JacksonModule[], you can import additional configuration classes using javadoc:org.springframework.context.annotation.Import[format=annotation] on your test.
 
 Often, javadoc:org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest[format=annotation] is limited to a single controller and used in combination with the javadoc:org.springframework.test.context.bean.override.mockito.MockitoBean[format=annotation] annotation to provide mock implementations for required collaborators.
 
diff --git a/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/web/spring-hateoas.adoc b/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/web/spring-hateoas.adoc
index 7ae39d0d073..d61fc774278 100644
--- a/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/web/spring-hateoas.adoc
+++ b/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/web/spring-hateoas.adoc
@@ -2,11 +2,11 @@
 = Spring HATEOAS
 
 If you develop a RESTful API that makes use of hypermedia, Spring Boot provides auto-configuration for Spring HATEOAS that works well with most applications.
-The auto-configuration replaces the need to use javadoc:org.springframework.hateoas.config.EnableHypermediaSupport[format=annotation] and registers a number of beans to ease building hypermedia-based applications, including a javadoc:org.springframework.hateoas.client.LinkDiscoverers[] (for client side support) and an javadoc:com.fasterxml.jackson.databind.ObjectMapper[] configured to correctly marshal responses into the desired representation.
-The javadoc:com.fasterxml.jackson.databind.ObjectMapper[] is customized by setting the various `spring.jackson.*` properties or, if one exists, by a javadoc:org.springframework.http.converter.json.Jackson2ObjectMapperBuilder[] bean.
+The auto-configuration replaces the need to use javadoc:org.springframework.hateoas.config.EnableHypermediaSupport[format=annotation] and registers a number of beans to ease building hypermedia-based applications, including a javadoc:org.springframework.hateoas.client.LinkDiscoverers[] (for client side support) and an javadoc:tools.jackson.databind.json.JsonMapper[] configured to correctly marshal responses into the desired representation.
+The javadoc:tools.jackson.databind.json.JsontMapper[] is customized by setting the various `spring.jackson.*` properties or, if any exist, the javadoc:org.springframework.boot.jackson.autoconfigure.JsonMapperBuilderCustomizer[] beans.
 
 You can take control of Spring HATEOAS's configuration by using javadoc:org.springframework.hateoas.config.EnableHypermediaSupport[format=annotation].
-Note that doing so disables the javadoc:com.fasterxml.jackson.databind.ObjectMapper[] customization described earlier.
+Note that doing so disables the javadoc:tools.jackson.databind.json.JsonMapper[] customization described earlier.
 
 WARNING: `spring-boot-starter-hateoas` is specific to Spring MVC and should not be combined with Spring WebFlux.
 In order to use Spring HATEOAS with Spring WebFlux, you can add a direct dependency on `org.springframework.hateoas:spring-hateoas` along with `spring-boot-starter-webflux`.
diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/json/jackson/customserializersanddeserializers/MyJsonComponent.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/json/jackson/customserializersanddeserializers/MyJsonComponent.java
index f04d8c590d4..4ed935ac541 100644
--- a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/json/jackson/customserializersanddeserializers/MyJsonComponent.java
+++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/json/jackson/customserializersanddeserializers/MyJsonComponent.java
@@ -16,41 +16,37 @@
 
 package org.springframework.boot.docs.features.json.jackson.customserializersanddeserializers;
 
-import java.io.IOException;
-
-import com.fasterxml.jackson.core.JsonGenerator;
-import com.fasterxml.jackson.core.JsonParser;
-import com.fasterxml.jackson.core.ObjectCodec;
-import com.fasterxml.jackson.databind.DeserializationContext;
-import com.fasterxml.jackson.databind.JsonDeserializer;
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.JsonSerializer;
-import com.fasterxml.jackson.databind.SerializerProvider;
+import tools.jackson.core.JsonGenerator;
+import tools.jackson.core.JsonParser;
+import tools.jackson.databind.DeserializationContext;
+import tools.jackson.databind.JsonNode;
+import tools.jackson.databind.SerializationContext;
+import tools.jackson.databind.ValueDeserializer;
+import tools.jackson.databind.ValueSerializer;
 
 import org.springframework.boot.jackson.JsonComponent;
 
 @JsonComponent
 public class MyJsonComponent {
 
-	public static class Serializer extends JsonSerializer {
+	public static class Serializer extends ValueSerializer {
 
 		@Override
-		public void serialize(MyObject value, JsonGenerator jgen, SerializerProvider serializers) throws IOException {
+		public void serialize(MyObject value, JsonGenerator jgen, SerializationContext context) {
 			jgen.writeStartObject();
-			jgen.writeStringField("name", value.getName());
-			jgen.writeNumberField("age", value.getAge());
+			jgen.writeStringProperty("name", value.getName());
+			jgen.writeNumberProperty("age", value.getAge());
 			jgen.writeEndObject();
 		}
 
 	}
 
-	public static class Deserializer extends JsonDeserializer {
+	public static class Deserializer extends ValueDeserializer {
 
 		@Override
-		public MyObject deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException {
-			ObjectCodec codec = jsonParser.getCodec();
-			JsonNode tree = codec.readTree(jsonParser);
-			String name = tree.get("name").textValue();
+		public MyObject deserialize(JsonParser jsonParser, DeserializationContext ctxt) {
+			JsonNode tree = jsonParser.readValueAsTree();
+			String name = tree.get("name").stringValue();
 			int age = tree.get("age").intValue();
 			return new MyObject(name, age);
 		}
diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/json/jackson/customserializersanddeserializers/object/MyJsonComponent.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/json/jackson/customserializersanddeserializers/object/MyJsonComponent.java
index 35cf5378414..7a4ba1e6882 100644
--- a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/json/jackson/customserializersanddeserializers/object/MyJsonComponent.java
+++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/features/json/jackson/customserializersanddeserializers/object/MyJsonComponent.java
@@ -16,38 +16,33 @@
 
 package org.springframework.boot.docs.features.json.jackson.customserializersanddeserializers.object;
 
-import java.io.IOException;
-
-import com.fasterxml.jackson.core.JsonGenerator;
-import com.fasterxml.jackson.core.JsonParser;
-import com.fasterxml.jackson.core.ObjectCodec;
-import com.fasterxml.jackson.databind.DeserializationContext;
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.SerializerProvider;
+import tools.jackson.core.JsonGenerator;
+import tools.jackson.core.JsonParser;
+import tools.jackson.databind.DeserializationContext;
+import tools.jackson.databind.JsonNode;
+import tools.jackson.databind.SerializationContext;
 
 import org.springframework.boot.jackson.JsonComponent;
-import org.springframework.boot.jackson.JsonObjectDeserializer;
-import org.springframework.boot.jackson.JsonObjectSerializer;
+import org.springframework.boot.jackson.ObjectValueDeserializer;
+import org.springframework.boot.jackson.ObjectValueSerializer;
 
 @JsonComponent
 public class MyJsonComponent {
 
-	public static class Serializer extends JsonObjectSerializer {
+	public static class Serializer extends ObjectValueSerializer {
 
 		@Override
-		protected void serializeObject(MyObject value, JsonGenerator jgen, SerializerProvider provider)
-				throws IOException {
-			jgen.writeStringField("name", value.getName());
-			jgen.writeNumberField("age", value.getAge());
+		protected void serializeObject(MyObject value, JsonGenerator jgen, SerializationContext context) {
+			jgen.writeStringProperty("name", value.getName());
+			jgen.writeNumberProperty("age", value.getAge());
 		}
 
 	}
 
-	public static class Deserializer extends JsonObjectDeserializer {
+	public static class Deserializer extends ObjectValueDeserializer {
 
 		@Override
-		protected MyObject deserializeObject(JsonParser jsonParser, DeserializationContext context, ObjectCodec codec,
-				JsonNode tree) throws IOException {
+		protected MyObject deserializeObject(JsonParser jsonParser, DeserializationContext context, JsonNode tree) {
 			String name = nullSafeValue(tree.get("name"), String.class);
 			int age = nullSafeValue(tree.get("age"), Integer.class);
 			return new MyObject(name, age);
diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/json/jackson/customserializersanddeserializers/MyJsonComponent.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/json/jackson/customserializersanddeserializers/MyJsonComponent.kt
index 9cb98e49874..c1c8f8f8a05 100644
--- a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/json/jackson/customserializersanddeserializers/MyJsonComponent.kt
+++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/json/jackson/customserializersanddeserializers/MyJsonComponent.kt
@@ -16,36 +16,32 @@
 
 package org.springframework.boot.docs.features.json.jackson.customserializersanddeserializers
 
-import com.fasterxml.jackson.core.JsonGenerator
-import com.fasterxml.jackson.core.JsonParser
-import com.fasterxml.jackson.core.JsonProcessingException
-import com.fasterxml.jackson.databind.DeserializationContext
-import com.fasterxml.jackson.databind.JsonDeserializer
-import com.fasterxml.jackson.databind.JsonNode
-import com.fasterxml.jackson.databind.JsonSerializer
-import com.fasterxml.jackson.databind.SerializerProvider
+import tools.jackson.core.JsonGenerator
+import tools.jackson.core.JsonParser
+import tools.jackson.databind.DeserializationContext
+import tools.jackson.databind.JsonNode
+import tools.jackson.databind.SerializationContext
+import tools.jackson.databind.ValueDeserializer
+import tools.jackson.databind.ValueSerializer
+
 import org.springframework.boot.jackson.JsonComponent
-import java.io.IOException
 
 @JsonComponent
 class MyJsonComponent {
 
-	class Serializer : JsonSerializer() {
-		@Throws(IOException::class)
-		override fun serialize(value: MyObject, jgen: JsonGenerator, serializers: SerializerProvider) {
+	class Serializer : ValueSerializer() {
+		override fun serialize(value: MyObject, jgen: JsonGenerator, serializers: SerializationContext) {
 			jgen.writeStartObject()
-			jgen.writeStringField("name", value.name)
-			jgen.writeNumberField("age", value.age)
+			jgen.writeStringProperty("name", value.name)
+			jgen.writeNumberProperty("age", value.age)
 			jgen.writeEndObject()
 		}
 	}
 
-	class Deserializer : JsonDeserializer() {
-		@Throws(IOException::class, JsonProcessingException::class)
+	class Deserializer : ValueDeserializer() {
 		override fun deserialize(jsonParser: JsonParser, ctxt: DeserializationContext): MyObject {
-			val codec = jsonParser.codec
-			val tree = codec.readTree(jsonParser)
-			val name = tree["name"].textValue()
+			val tree = jsonParser.readValueAsTree()
+			val name = tree["name"].stringValue()
 			val age = tree["age"].intValue()
 			return MyObject(name, age)
 		}
diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/json/jackson/customserializersanddeserializers/object/MyJsonComponent.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/json/jackson/customserializersanddeserializers/object/MyJsonComponent.kt
index 5324a942ae8..d8cabe366b4 100644
--- a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/json/jackson/customserializersanddeserializers/object/MyJsonComponent.kt
+++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/features/json/jackson/customserializersanddeserializers/object/MyJsonComponent.kt
@@ -16,32 +16,29 @@
 
 package org.springframework.boot.docs.features.json.jackson.customserializersanddeserializers.`object`
 
-import com.fasterxml.jackson.core.JsonGenerator
-import com.fasterxml.jackson.core.JsonParser
-import com.fasterxml.jackson.core.ObjectCodec
-import com.fasterxml.jackson.databind.DeserializationContext
-import com.fasterxml.jackson.databind.JsonNode
-import com.fasterxml.jackson.databind.SerializerProvider
-import org.springframework.boot.jackson.JsonComponent
-import org.springframework.boot.jackson.JsonObjectDeserializer
-import org.springframework.boot.jackson.JsonObjectSerializer
-import java.io.IOException
+import tools.jackson.core.JsonGenerator
+import tools.jackson.core.JsonParser
+import tools.jackson.databind.DeserializationContext
+import tools.jackson.databind.JsonNode
+import tools.jackson.databind.SerializationContext
+
+import org.springframework.boot.jackson.JsonComponent;
+import org.springframework.boot.jackson.ObjectValueDeserializer
+import org.springframework.boot.jackson.ObjectValueSerializer
 
 @JsonComponent
 class MyJsonComponent {
 
-	class Serializer : JsonObjectSerializer() {
-		@Throws(IOException::class)
-		override fun serializeObject(value: MyObject, jgen: JsonGenerator, provider: SerializerProvider) {
-			jgen.writeStringField("name", value.name)
-			jgen.writeNumberField("age", value.age)
+	class Serializer : ObjectValueSerializer() {
+		override fun serializeObject(value: MyObject, jgen: JsonGenerator, context: SerializationContext) {
+			jgen.writeStringProperty("name", value.name)
+			jgen.writeNumberProperty("age", value.age)
 		}
 	}
 
-	class Deserializer : JsonObjectDeserializer() {
-		@Throws(IOException::class)
+	class Deserializer : ObjectValueDeserializer() {
 		override fun deserializeObject(jsonParser: JsonParser, context: DeserializationContext,
-				codec: ObjectCodec, tree: JsonNode): MyObject {
+				tree: JsonNode): MyObject {
 			val name = nullSafeValue(tree["name"], String::class.java) ?: throw IllegalStateException("name is null")
 			val age = nullSafeValue(tree["age"], Int::class.java) ?: throw IllegalStateException("age is null")
 			return MyObject(name, age)
diff --git a/gradle.properties b/gradle.properties
index 5c2870ac272..9ba37f3a7d6 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -11,7 +11,8 @@ checkstyleToolVersion=10.12.4
 commonsCodecVersion=1.19.0
 graalVersion=22.3
 hamcrestVersion=3.0
-jacksonVersion=2.20.0-rc1
+jackson2Version=2.20.0
+jacksonVersion=3.0.0-rc8
 javaFormatVersion=0.0.47
 junitJupiterVersion=5.13.4
 kotlinVersion=2.2.0
diff --git a/integration-test/spring-boot-actuator-integration-tests/build.gradle b/integration-test/spring-boot-actuator-integration-tests/build.gradle
index b998bb1dc7f..67b21439af0 100644
--- a/integration-test/spring-boot-actuator-integration-tests/build.gradle
+++ b/integration-test/spring-boot-actuator-integration-tests/build.gradle
@@ -43,4 +43,5 @@ dependencies {
 	testImplementation("net.minidev:json-smart")
 	testImplementation("org.springframework.security:spring-security-web")
 	testRuntimeOnly("ch.qos.logback:logback-classic")
+	testRuntimeOnly("com.fasterxml.jackson.datatype:jackson-datatype-jsr310") // Required for Jersey
 }
diff --git a/integration-test/spring-boot-actuator-integration-tests/src/test/java/org/springframework/boot/actuate/metrics/MetricsEndpointWebIntegrationTests.java b/integration-test/spring-boot-actuator-integration-tests/src/test/java/org/springframework/boot/actuate/metrics/MetricsEndpointWebIntegrationTests.java
index acc566dfe49..9bc8516634f 100644
--- a/integration-test/spring-boot-actuator-integration-tests/src/test/java/org/springframework/boot/actuate/metrics/MetricsEndpointWebIntegrationTests.java
+++ b/integration-test/spring-boot-actuator-integration-tests/src/test/java/org/springframework/boot/actuate/metrics/MetricsEndpointWebIntegrationTests.java
@@ -16,16 +16,15 @@
 
 package org.springframework.boot.actuate.metrics;
 
-import java.io.IOException;
 import java.util.List;
 import java.util.Map;
 
-import com.fasterxml.jackson.databind.ObjectMapper;
 import io.micrometer.core.instrument.MeterRegistry;
 import io.micrometer.core.instrument.MockClock;
 import io.micrometer.core.instrument.binder.jvm.JvmMemoryMetrics;
 import io.micrometer.core.instrument.simple.SimpleConfig;
 import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
+import tools.jackson.databind.ObjectMapper;
 
 import org.springframework.boot.actuate.endpoint.web.test.WebEndpointTest;
 import org.springframework.boot.micrometer.metrics.actuate.endpoint.MetricsEndpoint;
@@ -49,7 +48,7 @@ class MetricsEndpointWebIntegrationTests {
 
 	@WebEndpointTest
 	@SuppressWarnings("unchecked")
-	void listNames(WebTestClient client) throws IOException {
+	void listNames(WebTestClient client) {
 		String responseBody = client.get()
 			.uri("/actuator/metrics")
 			.exchange()
diff --git a/module/spring-boot-activemq/src/test/java/org/springframework/boot/activemq/autoconfigure/ActiveMQAutoConfigurationTests.java b/module/spring-boot-activemq/src/test/java/org/springframework/boot/activemq/autoconfigure/ActiveMQAutoConfigurationTests.java
index 461034532cf..cd321351180 100644
--- a/module/spring-boot-activemq/src/test/java/org/springframework/boot/activemq/autoconfigure/ActiveMQAutoConfigurationTests.java
+++ b/module/spring-boot-activemq/src/test/java/org/springframework/boot/activemq/autoconfigure/ActiveMQAutoConfigurationTests.java
@@ -16,6 +16,12 @@
 
 package org.springframework.boot.activemq.autoconfigure;
 
+import java.util.HashMap;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
 import jakarta.jms.ConnectionFactory;
 import org.apache.activemq.ActiveMQConnectionFactory;
 import org.junit.jupiter.api.Test;
@@ -47,6 +53,14 @@ class ActiveMQAutoConfigurationTests {
 	private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
 		.withConfiguration(AutoConfigurations.of(ActiveMQAutoConfiguration.class, JmsAutoConfiguration.class));
 
+	@Test
+	void dunno() throws JsonProcessingException {
+		Map map = new HashMap<>();
+		map.put("key", null);
+		String string = new ObjectMapper().setDefaultPropertyInclusion(Include.NON_NULL).writeValueAsString(map);
+		System.out.println(string);
+	}
+
 	@Test
 	void brokerIsEmbeddedByDefault() {
 		this.contextRunner.withUserConfiguration(EmptyConfiguration.class).run((context) -> {
diff --git a/module/spring-boot-actuator-autoconfigure/build.gradle b/module/spring-boot-actuator-autoconfigure/build.gradle
index 3989a98855f..160705e92f6 100644
--- a/module/spring-boot-actuator-autoconfigure/build.gradle
+++ b/module/spring-boot-actuator-autoconfigure/build.gradle
@@ -29,13 +29,11 @@ dependencies {
 	api(project(":core:spring-boot-autoconfigure"))
 	api(project(":module:spring-boot-actuator"))
 
-	implementation("com.fasterxml.jackson.core:jackson-databind")
-	implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")
+	implementation("tools.jackson.core:jackson-databind")
 
 	optional(project(":module:spring-boot-health"))
 	optional(project(":module:spring-boot-web-server"))
 
-	optional("com.fasterxml.jackson.core:jackson-databind")
 	optional("io.micrometer:micrometer-core")
 	optional("io.projectreactor:reactor-core")
 	optional("jakarta.servlet:jakarta.servlet-api")
diff --git a/module/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jackson/JacksonEndpointAutoConfiguration.java b/module/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jackson/JacksonEndpointAutoConfiguration.java
index 1e2b62ef515..e7e3d6bfcf2 100644
--- a/module/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jackson/JacksonEndpointAutoConfiguration.java
+++ b/module/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jackson/JacksonEndpointAutoConfiguration.java
@@ -20,17 +20,15 @@ import java.util.HashSet;
 import java.util.Set;
 
 import com.fasterxml.jackson.annotation.JsonInclude.Include;
-import com.fasterxml.jackson.databind.MapperFeature;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.SerializationFeature;
+import tools.jackson.databind.ObjectMapper;
+import tools.jackson.databind.json.JsonMapper;
 
-import org.springframework.boot.actuate.endpoint.jackson.EndpointObjectMapper;
+import org.springframework.boot.actuate.endpoint.jackson.EndpointJsonMapper;
 import org.springframework.boot.autoconfigure.AutoConfiguration;
 import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
 import org.springframework.context.annotation.Bean;
-import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
 import org.springframework.util.ClassUtils;
 
 /**
@@ -40,30 +38,27 @@ import org.springframework.util.ClassUtils;
  * @since 3.0.0
  */
 @AutoConfiguration
-@SuppressWarnings("removal")
 public final class JacksonEndpointAutoConfiguration {
 
 	private static final String CONTRIBUTED_HEALTH = "org.springframework.boot.health.contributor.ContributedHealth";
 
 	@Bean
 	@ConditionalOnBooleanProperty(name = "management.endpoints.jackson.isolated-object-mapper", matchIfMissing = true)
-	@ConditionalOnClass({ ObjectMapper.class, Jackson2ObjectMapperBuilder.class })
-	EndpointObjectMapper endpointObjectMapper() {
-		ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json()
-			.featuresToEnable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY)
-			.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS,
-					SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS)
-			.serializationInclusion(Include.NON_NULL)
+	@ConditionalOnClass(ObjectMapper.class)
+	EndpointJsonMapper endpointJsonMapper() {
+		JsonMapper jsonMapper = JsonMapper.builder()
+			.changeDefaultPropertyInclusion(
+					(value) -> value.withValueInclusion(Include.NON_NULL).withContentInclusion(Include.NON_NULL))
 			.build();
-		Set> supportedTypes = new HashSet<>(EndpointObjectMapper.DEFAULT_SUPPORTED_TYPES);
+		Set> supportedTypes = new HashSet<>(EndpointJsonMapper.DEFAULT_SUPPORTED_TYPES);
 		if (ClassUtils.isPresent(CONTRIBUTED_HEALTH, null)) {
 			supportedTypes.add(ClassUtils.resolveClassName(CONTRIBUTED_HEALTH, null));
 		}
-		return new EndpointObjectMapper() {
+		return new EndpointJsonMapper() {
 
 			@Override
-			public ObjectMapper get() {
-				return objectMapper;
+			public JsonMapper get() {
+				return jsonMapper;
 			}
 
 			@Override
diff --git a/module/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/JmxEndpointAutoConfiguration.java b/module/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/JmxEndpointAutoConfiguration.java
index 34228701f8c..40a04438de1 100644
--- a/module/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/JmxEndpointAutoConfiguration.java
+++ b/module/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/jmx/JmxEndpointAutoConfiguration.java
@@ -18,7 +18,7 @@ package org.springframework.boot.actuate.autoconfigure.endpoint.jmx;
 
 import javax.management.MBeanServer;
 
-import com.fasterxml.jackson.databind.ObjectMapper;
+import tools.jackson.databind.ObjectMapper;
 
 import org.springframework.beans.factory.ObjectProvider;
 import org.springframework.boot.LazyInitializationExcludeFilter;
diff --git a/module/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/jackson/JacksonEndpointAutoConfigurationTests.java b/module/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/jackson/JacksonEndpointAutoConfigurationTests.java
index e1529e884f1..cf26d2c1def 100644
--- a/module/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/jackson/JacksonEndpointAutoConfigurationTests.java
+++ b/module/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/jackson/JacksonEndpointAutoConfigurationTests.java
@@ -22,10 +22,10 @@ import java.time.format.DateTimeFormatter;
 import java.util.HashMap;
 import java.util.Map;
 
-import com.fasterxml.jackson.databind.ObjectMapper;
 import org.junit.jupiter.api.Test;
+import tools.jackson.databind.json.JsonMapper;
 
-import org.springframework.boot.actuate.endpoint.jackson.EndpointObjectMapper;
+import org.springframework.boot.actuate.endpoint.jackson.EndpointJsonMapper;
 import org.springframework.boot.autoconfigure.AutoConfigurations;
 import org.springframework.boot.test.context.runner.ApplicationContextRunner;
 import org.springframework.context.annotation.Bean;
@@ -44,49 +44,49 @@ class JacksonEndpointAutoConfigurationTests {
 		.withConfiguration(AutoConfigurations.of(JacksonEndpointAutoConfiguration.class));
 
 	@Test
-	void endpointObjectMapperWhenNoProperty() {
-		this.runner.run((context) -> assertThat(context).hasSingleBean(EndpointObjectMapper.class));
+	void endpointJsonMapperWhenNoProperty() {
+		this.runner.run((context) -> assertThat(context).hasSingleBean(EndpointJsonMapper.class));
 	}
 
 	@Test
-	void endpointObjectMapperWhenPropertyTrue() {
+	void endpointJsonMapperWhenPropertyTrue() {
 		this.runner.withPropertyValues("management.endpoints.jackson.isolated-object-mapper=true")
-			.run((context) -> assertThat(context).hasSingleBean(EndpointObjectMapper.class));
+			.run((context) -> assertThat(context).hasSingleBean(EndpointJsonMapper.class));
 	}
 
 	@Test
-	void endpointObjectMapperWhenPropertyFalse() {
+	void endpointJsonMapperWhenPropertyFalse() {
 		this.runner.withPropertyValues("management.endpoints.jackson.isolated-object-mapper=false")
-			.run((context) -> assertThat(context).doesNotHaveBean(EndpointObjectMapper.class));
+			.run((context) -> assertThat(context).doesNotHaveBean(EndpointJsonMapper.class));
 	}
 
 	@Test
-	void endpointObjectMapperDoesNotSerializeDatesAsTimestamps() {
+	void endpointJsonMapperDoesNotSerializeDatesAsTimestamps() {
 		this.runner.run((context) -> {
-			ObjectMapper objectMapper = context.getBean(EndpointObjectMapper.class).get();
+			JsonMapper jsonMapper = context.getBean(EndpointJsonMapper.class).get();
 			Instant now = Instant.now();
-			String json = objectMapper.writeValueAsString(Map.of("timestamp", now));
+			String json = jsonMapper.writeValueAsString(Map.of("timestamp", now));
 			assertThat(json).contains(DateTimeFormatter.ISO_INSTANT.format(now));
 		});
 	}
 
 	@Test
-	void endpointObjectMapperDoesNotSerializeDurationsAsTimestamps() {
+	void endpointJsonMapperDoesNotSerializeDurationsAsTimestamps() {
 		this.runner.run((context) -> {
-			ObjectMapper objectMapper = context.getBean(EndpointObjectMapper.class).get();
+			JsonMapper jsonMapper = context.getBean(EndpointJsonMapper.class).get();
 			Duration duration = Duration.ofSeconds(42);
-			String json = objectMapper.writeValueAsString(Map.of("duration", duration));
+			String json = jsonMapper.writeValueAsString(Map.of("duration", duration));
 			assertThat(json).contains(duration.toString());
 		});
 	}
 
 	@Test
-	void endpointObjectMapperDoesNotSerializeNullValues() {
+	void endpointJsonMapperDoesNotSerializeNullValues() {
 		this.runner.run((context) -> {
-			ObjectMapper objectMapper = context.getBean(EndpointObjectMapper.class).get();
+			JsonMapper jsonMapper = context.getBean(EndpointJsonMapper.class).get();
 			HashMap map = new HashMap<>();
 			map.put("key", null);
-			String json = objectMapper.writeValueAsString(map);
+			String json = jsonMapper.writeValueAsString(map);
 			assertThat(json).isEqualTo("{}");
 		});
 	}
@@ -95,16 +95,16 @@ class JacksonEndpointAutoConfigurationTests {
 	static class TestEndpointMapperConfiguration {
 
 		@Bean
-		TestEndpointObjectMapper testEndpointObjectMapper() {
-			return new TestEndpointObjectMapper();
+		TestEndpointJsonMapper testEndpointJsonMapper() {
+			return new TestEndpointJsonMapper();
 		}
 
 	}
 
-	static class TestEndpointObjectMapper implements EndpointObjectMapper {
+	static class TestEndpointJsonMapper implements EndpointJsonMapper {
 
 		@Override
-		public ObjectMapper get() {
+		public JsonMapper get() {
 			return null;
 		}
 
diff --git a/module/spring-boot-actuator/build.gradle b/module/spring-boot-actuator/build.gradle
index 64ca8a10929..5078fd94fcb 100644
--- a/module/spring-boot-actuator/build.gradle
+++ b/module/spring-boot-actuator/build.gradle
@@ -32,8 +32,6 @@ dependencies {
 	optional(project(":module:spring-boot-jsonb"))
 	optional(project(":module:spring-boot-validation"))
 	optional(project(":module:spring-boot-web-server"))
-	optional("com.fasterxml.jackson.core:jackson-databind")
-	optional("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")
 	optional("com.github.ben-manes.caffeine:caffeine")
 	optional("com.google.code.findbugs:jsr305")
 	optional("com.zaxxer:HikariCP")
@@ -57,8 +55,8 @@ dependencies {
 	optional("org.springframework.graphql:spring-graphql")
 	optional("org.springframework.security:spring-security-core")
 	optional("org.springframework.security:spring-security-web")
+	optional("tools.jackson.core:jackson-databind")
 
-	testFixturesApi("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")
 	testFixturesImplementation("org.junit.jupiter:junit-jupiter-api")
 	testFixturesImplementation("org.springframework:spring-test")
 	testFixturesImplementation("org.springframework:spring-webflux")
diff --git a/module/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpoint.java b/module/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpoint.java
index 1266264028c..051df426522 100644
--- a/module/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpoint.java
+++ b/module/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpoint.java
@@ -29,30 +29,30 @@ import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
 import com.fasterxml.jackson.annotation.JsonInclude.Include;
-import com.fasterxml.jackson.core.JsonGenerator;
-import com.fasterxml.jackson.databind.BeanDescription;
-import com.fasterxml.jackson.databind.MapperFeature;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.SerializationConfig;
-import com.fasterxml.jackson.databind.SerializationFeature;
-import com.fasterxml.jackson.databind.SerializerProvider;
-import com.fasterxml.jackson.databind.introspect.Annotated;
-import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
-import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
-import com.fasterxml.jackson.databind.json.JsonMapper;
-import com.fasterxml.jackson.databind.module.SimpleModule;
-import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
-import com.fasterxml.jackson.databind.ser.BeanSerializerFactory;
-import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
-import com.fasterxml.jackson.databind.ser.PropertyWriter;
-import com.fasterxml.jackson.databind.ser.SerializerFactory;
-import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
-import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
-import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
-import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.jspecify.annotations.Nullable;
+import tools.jackson.core.JsonGenerator;
+import tools.jackson.databind.BeanDescription;
+import tools.jackson.databind.ObjectMapper;
+import tools.jackson.databind.SerializationConfig;
+import tools.jackson.databind.SerializationContext;
+import tools.jackson.databind.SerializationFeature;
+import tools.jackson.databind.cfg.MapperConfig;
+import tools.jackson.databind.introspect.Annotated;
+import tools.jackson.databind.introspect.AnnotatedMethod;
+import tools.jackson.databind.introspect.DefaultAccessorNamingStrategy;
+import tools.jackson.databind.introspect.JacksonAnnotationIntrospector;
+import tools.jackson.databind.json.JsonMapper;
+import tools.jackson.databind.module.SimpleModule;
+import tools.jackson.databind.ser.BeanPropertyWriter;
+import tools.jackson.databind.ser.BeanSerializerFactory;
+import tools.jackson.databind.ser.PropertyWriter;
+import tools.jackson.databind.ser.SerializerFactory;
+import tools.jackson.databind.ser.ValueSerializerModifier;
+import tools.jackson.databind.ser.std.SimpleBeanPropertyFilter;
+import tools.jackson.databind.ser.std.SimpleFilterProvider;
+import tools.jackson.databind.ser.std.ToStringSerializer;
 
 import org.springframework.beans.BeansException;
 import org.springframework.boot.actuate.endpoint.OperationResponseBody;
@@ -180,13 +180,10 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
 	 */
 	protected void configureJsonMapper(JsonMapper.Builder builder) {
 		builder.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
-		builder.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
-		builder.configure(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS, false);
-		builder.configure(MapperFeature.USE_STD_BEAN_NAMING, true);
-		builder.serializationInclusion(Include.NON_NULL);
+		builder.changeDefaultPropertyInclusion((value) -> value.withValueInclusion(Include.NON_NULL));
+		builder.accessorNaming(new DefaultAccessorNamingStrategy.Provider().withFirstCharAcceptance(true, false));
 		applyConfigurationPropertiesFilter(builder);
 		applySerializationModifier(builder);
-		builder.addModule(new JavaTimeModule());
 		builder.addModule(new ConfigurationPropertiesModule());
 	}
 
@@ -411,8 +408,8 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
 	private static final class ConfigurationPropertiesAnnotationIntrospector extends JacksonAnnotationIntrospector {
 
 		@Override
-		public Object findFilterId(Annotated a) {
-			Object id = super.findFilterId(a);
+		public Object findFilterId(MapperConfig config, Annotated a) {
+			Object id = super.findFilterId(config, a);
 			if (id == null) {
 				id = CONFIGURATION_PROPERTIES_FILTER_ID;
 			}
@@ -450,7 +447,7 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
 		}
 
 		@Override
-		public void serializeAsField(Object pojo, JsonGenerator jgen, SerializerProvider provider,
+		public void serializeAsProperty(Object pojo, JsonGenerator jgen, SerializationContext context,
 				PropertyWriter writer) throws Exception {
 			if (writer instanceof BeanPropertyWriter beanPropertyWriter) {
 				try {
@@ -470,7 +467,7 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
 					return;
 				}
 			}
-			super.serializeAsField(pojo, jgen, provider, writer);
+			super.serializeAsProperty(pojo, jgen, context, writer);
 		}
 
 	}
@@ -487,14 +484,14 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
 	}
 
 	/**
-	 * {@link BeanSerializerModifier} to return only relevant configuration properties.
+	 * {@link ValueSerializerModifier} to return only relevant configuration properties.
 	 */
-	protected static class GenericSerializerModifier extends BeanSerializerModifier {
+	protected static class GenericSerializerModifier extends ValueSerializerModifier {
 
 		private static final ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = new DefaultParameterNameDiscoverer();
 
 		@Override
-		public List changeProperties(SerializationConfig config, BeanDescription beanDesc,
+		public List changeProperties(SerializationConfig config, BeanDescription.Supplier beanDesc,
 				List beanProperties) {
 			List result = new ArrayList<>();
 			Class beanClass = beanDesc.getType().getRawClass();
@@ -508,7 +505,7 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
 			return result;
 		}
 
-		private boolean isCandidate(BeanDescription beanDesc, BeanPropertyWriter writer,
+		private boolean isCandidate(BeanDescription.Supplier beanDesc, BeanPropertyWriter writer,
 				@Nullable Constructor constructor) {
 			if (constructor != null) {
 				Parameter[] parameters = constructor.getParameters();
@@ -529,10 +526,10 @@ public class ConfigurationPropertiesReportEndpoint implements ApplicationContext
 			return isReadable(beanDesc, writer);
 		}
 
-		private boolean isReadable(BeanDescription beanDesc, BeanPropertyWriter writer) {
-			Class parentType = beanDesc.getType().getRawClass();
+		private boolean isReadable(BeanDescription.Supplier beanDesc, BeanPropertyWriter writer) {
+			Class parentType = beanDesc.get().getType().getRawClass();
 			Class type = writer.getType().getRawClass();
-			AnnotatedMethod setter = findSetter(beanDesc, writer);
+			AnnotatedMethod setter = findSetter(beanDesc.get(), writer);
 			// If there's a setter, we assume it's OK to report on the value,
 			// similarly, if there's no setter but the package names match, we assume
 			// that it is a nested class used solely for binding to config props, so it
diff --git a/module/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/OperationResponseBody.java b/module/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/OperationResponseBody.java
index 57f46e74aac..a60be05fc37 100644
--- a/module/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/OperationResponseBody.java
+++ b/module/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/OperationResponseBody.java
@@ -18,8 +18,8 @@ package org.springframework.boot.actuate.endpoint;
 
 import java.util.Map;
 
-import com.fasterxml.jackson.databind.ObjectMapper;
 import org.jspecify.annotations.Nullable;
+import tools.jackson.databind.ObjectMapper;
 
 import org.springframework.lang.Contract;
 
diff --git a/module/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jackson/EndpointObjectMapper.java b/module/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jackson/EndpointJsonMapper.java
similarity index 80%
rename from module/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jackson/EndpointObjectMapper.java
rename to module/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jackson/EndpointJsonMapper.java
index 91c11a9fb1a..a38f6d7f8bc 100644
--- a/module/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jackson/EndpointObjectMapper.java
+++ b/module/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jackson/EndpointJsonMapper.java
@@ -18,19 +18,19 @@ package org.springframework.boot.actuate.endpoint.jackson;
 
 import java.util.Set;
 
-import com.fasterxml.jackson.databind.ObjectMapper;
+import tools.jackson.databind.json.JsonMapper;
 
 import org.springframework.boot.actuate.endpoint.OperationResponseBody;
 
 /**
- * Interface used to supply the {@link ObjectMapper} that should be used when serializing
+ * Interface used to supply the {@link JsonMapper} that should be used when serializing
  * endpoint results.
  *
  * @author Phillip Webb
- * @since 3.0.0
+ * @since 4.0.0
  * @see OperationResponseBody
  */
-public interface EndpointObjectMapper {
+public interface EndpointJsonMapper {
 
 	/**
 	 * The default supported types.
@@ -38,16 +38,15 @@ public interface EndpointObjectMapper {
 	Set> DEFAULT_SUPPORTED_TYPES = Set.of(OperationResponseBody.class);
 
 	/**
-	 * Return the {@link ObjectMapper} that should be used to serialize
+	 * Return the {@link JsonMapper} that should be used to serialize
 	 * {@link OperationResponseBody} endpoint results.
 	 * @return the object mapper
 	 */
-	ObjectMapper get();
+	JsonMapper get();
 
 	/**
 	 * Return the types that this endpoint mapper supports.
 	 * @return the supported types
-	 * @since 4.0.0
 	 */
 	default Set> getSupportedTypes() {
 		return DEFAULT_SUPPORTED_TYPES;
diff --git a/module/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/JacksonJmxOperationResponseMapper.java b/module/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/JacksonJmxOperationResponseMapper.java
index dd38d459aa5..ef074123d1d 100644
--- a/module/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/JacksonJmxOperationResponseMapper.java
+++ b/module/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/JacksonJmxOperationResponseMapper.java
@@ -20,9 +20,9 @@ import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 
-import com.fasterxml.jackson.databind.JavaType;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import org.jspecify.annotations.Nullable;
+import tools.jackson.databind.JavaType;
+import tools.jackson.databind.ObjectMapper;
 
 /**
  * {@link JmxOperationResponseMapper} that delegates to a Jackson {@link ObjectMapper} to
diff --git a/module/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/audit/AuditEventTests.java b/module/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/audit/AuditEventTests.java
index 06691bb947c..b2a016acc90 100644
--- a/module/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/audit/AuditEventTests.java
+++ b/module/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/audit/AuditEventTests.java
@@ -20,6 +20,7 @@ import java.util.Collections;
 
 import org.json.JSONObject;
 import org.junit.jupiter.api.Test;
+import tools.jackson.databind.ObjectMapper;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
@@ -69,13 +70,10 @@ class AuditEventTests {
 	}
 
 	@Test
-	@SuppressWarnings({ "removal", "deprecation" })
 	void jsonFormat() throws Exception {
 		AuditEvent event = new AuditEvent("johannes", "UNKNOWN",
 				Collections.singletonMap("type", (Object) "BadCredentials"));
-		String json = org.springframework.http.converter.json.Jackson2ObjectMapperBuilder.json()
-			.build()
-			.writeValueAsString(event);
+		String json = new ObjectMapper().writeValueAsString(event);
 		JSONObject jsonObject = new JSONObject(json);
 		assertThat(jsonObject.getString("type")).isEqualTo("UNKNOWN");
 		assertThat(jsonObject.getJSONObject("data").getString("type")).isEqualTo("BadCredentials");
diff --git a/module/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointTests.java b/module/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointTests.java
index b26edf1b69d..7667401ecfc 100644
--- a/module/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointTests.java
+++ b/module/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointTests.java
@@ -179,7 +179,7 @@ class ConfigurationPropertiesReportEndpointTests {
 	void descriptorWithMixedCaseProperty() {
 		this.contextRunner.withUserConfiguration(MixedCasePropertiesConfiguration.class)
 			.run(assertProperties("mixedcase",
-					(properties) -> assertThat(properties.get("mIxedCase")).isEqualTo("mixed")));
+					(properties) -> assertThat(properties).containsEntry("mIxedCase", "mixed")));
 	}
 
 	@Test
diff --git a/module/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/JacksonJmxOperationResponseMapperTests.java b/module/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/JacksonJmxOperationResponseMapperTests.java
index 4f392c8f890..4159aaa31ab 100644
--- a/module/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/JacksonJmxOperationResponseMapperTests.java
+++ b/module/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/jmx/JacksonJmxOperationResponseMapperTests.java
@@ -23,9 +23,9 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
-import com.fasterxml.jackson.databind.JavaType;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import org.junit.jupiter.api.Test;
+import tools.jackson.databind.JavaType;
+import tools.jackson.databind.ObjectMapper;
 
 import org.springframework.boot.test.json.BasicJsonTester;
 
diff --git a/module/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeHealthDescriptorTests.java b/module/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeHealthDescriptorTests.java
index 06df5e7fc2d..be5c1291616 100644
--- a/module/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeHealthDescriptorTests.java
+++ b/module/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/CompositeHealthDescriptorTests.java
@@ -20,10 +20,10 @@ import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.Map;
 
-import com.fasterxml.jackson.databind.MapperFeature;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.json.JsonMapper;
 import org.junit.jupiter.api.Test;
+import tools.jackson.databind.MapperFeature;
+import tools.jackson.databind.ObjectMapper;
+import tools.jackson.databind.json.JsonMapper;
 
 import org.springframework.boot.actuate.endpoint.ApiVersion;
 import org.springframework.boot.health.contributor.Health;
diff --git a/module/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/IndicatedHealthDescriptorTests.java b/module/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/IndicatedHealthDescriptorTests.java
index 1d7cd12a72b..8a4b7085b5e 100644
--- a/module/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/IndicatedHealthDescriptorTests.java
+++ b/module/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/IndicatedHealthDescriptorTests.java
@@ -16,10 +16,10 @@
 
 package org.springframework.boot.actuate.health;
 
-import com.fasterxml.jackson.databind.MapperFeature;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.json.JsonMapper;
 import org.junit.jupiter.api.Test;
+import tools.jackson.databind.MapperFeature;
+import tools.jackson.databind.ObjectMapper;
+import tools.jackson.databind.json.JsonMapper;
 
 import org.springframework.boot.health.contributor.Health;
 
diff --git a/module/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/SystemHealthDescriptorTests.java b/module/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/SystemHealthDescriptorTests.java
index 0d1f039c4d3..8e14dbd3430 100644
--- a/module/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/SystemHealthDescriptorTests.java
+++ b/module/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/health/SystemHealthDescriptorTests.java
@@ -22,10 +22,10 @@ import java.util.LinkedHashSet;
 import java.util.Map;
 import java.util.Set;
 
-import com.fasterxml.jackson.databind.MapperFeature;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.json.JsonMapper;
 import org.junit.jupiter.api.Test;
+import tools.jackson.databind.MapperFeature;
+import tools.jackson.databind.ObjectMapper;
+import tools.jackson.databind.json.JsonMapper;
 
 import org.springframework.boot.actuate.endpoint.ApiVersion;
 import org.springframework.boot.health.contributor.Health;
diff --git a/module/spring-boot-couchbase/build.gradle b/module/spring-boot-couchbase/build.gradle
index ffcef5be83b..ddc1b07bf55 100644
--- a/module/spring-boot-couchbase/build.gradle
+++ b/module/spring-boot-couchbase/build.gradle
@@ -33,6 +33,7 @@ dependencies {
 	optional(project(":core:spring-boot-testcontainers"))
 	optional(project(":module:spring-boot-health"))
 	optional(project(":module:spring-boot-jackson"))
+	optional("com.fasterxml.jackson.core:jackson-databind")
 	optional("org.testcontainers:couchbase")
 
 	dockerTestImplementation(project(":core:spring-boot-test"))
diff --git a/module/spring-boot-couchbase/src/test/java/org/springframework/boot/couchbase/autoconfigure/CouchbaseAutoConfigurationTests.java b/module/spring-boot-couchbase/src/test/java/org/springframework/boot/couchbase/autoconfigure/CouchbaseAutoConfigurationTests.java
index d99f2f423c1..173f1f0af75 100644
--- a/module/spring-boot-couchbase/src/test/java/org/springframework/boot/couchbase/autoconfigure/CouchbaseAutoConfigurationTests.java
+++ b/module/spring-boot-couchbase/src/test/java/org/springframework/boot/couchbase/autoconfigure/CouchbaseAutoConfigurationTests.java
@@ -17,7 +17,6 @@
 package org.springframework.boot.couchbase.autoconfigure;
 
 import java.time.Duration;
-import java.util.HashSet;
 import java.util.Set;
 import java.util.function.Consumer;
 
@@ -33,6 +32,7 @@ import com.couchbase.client.java.codec.JsonSerializer;
 import com.couchbase.client.java.env.ClusterEnvironment;
 import com.couchbase.client.java.json.JsonValueModule;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.module.SimpleModule;
 import org.assertj.core.api.InstanceOfAssertFactories;
 import org.junit.jupiter.api.Test;
 
@@ -129,14 +129,14 @@ class CouchbaseAutoConfigurationTests {
 
 	@Test
 	void whenObjectMapperBeanIsDefinedThenClusterEnvironmentObjectMapperIsDerivedFromIt() {
+		ObjectMapper objectMapper = new ObjectMapper();
+		objectMapper.registerModule(new SimpleModule("custom"));
 		this.contextRunner.withUserConfiguration(CouchbaseTestConfiguration.class)
-			.withConfiguration(AutoConfigurations.of(JacksonAutoConfiguration.class))
+			.withBean(ObjectMapper.class, () -> objectMapper)
 			.withPropertyValues("spring.couchbase.connection-string=localhost")
 			.run((context) -> {
 				ClusterEnvironment env = context.getBean(ClusterEnvironment.class);
-				Set expectedModuleIds = new HashSet<>(
-						context.getBean(ObjectMapper.class).getRegisteredModuleIds());
-				expectedModuleIds.add(new JsonValueModule().getTypeId());
+				Set expectedModuleIds = Set.of("custom", new JsonValueModule().getTypeId());
 				JsonSerializer serializer = env.jsonSerializer();
 				assertThat(serializer).extracting("wrapped")
 					.isInstanceOf(JacksonJsonSerializer.class)
diff --git a/module/spring-boot-data-rest/src/main/java/org/springframework/boot/data/rest/autoconfigure/RepositoryRestMvcAutoConfiguration.java b/module/spring-boot-data-rest/src/main/java/org/springframework/boot/data/rest/autoconfigure/RepositoryRestMvcAutoConfiguration.java
index 972baac2cdb..717ad7a6f5d 100644
--- a/module/spring-boot-data-rest/src/main/java/org/springframework/boot/data/rest/autoconfigure/RepositoryRestMvcAutoConfiguration.java
+++ b/module/spring-boot-data-rest/src/main/java/org/springframework/boot/data/rest/autoconfigure/RepositoryRestMvcAutoConfiguration.java
@@ -26,11 +26,11 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplicat
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.boot.data.autoconfigure.web.SpringDataWebAutoConfiguration;
 import org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration;
+import org.springframework.boot.jackson.autoconfigure.JsonMapperBuilderCustomizer;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Import;
 import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
 import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration;
-import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
 
 /**
  * {@link EnableAutoConfiguration Auto-configuration} for Spring Data Rest's MVC
@@ -53,13 +53,14 @@ import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
 @ConditionalOnClass(RepositoryRestMvcConfiguration.class)
 @EnableConfigurationProperties(RepositoryRestProperties.class)
 @Import(RepositoryRestMvcConfiguration.class)
-@SuppressWarnings("removal")
 public final class RepositoryRestMvcAutoConfiguration {
 
 	@Bean
 	SpringBootRepositoryRestConfigurer springBootRepositoryRestConfigurer(
-			ObjectProvider objectMapperBuilder, RepositoryRestProperties properties) {
-		return new SpringBootRepositoryRestConfigurer(objectMapperBuilder.getIfAvailable(), properties);
+			ObjectProvider jsonMapperBuilderCustomizers,
+			RepositoryRestProperties properties) {
+		return new SpringBootRepositoryRestConfigurer(jsonMapperBuilderCustomizers.orderedStream().toList(),
+				properties);
 	}
 
 }
diff --git a/module/spring-boot-data-rest/src/main/java/org/springframework/boot/data/rest/autoconfigure/SpringBootRepositoryRestConfigurer.java b/module/spring-boot-data-rest/src/main/java/org/springframework/boot/data/rest/autoconfigure/SpringBootRepositoryRestConfigurer.java
index 58eadf80ef2..3a1f59d28fc 100644
--- a/module/spring-boot-data-rest/src/main/java/org/springframework/boot/data/rest/autoconfigure/SpringBootRepositoryRestConfigurer.java
+++ b/module/spring-boot-data-rest/src/main/java/org/springframework/boot/data/rest/autoconfigure/SpringBootRepositoryRestConfigurer.java
@@ -16,35 +16,36 @@
 
 package org.springframework.boot.data.rest.autoconfigure;
 
-import com.fasterxml.jackson.databind.ObjectMapper;
-import org.jspecify.annotations.Nullable;
+import java.util.List;
 
+import tools.jackson.databind.cfg.MapperBuilder;
+import tools.jackson.databind.json.JsonMapper;
+
+import org.springframework.boot.jackson.autoconfigure.JsonMapperBuilderCustomizer;
 import org.springframework.core.annotation.Order;
 import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
 import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurer;
-import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
 import org.springframework.web.servlet.config.annotation.CorsRegistry;
 
 /**
  * A {@code RepositoryRestConfigurer} that applies configuration items from the
- * {@code spring.data.rest} namespace to Spring Data REST. Also, if a
- * {@link Jackson2ObjectMapperBuilder} is available, it is used to configure Spring Data
- * REST's {@link ObjectMapper ObjectMappers}.
+ * {@code spring.data.rest} namespace to Spring Data REST. Also, if any
+ * {@link JsonMapperBuilderCustomizer JsonMapperBuilderCustomizers} are available, they
+ * are used to configure Spring Data REST's {@link JsonMapper JsonMappers}.
  *
  * @author Andy Wilkinson
  * @author Stephane Nicoll
  */
 @Order(0)
-@SuppressWarnings("removal")
 class SpringBootRepositoryRestConfigurer implements RepositoryRestConfigurer {
 
-	private final @Nullable Jackson2ObjectMapperBuilder objectMapperBuilder;
+	private final List jsonMapperBuilderCustomizers;
 
 	private final RepositoryRestProperties properties;
 
-	SpringBootRepositoryRestConfigurer(@Nullable Jackson2ObjectMapperBuilder objectMapperBuilder,
+	SpringBootRepositoryRestConfigurer(List jsonMapperBuilderCustomizers,
 			RepositoryRestProperties properties) {
-		this.objectMapperBuilder = objectMapperBuilder;
+		this.jsonMapperBuilderCustomizers = jsonMapperBuilderCustomizers;
 		this.properties = properties;
 	}
 
@@ -54,9 +55,9 @@ class SpringBootRepositoryRestConfigurer implements RepositoryRestConfigurer {
 	}
 
 	@Override
-	public void configureJacksonObjectMapper(ObjectMapper objectMapper) {
-		if (this.objectMapperBuilder != null) {
-			this.objectMapperBuilder.configure(objectMapper);
+	public void configureJacksonObjectMapper(MapperBuilder mapperBuilder) {
+		if (mapperBuilder instanceof JsonMapper.Builder jsonMapperBuilder) {
+			this.jsonMapperBuilderCustomizers.forEach((customizer) -> customizer.customize(jsonMapperBuilder));
 		}
 	}
 
diff --git a/module/spring-boot-data-rest/src/test/java/org/springframework/boot/data/rest/autoconfigure/RepositoryRestMvcAutoConfigurationTests.java b/module/spring-boot-data-rest/src/test/java/org/springframework/boot/data/rest/autoconfigure/RepositoryRestMvcAutoConfigurationTests.java
index 9f8ceadc46d..9488b1225fb 100644
--- a/module/spring-boot-data-rest/src/test/java/org/springframework/boot/data/rest/autoconfigure/RepositoryRestMvcAutoConfigurationTests.java
+++ b/module/spring-boot-data-rest/src/test/java/org/springframework/boot/data/rest/autoconfigure/RepositoryRestMvcAutoConfigurationTests.java
@@ -17,9 +17,11 @@
 package org.springframework.boot.data.rest.autoconfigure;
 
 import java.net.URI;
+import java.text.SimpleDateFormat;
 
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Test;
+import tools.jackson.databind.json.JsonMapper;
 
 import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
 import org.springframework.boot.autoconfigure.TestAutoConfigurationPackage;
@@ -160,14 +162,11 @@ class RepositoryRestMvcAutoConfigurationTests {
 	@Configuration(proxyBeanMethods = false)
 	@TestAutoConfigurationPackage(City.class)
 	@EnableWebMvc
-	@SuppressWarnings({ "removal", "deprecation" })
-	static class TestConfigurationWithObjectMapperBuilder {
+	static class TestConfigurationWithJsonMapperBuilder {
 
 		@Bean
-		org.springframework.http.converter.json.Jackson2ObjectMapperBuilder objectMapperBuilder() {
-			org.springframework.http.converter.json.Jackson2ObjectMapperBuilder objectMapperBuilder = new org.springframework.http.converter.json.Jackson2ObjectMapperBuilder();
-			objectMapperBuilder.simpleDateFormat("yyyy-MM");
-			return objectMapperBuilder;
+		JsonMapper.Builder jsonMapperBuilder() {
+			return JsonMapper.builder().defaultDateFormat(new SimpleDateFormat("yyyy-MM"));
 		}
 
 	}
diff --git a/module/spring-boot-elasticsearch/src/test/java/org/springframework/boot/elasticsearch/autoconfigure/ElasticsearchClientAutoConfigurationTests.java b/module/spring-boot-elasticsearch/src/test/java/org/springframework/boot/elasticsearch/autoconfigure/ElasticsearchClientAutoConfigurationTests.java
index 4c5ddd5ecc7..8505a7fce82 100644
--- a/module/spring-boot-elasticsearch/src/test/java/org/springframework/boot/elasticsearch/autoconfigure/ElasticsearchClientAutoConfigurationTests.java
+++ b/module/spring-boot-elasticsearch/src/test/java/org/springframework/boot/elasticsearch/autoconfigure/ElasticsearchClientAutoConfigurationTests.java
@@ -123,6 +123,7 @@ class ElasticsearchClientAutoConfigurationTests {
 	void jacksonJsonpMapperDoesNotUseGlobalObjectMapper() {
 		this.contextRunner.withConfiguration(AutoConfigurations.of(JacksonAutoConfiguration.class))
 			.withUserConfiguration(RestClientConfiguration.class)
+			.withBean(ObjectMapper.class)
 			.run((context) -> {
 				ObjectMapper objectMapper = context.getBean(ObjectMapper.class);
 				JacksonJsonpMapper jacksonJsonpMapper = context.getBean(JacksonJsonpMapper.class);
diff --git a/module/spring-boot-graphql-test/build.gradle b/module/spring-boot-graphql-test/build.gradle
index 1f955c2fa35..d28ddb24a39 100644
--- a/module/spring-boot-graphql-test/build.gradle
+++ b/module/spring-boot-graphql-test/build.gradle
@@ -38,7 +38,7 @@ dependencies {
 	testImplementation("org.springframework:spring-webmvc")
 
 	testRuntimeOnly("ch.qos.logback:logback-classic")
-	testRuntimeOnly("com.fasterxml.jackson.core:jackson-databind")
+	testRuntimeOnly("tools.jackson.core:jackson-databind")
 }
 
 tasks.named("javadoc") {
diff --git a/module/spring-boot-graphql/src/main/java/org/springframework/boot/graphql/autoconfigure/rsocket/GraphQlRSocketAutoConfiguration.java b/module/spring-boot-graphql/src/main/java/org/springframework/boot/graphql/autoconfigure/rsocket/GraphQlRSocketAutoConfiguration.java
index d4f3a1c8df9..2e10fee9938 100644
--- a/module/spring-boot-graphql/src/main/java/org/springframework/boot/graphql/autoconfigure/rsocket/GraphQlRSocketAutoConfiguration.java
+++ b/module/spring-boot-graphql/src/main/java/org/springframework/boot/graphql/autoconfigure/rsocket/GraphQlRSocketAutoConfiguration.java
@@ -16,10 +16,10 @@
 
 package org.springframework.boot.graphql.autoconfigure.rsocket;
 
-import com.fasterxml.jackson.databind.ObjectMapper;
 import graphql.GraphQL;
 import io.rsocket.core.RSocketServer;
 import reactor.netty.http.server.HttpServer;
+import tools.jackson.databind.json.JsonMapper;
 
 import org.springframework.beans.factory.ObjectProvider;
 import org.springframework.boot.autoconfigure.AutoConfiguration;
@@ -35,6 +35,7 @@ import org.springframework.graphql.data.method.annotation.support.AnnotatedContr
 import org.springframework.graphql.execution.GraphQlSource;
 import org.springframework.graphql.server.GraphQlRSocketHandler;
 import org.springframework.graphql.server.RSocketGraphQlInterceptor;
+import org.springframework.http.codec.json.JacksonJsonEncoder;
 import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler;
 
 /**
@@ -53,11 +54,10 @@ public final class GraphQlRSocketAutoConfiguration {
 
 	@Bean
 	@ConditionalOnMissingBean
-	@SuppressWarnings({ "removal", "deprecation" })
 	GraphQlRSocketHandler graphQlRSocketHandler(ExecutionGraphQlService graphQlService,
-			ObjectProvider interceptors, ObjectMapper objectMapper) {
+			ObjectProvider interceptors, JsonMapper jsonMapper) {
 		return new GraphQlRSocketHandler(graphQlService, interceptors.orderedStream().toList(),
-				new org.springframework.http.codec.json.Jackson2JsonEncoder(objectMapper));
+				new JacksonJsonEncoder(jsonMapper));
 	}
 
 	@Bean
diff --git a/module/spring-boot-hateoas/build.gradle b/module/spring-boot-hateoas/build.gradle
index 6a2633f69fe..d506d63aee0 100644
--- a/module/spring-boot-hateoas/build.gradle
+++ b/module/spring-boot-hateoas/build.gradle
@@ -28,7 +28,7 @@ dependencies {
 	api(project(":core:spring-boot"))
 	api("org.springframework.hateoas:spring-hateoas")
 
-	implementation("com.fasterxml.jackson.core:jackson-databind")
+	implementation(project(":module:spring-boot-jackson"))
 	implementation("org.springframework:spring-webmvc")
 
 	optional(project(":core:spring-boot-autoconfigure"))
diff --git a/module/spring-boot-hateoas/src/main/java/org/springframework/boot/hateoas/autoconfigure/HypermediaAutoConfiguration.java b/module/spring-boot-hateoas/src/main/java/org/springframework/boot/hateoas/autoconfigure/HypermediaAutoConfiguration.java
index b1d6bbdf66b..3247b61bd62 100644
--- a/module/spring-boot-hateoas/src/main/java/org/springframework/boot/hateoas/autoconfigure/HypermediaAutoConfiguration.java
+++ b/module/spring-boot-hateoas/src/main/java/org/springframework/boot/hateoas/autoconfigure/HypermediaAutoConfiguration.java
@@ -16,7 +16,7 @@
 
 package org.springframework.boot.hateoas.autoconfigure;
 
-import com.fasterxml.jackson.databind.ObjectMapper;
+import tools.jackson.databind.json.JsonMapper;
 
 import org.springframework.boot.autoconfigure.AutoConfiguration;
 import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
@@ -48,7 +48,7 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandl
  */
 @AutoConfiguration
 @ConditionalOnClass({ EntityModel.class, RequestMapping.class, RequestMappingHandlerAdapter.class, Plugin.class,
-		ObjectMapper.class })
+		JsonMapper.class })
 @ConditionalOnWebApplication
 @EnableConfigurationProperties(HateoasProperties.class)
 public final class HypermediaAutoConfiguration {
diff --git a/module/spring-boot-hateoas/src/test/java/org/springframework/boot/hateoas/autoconfigure/HypermediaAutoConfigurationTests.java b/module/spring-boot-hateoas/src/test/java/org/springframework/boot/hateoas/autoconfigure/HypermediaAutoConfigurationTests.java
index ca69fa93fc8..8130f35c025 100644
--- a/module/spring-boot-hateoas/src/test/java/org/springframework/boot/hateoas/autoconfigure/HypermediaAutoConfigurationTests.java
+++ b/module/spring-boot-hateoas/src/test/java/org/springframework/boot/hateoas/autoconfigure/HypermediaAutoConfigurationTests.java
@@ -35,6 +35,7 @@ import org.springframework.hateoas.mediatype.hal.HalLinkDiscoverer;
 import org.springframework.hateoas.server.EntityLinks;
 import org.springframework.http.MediaType;
 import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter;
 import org.springframework.web.servlet.config.annotation.EnableWebMvc;
 import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
 
@@ -85,13 +86,12 @@ class HypermediaAutoConfigurationTests {
 	}
 
 	@Test
-	@SuppressWarnings("removal")
 	void whenUsingTheDefaultConfigurationThenMappingJacksonConverterCanWriteHateoasTypeAsApplicationJson() {
 		this.contextRunner.run((context) -> {
 			RequestMappingHandlerAdapter handlerAdapter = context.getBean(RequestMappingHandlerAdapter.class);
 			Optional> mappingJacksonConverter = handlerAdapter.getMessageConverters()
 				.stream()
-				.filter(org.springframework.http.converter.json.MappingJackson2HttpMessageConverter.class::isInstance)
+				.filter(JacksonJsonHttpMessageConverter.class::isInstance)
 				.findFirst();
 			assertThat(mappingJacksonConverter).hasValueSatisfying(
 					(converter) -> assertThat(converter.canWrite(RepresentationModel.class, MediaType.APPLICATION_JSON))
@@ -100,14 +100,13 @@ class HypermediaAutoConfigurationTests {
 	}
 
 	@Test
-	@SuppressWarnings("removal")
 	void whenHalIsNotTheDefaultJsonMediaTypeThenMappingJacksonConverterCannotWriteHateoasTypeAsApplicationJson() {
 		this.contextRunner.withPropertyValues("spring.hateoas.use-hal-as-default-json-media-type:false")
 			.run((context) -> {
 				RequestMappingHandlerAdapter handlerAdapter = context.getBean(RequestMappingHandlerAdapter.class);
 				Optional> mappingJacksonConverter = handlerAdapter.getMessageConverters()
 					.stream()
-					.filter(org.springframework.http.converter.json.MappingJackson2HttpMessageConverter.class::isInstance)
+					.filter(JacksonJsonHttpMessageConverter.class::isInstance)
 					.findFirst();
 				assertThat(mappingJacksonConverter).hasValueSatisfying((converter) -> assertThat(
 						converter.canWrite(RepresentationModel.class, MediaType.APPLICATION_JSON))
diff --git a/module/spring-boot-health/build.gradle b/module/spring-boot-health/build.gradle
index 2bb3b2bb2da..6b3b9ab0344 100644
--- a/module/spring-boot-health/build.gradle
+++ b/module/spring-boot-health/build.gradle
@@ -34,8 +34,8 @@ dependencies {
 
 	testImplementation(project(":core:spring-boot-test"))
 	testImplementation(project(":test-support:spring-boot-test-support"))
-	testImplementation("com.fasterxml.jackson.core:jackson-databind")
 	testImplementation("io.projectreactor:reactor-test")
+	testImplementation("tools.jackson.core:jackson-databind")
 
 	testRuntimeOnly("ch.qos.logback:logback-classic")
 }
diff --git a/module/spring-boot-health/src/test/java/org/springframework/boot/health/contributor/HealthTests.java b/module/spring-boot-health/src/test/java/org/springframework/boot/health/contributor/HealthTests.java
index dfe1f3ffa1e..ca3f9ef8514 100644
--- a/module/spring-boot-health/src/test/java/org/springframework/boot/health/contributor/HealthTests.java
+++ b/module/spring-boot-health/src/test/java/org/springframework/boot/health/contributor/HealthTests.java
@@ -20,8 +20,8 @@ import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.Map;
 
-import com.fasterxml.jackson.databind.ObjectMapper;
 import org.junit.jupiter.api.Test;
+import tools.jackson.databind.ObjectMapper;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
@@ -183,7 +183,7 @@ class HealthTests {
 		Health health = Health.down().withDetail("a", "b").build();
 		ObjectMapper mapper = new ObjectMapper();
 		String json = mapper.writeValueAsString(health);
-		assertThat(json).isEqualTo("{\"status\":\"DOWN\",\"details\":{\"a\":\"b\"}}");
+		assertThat(json).isEqualTo("{\"details\":{\"a\":\"b\"},\"status\":\"DOWN\"}");
 	}
 
 }
diff --git a/module/spring-boot-health/src/test/java/org/springframework/boot/health/contributor/StatusTests.java b/module/spring-boot-health/src/test/java/org/springframework/boot/health/contributor/StatusTests.java
index 92ba26c4a22..e5edc06e2c4 100644
--- a/module/spring-boot-health/src/test/java/org/springframework/boot/health/contributor/StatusTests.java
+++ b/module/spring-boot-health/src/test/java/org/springframework/boot/health/contributor/StatusTests.java
@@ -16,8 +16,8 @@
 
 package org.springframework.boot.health.contributor;
 
-import com.fasterxml.jackson.databind.ObjectMapper;
 import org.junit.jupiter.api.Test;
+import tools.jackson.databind.ObjectMapper;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
diff --git a/module/spring-boot-http-codec/src/main/java/org/springframework/boot/http/codec/autoconfigure/CodecsAutoConfiguration.java b/module/spring-boot-http-codec/src/main/java/org/springframework/boot/http/codec/autoconfigure/CodecsAutoConfiguration.java
index a8564ec838a..2dca1d52617 100644
--- a/module/spring-boot-http-codec/src/main/java/org/springframework/boot/http/codec/autoconfigure/CodecsAutoConfiguration.java
+++ b/module/spring-boot-http-codec/src/main/java/org/springframework/boot/http/codec/autoconfigure/CodecsAutoConfiguration.java
@@ -16,8 +16,9 @@
 
 package org.springframework.boot.http.codec.autoconfigure;
 
-import com.fasterxml.jackson.databind.ObjectMapper;
 import org.jspecify.annotations.Nullable;
+import tools.jackson.databind.ObjectMapper;
+import tools.jackson.databind.json.JsonMapper;
 
 import org.springframework.boot.autoconfigure.AutoConfiguration;
 import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
@@ -31,7 +32,8 @@ import org.springframework.context.annotation.Configuration;
 import org.springframework.core.Ordered;
 import org.springframework.core.annotation.Order;
 import org.springframework.http.codec.CodecConfigurer;
-import org.springframework.util.MimeType;
+import org.springframework.http.codec.json.JacksonJsonDecoder;
+import org.springframework.http.codec.json.JacksonJsonEncoder;
 import org.springframework.util.unit.DataSize;
 import org.springframework.web.reactive.function.client.WebClient;
 
@@ -47,23 +49,18 @@ import org.springframework.web.reactive.function.client.WebClient;
 @ConditionalOnClass({ CodecConfigurer.class, WebClient.class })
 public final class CodecsAutoConfiguration {
 
-	private static final MimeType[] EMPTY_MIME_TYPES = {};
-
 	@Configuration(proxyBeanMethods = false)
 	@ConditionalOnClass(ObjectMapper.class)
-	@SuppressWarnings({ "removal", "deprecation" })
-	static class JacksonCodecConfiguration {
+	static class JacksonJsonCodecConfiguration {
 
 		@Bean
 		@Order(0)
-		@ConditionalOnBean(ObjectMapper.class)
-		CodecCustomizer jacksonCodecCustomizer(ObjectMapper objectMapper) {
+		@ConditionalOnBean(JsonMapper.class)
+		CodecCustomizer jacksonCodecCustomizer(JsonMapper jsonMapper) {
 			return (configurer) -> {
 				CodecConfigurer.DefaultCodecs defaults = configurer.defaultCodecs();
-				defaults.jackson2JsonDecoder(
-						new org.springframework.http.codec.json.Jackson2JsonDecoder(objectMapper, EMPTY_MIME_TYPES));
-				defaults.jackson2JsonEncoder(
-						new org.springframework.http.codec.json.Jackson2JsonEncoder(objectMapper, EMPTY_MIME_TYPES));
+				defaults.jacksonJsonDecoder(new JacksonJsonDecoder(jsonMapper));
+				defaults.jacksonJsonEncoder(new JacksonJsonEncoder(jsonMapper));
 			};
 		}
 
diff --git a/module/spring-boot-http-codec/src/test/java/org/springframework/boot/http/codec/autoconfigure/CodecsAutoConfigurationTests.java b/module/spring-boot-http-codec/src/test/java/org/springframework/boot/http/codec/autoconfigure/CodecsAutoConfigurationTests.java
index d43c0bc6bd3..39e7d050b67 100644
--- a/module/spring-boot-http-codec/src/test/java/org/springframework/boot/http/codec/autoconfigure/CodecsAutoConfigurationTests.java
+++ b/module/spring-boot-http-codec/src/test/java/org/springframework/boot/http/codec/autoconfigure/CodecsAutoConfigurationTests.java
@@ -18,8 +18,8 @@ package org.springframework.boot.http.codec.autoconfigure;
 
 import java.util.List;
 
-import com.fasterxml.jackson.databind.ObjectMapper;
 import org.junit.jupiter.api.Test;
+import tools.jackson.databind.json.JsonMapper;
 
 import org.springframework.boot.autoconfigure.AutoConfigurations;
 import org.springframework.boot.http.codec.CodecCustomizer;
@@ -77,14 +77,14 @@ class CodecsAutoConfigurationTests {
 	}
 
 	@Test
-	void jacksonCodecCustomizerIsAutoConfiguredWhenObjectMapperIsPresent() {
-		this.contextRunner.withUserConfiguration(ObjectMapperConfiguration.class)
+	void jacksonCodecCustomizerIsAutoConfiguredWhenJsonMapperIsPresent() {
+		this.contextRunner.withUserConfiguration(JsonMapperConfiguration.class)
 			.run((context) -> assertThat(context).hasBean("jacksonCodecCustomizer"));
 	}
 
 	@Test
 	void userProvidedCustomizerCanOverrideJacksonCodecCustomizer() {
-		this.contextRunner.withUserConfiguration(ObjectMapperConfiguration.class, CodecCustomizerConfiguration.class)
+		this.contextRunner.withUserConfiguration(JsonMapperConfiguration.class, CodecCustomizerConfiguration.class)
 			.run((context) -> {
 				List codecCustomizers = context.getBean(CodecCustomizers.class).codecCustomizers;
 				assertThat(codecCustomizers).hasSize(3);
@@ -107,11 +107,11 @@ class CodecsAutoConfigurationTests {
 	}
 
 	@Configuration(proxyBeanMethods = false)
-	static class ObjectMapperConfiguration {
+	static class JsonMapperConfiguration {
 
 		@Bean
-		ObjectMapper objectMapper() {
-			return new ObjectMapper();
+		JsonMapper jsonMapper() {
+			return new JsonMapper();
 		}
 
 	}
diff --git a/module/spring-boot-http-converter/build.gradle b/module/spring-boot-http-converter/build.gradle
index 31d50cd302f..9b265151eb2 100644
--- a/module/spring-boot-http-converter/build.gradle
+++ b/module/spring-boot-http-converter/build.gradle
@@ -34,18 +34,18 @@ dependencies {
 	optional(project(":module:spring-boot-gson"))
 	optional(project(":module:spring-boot-jackson"))
 	optional(project(":module:spring-boot-jsonb"))
-	optional("com.fasterxml.jackson.dataformat:jackson-dataformat-xml")
 	optional("com.google.code.gson:gson")
 	optional("jakarta.json.bind:jakarta.json.bind-api")
 	optional("org.springframework:spring-webmvc")
+	optional("tools.jackson.dataformat:jackson-dataformat-xml")
 
 	testImplementation(project(":core:spring-boot-test"))
 	testImplementation(project(":test-support:spring-boot-test-support"))
 	testImplementation("org.springframework.data:spring-data-rest-webmvc")
 
 	testRuntimeOnly("ch.qos.logback:logback-classic")
-	testRuntimeOnly("com.fasterxml.jackson.dataformat:jackson-dataformat-cbor")
 	testRuntimeOnly("jakarta.servlet:jakarta.servlet-api")
 	testRuntimeOnly("org.eclipse:yasson")
 	testRuntimeOnly("org.springframework:spring-webflux")
+	testRuntimeOnly("tools.jackson.dataformat:jackson-dataformat-cbor")
 }
diff --git a/module/spring-boot-http-converter/src/main/java/org/springframework/boot/http/converter/autoconfigure/GsonHttpMessageConvertersConfiguration.java b/module/spring-boot-http-converter/src/main/java/org/springframework/boot/http/converter/autoconfigure/GsonHttpMessageConvertersConfiguration.java
index 79e3c646ca5..17528f4c61e 100644
--- a/module/spring-boot-http-converter/src/main/java/org/springframework/boot/http/converter/autoconfigure/GsonHttpMessageConvertersConfiguration.java
+++ b/module/spring-boot-http-converter/src/main/java/org/springframework/boot/http/converter/autoconfigure/GsonHttpMessageConvertersConfiguration.java
@@ -28,6 +28,7 @@ import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Conditional;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.http.converter.json.GsonHttpMessageConverter;
+import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter;
 
 /**
  * Configuration for HTTP Message converters that use Gson.
@@ -79,8 +80,7 @@ class GsonHttpMessageConvertersConfiguration {
 			super(ConfigurationPhase.REGISTER_BEAN);
 		}
 
-		@SuppressWarnings({ "deprecation", "removal" })
-		@ConditionalOnBean(org.springframework.http.converter.json.MappingJackson2HttpMessageConverter.class)
+		@ConditionalOnBean(JacksonJsonHttpMessageConverter.class)
 		static class JacksonAvailable {
 
 		}
diff --git a/module/spring-boot-http-converter/src/main/java/org/springframework/boot/http/converter/autoconfigure/JacksonHttpMessageConvertersConfiguration.java b/module/spring-boot-http-converter/src/main/java/org/springframework/boot/http/converter/autoconfigure/JacksonHttpMessageConvertersConfiguration.java
index a6e53490f4d..d86d1327599 100644
--- a/module/spring-boot-http-converter/src/main/java/org/springframework/boot/http/converter/autoconfigure/JacksonHttpMessageConvertersConfiguration.java
+++ b/module/spring-boot-http-converter/src/main/java/org/springframework/boot/http/converter/autoconfigure/JacksonHttpMessageConvertersConfiguration.java
@@ -16,8 +16,8 @@
 
 package org.springframework.boot.http.converter.autoconfigure;
 
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.dataformat.xml.XmlMapper;
+import tools.jackson.databind.json.JsonMapper;
+import tools.jackson.dataformat.xml.XmlMapper;
 
 import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
@@ -25,9 +25,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
-import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
-import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
-import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;
+import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter;
+import org.springframework.http.converter.xml.JacksonXmlHttpMessageConverter;
 
 /**
  * Configuration for HTTP message converters that use Jackson.
@@ -35,36 +34,34 @@ import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConve
  * @author Andy Wilkinson
  */
 @Configuration(proxyBeanMethods = false)
-@SuppressWarnings("removal")
 class JacksonHttpMessageConvertersConfiguration {
 
 	@Configuration(proxyBeanMethods = false)
-	@ConditionalOnClass(ObjectMapper.class)
-	@ConditionalOnBean(ObjectMapper.class)
+	@ConditionalOnClass(JsonMapper.class)
+	@ConditionalOnBean(JsonMapper.class)
 	@ConditionalOnProperty(name = HttpMessageConvertersAutoConfiguration.PREFERRED_MAPPER_PROPERTY,
 			havingValue = "jackson", matchIfMissing = true)
-	static class MappingJackson2HttpMessageConverterConfiguration {
+	static class JacksonJsonHttpMessageConverterConfiguration {
 
 		@Bean
-		@ConditionalOnMissingBean(ignoredType = {
-				"org.springframework.hateoas.server.mvc.TypeConstrainedMappingJackson2HttpMessageConverter",
-				"org.springframework.data.rest.webmvc.alps.AlpsJsonHttpMessageConverter" })
-		MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(ObjectMapper objectMapper) {
-			return new MappingJackson2HttpMessageConverter(objectMapper);
+		@ConditionalOnMissingBean(
+				ignoredType = { "org.springframework.hateoas.server.mvc.TypeConstrainedJacksonJsonHttpMessageConverter",
+						"org.springframework.data.rest.webmvc.alps.AlpsJackson3JsonHttpMessageConverter" })
+		JacksonJsonHttpMessageConverter jacksonJsonHttpMessageConverter(JsonMapper jsonMapper) {
+			return new JacksonJsonHttpMessageConverter(jsonMapper);
 		}
 
 	}
 
 	@Configuration(proxyBeanMethods = false)
 	@ConditionalOnClass(XmlMapper.class)
-	@ConditionalOnBean(Jackson2ObjectMapperBuilder.class)
-	protected static class MappingJackson2XmlHttpMessageConverterConfiguration {
+	@ConditionalOnBean(XmlMapper.class)
+	protected static class JacksonXmlHttpMessageConverterConfiguration {
 
 		@Bean
 		@ConditionalOnMissingBean
-		public MappingJackson2XmlHttpMessageConverter mappingJackson2XmlHttpMessageConverter(
-				Jackson2ObjectMapperBuilder builder) {
-			return new MappingJackson2XmlHttpMessageConverter(builder.createXmlMapper(true).build());
+		public JacksonXmlHttpMessageConverter jacksonXmlHttpMessageConverter(XmlMapper xmlMapper) {
+			return new JacksonXmlHttpMessageConverter(xmlMapper);
 		}
 
 	}
diff --git a/module/spring-boot-http-converter/src/main/java/org/springframework/boot/http/converter/autoconfigure/JsonbHttpMessageConvertersConfiguration.java b/module/spring-boot-http-converter/src/main/java/org/springframework/boot/http/converter/autoconfigure/JsonbHttpMessageConvertersConfiguration.java
index 440cfa555a8..7669010ae60 100644
--- a/module/spring-boot-http-converter/src/main/java/org/springframework/boot/http/converter/autoconfigure/JsonbHttpMessageConvertersConfiguration.java
+++ b/module/spring-boot-http-converter/src/main/java/org/springframework/boot/http/converter/autoconfigure/JsonbHttpMessageConvertersConfiguration.java
@@ -27,6 +27,7 @@ import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Conditional;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.http.converter.json.GsonHttpMessageConverter;
+import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter;
 import org.springframework.http.converter.json.JsonbHttpMessageConverter;
 
 /**
@@ -65,9 +66,7 @@ class JsonbHttpMessageConvertersConfiguration {
 
 		}
 
-		@SuppressWarnings("removal")
-		@ConditionalOnMissingBean({ org.springframework.http.converter.json.MappingJackson2HttpMessageConverter.class,
-				GsonHttpMessageConverter.class })
+		@ConditionalOnMissingBean({ JacksonJsonHttpMessageConverter.class, GsonHttpMessageConverter.class })
 		static class JacksonAndGsonMissing {
 
 		}
diff --git a/module/spring-boot-http-converter/src/test/java/org/springframework/boot/http/converter/autoconfigure/HttpMessageConvertersAutoConfigurationTests.java b/module/spring-boot-http-converter/src/test/java/org/springframework/boot/http/converter/autoconfigure/HttpMessageConvertersAutoConfigurationTests.java
index 93c7974fd8e..7386f22aa24 100644
--- a/module/spring-boot-http-converter/src/test/java/org/springframework/boot/http/converter/autoconfigure/HttpMessageConvertersAutoConfigurationTests.java
+++ b/module/spring-boot-http-converter/src/test/java/org/springframework/boot/http/converter/autoconfigure/HttpMessageConvertersAutoConfigurationTests.java
@@ -18,15 +18,17 @@ package org.springframework.boot.http.converter.autoconfigure;
 
 import java.nio.charset.StandardCharsets;
 
-import com.fasterxml.jackson.databind.ObjectMapper;
 import com.google.gson.Gson;
 import jakarta.json.bind.Jsonb;
 import jakarta.json.bind.JsonbBuilder;
 import org.junit.jupiter.api.Test;
+import tools.jackson.databind.ObjectMapper;
+import tools.jackson.databind.json.JsonMapper;
+import tools.jackson.dataformat.xml.XmlMapper;
 
 import org.springframework.beans.factory.config.BeanDefinition;
 import org.springframework.boot.autoconfigure.AutoConfigurations;
-import org.springframework.boot.http.converter.autoconfigure.JacksonHttpMessageConvertersConfiguration.MappingJackson2HttpMessageConverterConfiguration;
+import org.springframework.boot.http.converter.autoconfigure.JacksonHttpMessageConvertersConfiguration.JacksonJsonHttpMessageConverterConfiguration;
 import org.springframework.boot.test.context.FilteredClassLoader;
 import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
 import org.springframework.boot.test.context.runner.ApplicationContextRunner;
@@ -38,14 +40,13 @@ import org.springframework.context.annotation.Configuration;
 import org.springframework.context.support.GenericApplicationContext;
 import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration;
 import org.springframework.hateoas.RepresentationModel;
-import org.springframework.hateoas.server.mvc.TypeConstrainedMappingJackson2HttpMessageConverter;
+import org.springframework.hateoas.server.mvc.TypeConstrainedJacksonJsonHttpMessageConverter;
 import org.springframework.http.converter.HttpMessageConverter;
 import org.springframework.http.converter.StringHttpMessageConverter;
 import org.springframework.http.converter.json.GsonHttpMessageConverter;
-import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
+import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter;
 import org.springframework.http.converter.json.JsonbHttpMessageConverter;
-import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
-import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;
+import org.springframework.http.converter.xml.JacksonXmlHttpMessageConverter;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
@@ -61,7 +62,6 @@ import static org.assertj.core.api.Assertions.assertThat;
  * @author Moritz Halbritter
  * @author Sebastien Deleuze
  */
-@SuppressWarnings("removal")
 class HttpMessageConvertersAutoConfigurationTests {
 
 	private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
@@ -71,34 +71,33 @@ class HttpMessageConvertersAutoConfigurationTests {
 	void jacksonNotAvailable() {
 		this.contextRunner.run((context) -> {
 			assertThat(context).doesNotHaveBean(ObjectMapper.class);
-			assertThat(context).doesNotHaveBean(MappingJackson2HttpMessageConverter.class);
-			assertThat(context).doesNotHaveBean(MappingJackson2XmlHttpMessageConverter.class);
+			assertThat(context).doesNotHaveBean(JacksonJsonHttpMessageConverter.class);
+			assertThat(context).doesNotHaveBean(JacksonXmlHttpMessageConverter.class);
 		});
 	}
 
 	@Test
 	void jacksonDefaultConverter() {
-		this.contextRunner.withUserConfiguration(JacksonObjectMapperConfig.class)
-			.run(assertConverter(MappingJackson2HttpMessageConverter.class, "mappingJackson2HttpMessageConverter"));
+		this.contextRunner.withUserConfiguration(JacksonJsonMapperConfig.class)
+			.run(assertConverter(JacksonJsonHttpMessageConverter.class, "jacksonJsonHttpMessageConverter"));
 	}
 
 	@Test
 	void jacksonConverterWithBuilder() {
-		this.contextRunner.withUserConfiguration(JacksonObjectMapperBuilderConfig.class)
-			.run(assertConverter(MappingJackson2HttpMessageConverter.class, "mappingJackson2HttpMessageConverter"));
+		this.contextRunner.withUserConfiguration(JacksonJsonMapperBuilderConfig.class)
+			.run(assertConverter(JacksonJsonHttpMessageConverter.class, "jacksonJsonHttpMessageConverter"));
 	}
 
 	@Test
 	void jacksonXmlConverterWithBuilder() {
-		this.contextRunner.withUserConfiguration(JacksonObjectMapperBuilderConfig.class)
-			.run(assertConverter(MappingJackson2XmlHttpMessageConverter.class,
-					"mappingJackson2XmlHttpMessageConverter"));
+		this.contextRunner.withUserConfiguration(JacksonXmlMapperBuilderConfig.class)
+			.run(assertConverter(JacksonXmlHttpMessageConverter.class, "jacksonXmlHttpMessageConverter"));
 	}
 
 	@Test
 	void jacksonCustomConverter() {
-		this.contextRunner.withUserConfiguration(JacksonObjectMapperConfig.class, JacksonConverterConfig.class)
-			.run(assertConverter(MappingJackson2HttpMessageConverter.class, "customJacksonMessageConverter"));
+		this.contextRunner.withUserConfiguration(JacksonJsonMapperConfig.class, JacksonConverterConfig.class)
+			.run(assertConverter(JacksonJsonHttpMessageConverter.class, "customJacksonMessageConverter"));
 	}
 
 	@Test
@@ -128,7 +127,7 @@ class HttpMessageConvertersAutoConfigurationTests {
 			assertConverterBeanExists(context, GsonHttpMessageConverter.class, "gsonHttpMessageConverter");
 			assertConverterBeanRegisteredWithHttpMessageConverters(context, GsonHttpMessageConverter.class);
 			assertThat(context).doesNotHaveBean(JsonbHttpMessageConverter.class);
-			assertThat(context).doesNotHaveBean(MappingJackson2HttpMessageConverter.class);
+			assertThat(context).doesNotHaveBean(JacksonJsonHttpMessageConverter.class);
 		});
 	}
 
@@ -159,7 +158,7 @@ class HttpMessageConvertersAutoConfigurationTests {
 			assertConverterBeanExists(context, JsonbHttpMessageConverter.class, "jsonbHttpMessageConverter");
 			assertConverterBeanRegisteredWithHttpMessageConverters(context, JsonbHttpMessageConverter.class);
 			assertThat(context).doesNotHaveBean(GsonHttpMessageConverter.class);
-			assertThat(context).doesNotHaveBean(MappingJackson2HttpMessageConverter.class);
+			assertThat(context).doesNotHaveBean(JacksonJsonHttpMessageConverter.class);
 		});
 	}
 
@@ -177,33 +176,33 @@ class HttpMessageConvertersAutoConfigurationTests {
 	@Test
 	void typeConstrainedConverterDoesNotPreventAutoConfigurationOfJacksonConverter() {
 		this.contextRunner
-			.withUserConfiguration(JacksonObjectMapperBuilderConfig.class, TypeConstrainedConverterConfiguration.class)
+			.withUserConfiguration(JacksonJsonMapperBuilderConfig.class, TypeConstrainedConverterConfiguration.class)
 			.run((context) -> {
 				BeanDefinition beanDefinition = ((GenericApplicationContext) context.getSourceApplicationContext())
-					.getBeanDefinition("mappingJackson2HttpMessageConverter");
+					.getBeanDefinition("jacksonJsonHttpMessageConverter");
 				assertThat(beanDefinition.getFactoryBeanName())
-					.isEqualTo(MappingJackson2HttpMessageConverterConfiguration.class.getName());
+					.isEqualTo(JacksonJsonHttpMessageConverterConfiguration.class.getName());
 			});
 	}
 
 	@Test
 	void typeConstrainedConverterFromSpringDataDoesNotPreventAutoConfigurationOfJacksonConverter() {
 		this.contextRunner
-			.withUserConfiguration(JacksonObjectMapperBuilderConfig.class, RepositoryRestMvcConfiguration.class)
+			.withUserConfiguration(JacksonJsonMapperBuilderConfig.class, RepositoryRestMvcConfiguration.class)
 			.run((context) -> {
 				BeanDefinition beanDefinition = ((GenericApplicationContext) context.getSourceApplicationContext())
-					.getBeanDefinition("mappingJackson2HttpMessageConverter");
+					.getBeanDefinition("jacksonJsonHttpMessageConverter");
 				assertThat(beanDefinition.getFactoryBeanName())
-					.isEqualTo(MappingJackson2HttpMessageConverterConfiguration.class.getName());
+					.isEqualTo(JacksonJsonHttpMessageConverterConfiguration.class.getName());
 			});
 	}
 
 	@Test
 	void jacksonIsPreferredByDefault() {
 		allOptionsRunner().run((context) -> {
-			assertConverterBeanExists(context, MappingJackson2HttpMessageConverter.class,
-					"mappingJackson2HttpMessageConverter");
-			assertConverterBeanRegisteredWithHttpMessageConverters(context, MappingJackson2HttpMessageConverter.class);
+			assertConverterBeanExists(context, JacksonJsonHttpMessageConverter.class,
+					"jacksonJsonHttpMessageConverter");
+			assertConverterBeanRegisteredWithHttpMessageConverters(context, JacksonJsonHttpMessageConverter.class);
 			assertThat(context).doesNotHaveBean(GsonHttpMessageConverter.class);
 			assertThat(context).doesNotHaveBean(JsonbHttpMessageConverter.class);
 		});
@@ -266,7 +265,7 @@ class HttpMessageConvertersAutoConfigurationTests {
 
 	private ApplicationContextRunner allOptionsRunner() {
 		return this.contextRunner.withBean(Gson.class)
-			.withBean(ObjectMapper.class)
+			.withBean(JsonMapper.class)
 			.withBean(Jsonb.class, JsonbBuilder::create);
 	}
 
@@ -291,26 +290,41 @@ class HttpMessageConvertersAutoConfigurationTests {
 	}
 
 	@Configuration(proxyBeanMethods = false)
-	static class JacksonObjectMapperConfig {
+	static class JacksonJsonMapperConfig {
 
 		@Bean
-		ObjectMapper objectMapper() {
-			return new ObjectMapper();
+		JsonMapper jsonMapper() {
+			return new JsonMapper();
 		}
 
 	}
 
 	@Configuration(proxyBeanMethods = false)
-	static class JacksonObjectMapperBuilderConfig {
+	static class JacksonJsonMapperBuilderConfig {
 
 		@Bean
-		ObjectMapper objectMapper() {
-			return new ObjectMapper();
+		JsonMapper jsonMapper() {
+			return new JsonMapper();
 		}
 
 		@Bean
-		Jackson2ObjectMapperBuilder builder() {
-			return new Jackson2ObjectMapperBuilder();
+		JsonMapper.Builder builder() {
+			return JsonMapper.builder();
+		}
+
+	}
+
+	@Configuration(proxyBeanMethods = false)
+	static class JacksonXmlMapperBuilderConfig {
+
+		@Bean
+		XmlMapper xmlMapper() {
+			return new XmlMapper();
+		}
+
+		@Bean
+		XmlMapper.Builder builder() {
+			return XmlMapper.builder();
 		}
 
 	}
@@ -319,9 +333,8 @@ class HttpMessageConvertersAutoConfigurationTests {
 	static class JacksonConverterConfig {
 
 		@Bean
-		MappingJackson2HttpMessageConverter customJacksonMessageConverter(ObjectMapper objectMapper) {
-			MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
-			converter.setObjectMapper(objectMapper);
+		JacksonJsonHttpMessageConverter customJacksonMessageConverter(JsonMapper jsonMapperMapper) {
+			JacksonJsonHttpMessageConverter converter = new JacksonJsonHttpMessageConverter(jsonMapperMapper);
 			return converter;
 		}
 
@@ -365,8 +378,8 @@ class HttpMessageConvertersAutoConfigurationTests {
 	static class TypeConstrainedConverterConfiguration {
 
 		@Bean
-		TypeConstrainedMappingJackson2HttpMessageConverter typeConstrainedConverter() {
-			return new TypeConstrainedMappingJackson2HttpMessageConverter(RepresentationModel.class);
+		TypeConstrainedJacksonJsonHttpMessageConverter typeConstrainedConverter() {
+			return new TypeConstrainedJacksonJsonHttpMessageConverter(RepresentationModel.class);
 		}
 
 	}
diff --git a/module/spring-boot-http-converter/src/test/java/org/springframework/boot/http/converter/autoconfigure/HttpMessageConvertersTests.java b/module/spring-boot-http-converter/src/test/java/org/springframework/boot/http/converter/autoconfigure/HttpMessageConvertersTests.java
index baf81f76a54..3b2f262ee6b 100644
--- a/module/spring-boot-http-converter/src/test/java/org/springframework/boot/http/converter/autoconfigure/HttpMessageConvertersTests.java
+++ b/module/spring-boot-http-converter/src/test/java/org/springframework/boot/http/converter/autoconfigure/HttpMessageConvertersTests.java
@@ -28,11 +28,11 @@ import org.springframework.http.converter.HttpMessageConverter;
 import org.springframework.http.converter.ResourceHttpMessageConverter;
 import org.springframework.http.converter.ResourceRegionHttpMessageConverter;
 import org.springframework.http.converter.StringHttpMessageConverter;
-import org.springframework.http.converter.cbor.MappingJackson2CborHttpMessageConverter;
+import org.springframework.http.converter.cbor.JacksonCborHttpMessageConverter;
 import org.springframework.http.converter.json.GsonHttpMessageConverter;
-import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
+import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter;
 import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter;
-import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;
+import org.springframework.http.converter.xml.JacksonXmlHttpMessageConverter;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
@@ -43,7 +43,6 @@ import static org.mockito.Mockito.mock;
  * @author Dave Syer
  * @author Phillip Webb
  */
-@SuppressWarnings("removal")
 class HttpMessageConvertersTests {
 
 	@Test
@@ -56,21 +55,21 @@ class HttpMessageConvertersTests {
 		assertThat(converterClasses).containsExactly(ByteArrayHttpMessageConverter.class,
 				StringHttpMessageConverter.class, ResourceHttpMessageConverter.class,
 				ResourceRegionHttpMessageConverter.class, AllEncompassingFormHttpMessageConverter.class,
-				MappingJackson2HttpMessageConverter.class, MappingJackson2CborHttpMessageConverter.class,
-				MappingJackson2XmlHttpMessageConverter.class);
+				JacksonJsonHttpMessageConverter.class, JacksonCborHttpMessageConverter.class,
+				JacksonXmlHttpMessageConverter.class);
 	}
 
 	@Test
 	void addBeforeExistingConverter() {
-		MappingJackson2HttpMessageConverter converter1 = new MappingJackson2HttpMessageConverter();
-		MappingJackson2HttpMessageConverter converter2 = new MappingJackson2HttpMessageConverter();
+		JacksonJsonHttpMessageConverter converter1 = new JacksonJsonHttpMessageConverter();
+		JacksonJsonHttpMessageConverter converter2 = new JacksonJsonHttpMessageConverter();
 		HttpMessageConverters converters = new HttpMessageConverters(converter1, converter2);
 		assertThat(converters.getConverters()).contains(converter1);
 		assertThat(converters.getConverters()).contains(converter2);
-		List httpConverters = new ArrayList<>();
+		List httpConverters = new ArrayList<>();
 		for (HttpMessageConverter candidate : converters) {
-			if (candidate instanceof MappingJackson2HttpMessageConverter) {
-				httpConverters.add((MappingJackson2HttpMessageConverter) candidate);
+			if (candidate instanceof JacksonJsonHttpMessageConverter jsonConverter) {
+				httpConverters.add(jsonConverter);
 			}
 		}
 		// The existing converter is still there, but with a lower priority
@@ -86,7 +85,7 @@ class HttpMessageConvertersTests {
 		HttpMessageConverters converters = new HttpMessageConverters(converter1);
 		Stream> converterClasses = converters.getConverters().stream().map(HttpMessageConverter::getClass);
 		assertThat(converterClasses).containsSequence(GsonHttpMessageConverter.class,
-				MappingJackson2HttpMessageConverter.class);
+				JacksonJsonHttpMessageConverter.class);
 	}
 
 	@Test
@@ -114,7 +113,7 @@ class HttpMessageConvertersTests {
 
 			@Override
 			protected List> postProcessConverters(List> converters) {
-				converters.removeIf(MappingJackson2XmlHttpMessageConverter.class::isInstance);
+				converters.removeIf(JacksonXmlHttpMessageConverter.class::isInstance);
 				return converters;
 			}
 
@@ -126,7 +125,7 @@ class HttpMessageConvertersTests {
 		assertThat(converterClasses).containsExactly(ByteArrayHttpMessageConverter.class,
 				StringHttpMessageConverter.class, ResourceHttpMessageConverter.class,
 				ResourceRegionHttpMessageConverter.class, AllEncompassingFormHttpMessageConverter.class,
-				MappingJackson2HttpMessageConverter.class, MappingJackson2CborHttpMessageConverter.class);
+				JacksonJsonHttpMessageConverter.class, JacksonCborHttpMessageConverter.class);
 	}
 
 	@Test
@@ -136,7 +135,7 @@ class HttpMessageConvertersTests {
 			@Override
 			protected List> postProcessPartConverters(
 					List> converters) {
-				converters.removeIf(MappingJackson2XmlHttpMessageConverter.class::isInstance);
+				converters.removeIf(JacksonXmlHttpMessageConverter.class::isInstance);
 				return converters;
 			}
 
@@ -147,7 +146,7 @@ class HttpMessageConvertersTests {
 		}
 		assertThat(converterClasses).containsExactly(ByteArrayHttpMessageConverter.class,
 				StringHttpMessageConverter.class, ResourceHttpMessageConverter.class,
-				MappingJackson2HttpMessageConverter.class, MappingJackson2CborHttpMessageConverter.class);
+				JacksonJsonHttpMessageConverter.class, JacksonCborHttpMessageConverter.class);
 	}
 
 	private List> extractFormPartConverters(List> converters) {
diff --git a/module/spring-boot-jackson/build.gradle b/module/spring-boot-jackson/build.gradle
index 72425716514..af41f364f43 100644
--- a/module/spring-boot-jackson/build.gradle
+++ b/module/spring-boot-jackson/build.gradle
@@ -26,16 +26,14 @@ description = "Spring Boot Jackson"
 
 dependencies {
 	api(project(":core:spring-boot"))
-	api("com.fasterxml.jackson.core:jackson-databind")
+	api("tools.jackson.core:jackson-databind")
 
 	implementation("org.springframework:spring-web")
 
 	optional(project(":core:spring-boot-autoconfigure"))
-	optional("com.fasterxml.jackson.dataformat:jackson-dataformat-cbor")
-	optional("com.fasterxml.jackson.dataformat:jackson-dataformat-xml")
-	optional("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")
-	optional("com.fasterxml.jackson.module:jackson-module-jakarta-xmlbind-annotations")
-	optional("com.fasterxml.jackson.module:jackson-module-parameter-names")
+	optional("tools.jackson.dataformat:jackson-dataformat-cbor")
+	optional("tools.jackson.dataformat:jackson-dataformat-xml")
+	optional("tools.jackson.module:jackson-module-jakarta-xmlbind-annotations")
 
 	testImplementation(project(":core:spring-boot-test"))
 	testImplementation(project(":test-support:spring-boot-test-support"))
diff --git a/module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/JsonComponent.java b/module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/JsonComponent.java
index 44fe45c0671..b7767c690ac 100644
--- a/module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/JsonComponent.java
+++ b/module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/JsonComponent.java
@@ -22,29 +22,29 @@ import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
-import com.fasterxml.jackson.databind.JsonDeserializer;
-import com.fasterxml.jackson.databind.JsonSerializer;
-import com.fasterxml.jackson.databind.KeyDeserializer;
+import tools.jackson.databind.KeyDeserializer;
+import tools.jackson.databind.ValueDeserializer;
+import tools.jackson.databind.ValueSerializer;
 
 import org.springframework.core.annotation.AliasFor;
 import org.springframework.stereotype.Component;
 
 /**
- * {@link Component @Component} that provides {@link JsonSerializer},
- * {@link JsonDeserializer} or {@link KeyDeserializer} implementations to be registered
+ * {@link Component @Component} that provides {@link ValueSerializer},
+ * {@link ValueDeserializer} or {@link KeyDeserializer} implementations to be registered
  * with Jackson when {@link JsonComponentModule} is in use. Can be used to annotate
  * implementations directly or a class that contains them as inner-classes. For example:
  * 
  * @JsonComponent
  * public class CustomerJsonComponent {
  *
- *     public static class Serializer extends JsonSerializer<Customer> {
+ *     public static class Serializer extends ValueSerializer<Customer> {
  *
  *         // ...
  *
  *     }
  *
- *     public static class Deserializer extends JsonDeserializer<Customer> {
+ *     public static class Deserializer extends ValueDeserializer<Customer> {
  *
  *         // ...
  *
@@ -76,10 +76,9 @@ public @interface JsonComponent {
 	/**
 	 * The types that are handled by the provided serializer/deserializer. This attribute
 	 * is mandatory for a {@link KeyDeserializer}, as the type cannot be inferred. For a
-	 * {@link JsonSerializer} or {@link JsonDeserializer} it can be used to limit handling
-	 * to a subclasses of type inferred from the generic.
+	 * {@link ValueSerializer} or {@link ValueDeserializer} it can be used to limit
+	 * handling to a subclasses of type inferred from the generic.
 	 * @return the types that should be handled by the component
-	 * @since 2.2.0
 	 */
 	Class[] type() default {};
 
@@ -87,7 +86,6 @@ public @interface JsonComponent {
 	 * The scope under which the serializer/deserializer should be registered with the
 	 * module.
 	 * @return the component's handle type
-	 * @since 2.2.0
 	 */
 	Scope scope() default Scope.VALUES;
 
@@ -98,15 +96,15 @@ public @interface JsonComponent {
 
 		/**
 		 * A serializer/deserializer for regular value content.
-		 * @see JsonSerializer
-		 * @see JsonDeserializer
+		 * @see ValueSerializer
+		 * @see ValueDeserializer
 		 */
 		VALUES,
 
 		/**
 		 * A serializer/deserializer for keys.
-		 * @see JsonSerializer
-		 * @see KeyDeserializer
+		 * @see ValueSerializer
+		 * @see ValueDeserializer
 		 */
 		KEYS
 
diff --git a/module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/JsonComponentModule.java b/module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/JsonComponentModule.java
index 2888c8047e2..304ed65a5b3 100644
--- a/module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/JsonComponentModule.java
+++ b/module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/JsonComponentModule.java
@@ -23,12 +23,11 @@ import java.util.List;
 import java.util.Map;
 import java.util.function.BiConsumer;
 
-import com.fasterxml.jackson.databind.JsonDeserializer;
-import com.fasterxml.jackson.databind.JsonSerializer;
-import com.fasterxml.jackson.databind.KeyDeserializer;
-import com.fasterxml.jackson.databind.Module;
-import com.fasterxml.jackson.databind.module.SimpleModule;
 import org.jspecify.annotations.Nullable;
+import tools.jackson.databind.KeyDeserializer;
+import tools.jackson.databind.ValueDeserializer;
+import tools.jackson.databind.ValueSerializer;
+import tools.jackson.databind.module.SimpleModule;
 
 import org.springframework.aot.generate.GenerationContext;
 import org.springframework.aot.hint.MemberCategory;
@@ -104,11 +103,11 @@ public class JsonComponentModule extends SimpleModule implements BeanFactoryAwar
 	}
 
 	private void addJsonBean(Object bean, Class[] types, Scope scope) {
-		if (bean instanceof JsonSerializer jsonSerializer) {
-			addJsonSerializerBean(jsonSerializer, scope, types);
+		if (bean instanceof ValueSerializer jsonSerializer) {
+			addValueSerializerBean(jsonSerializer, scope, types);
 		}
-		else if (bean instanceof JsonDeserializer jsonDeserializer) {
-			addJsonDeserializerBean(jsonDeserializer, types);
+		else if (bean instanceof ValueDeserializer jsonDeserializer) {
+			addValueDeserializerBean(jsonDeserializer, types);
 		}
 		else if (bean instanceof KeyDeserializer keyDeserializer) {
 			addKeyDeserializerBean(keyDeserializer, types);
@@ -122,14 +121,15 @@ public class JsonComponentModule extends SimpleModule implements BeanFactoryAwar
 	}
 
 	private static boolean isSuitableInnerClass(Class innerClass) {
-		return !Modifier.isAbstract(innerClass.getModifiers()) && (JsonSerializer.class.isAssignableFrom(innerClass)
-				|| JsonDeserializer.class.isAssignableFrom(innerClass)
+		return !Modifier.isAbstract(innerClass.getModifiers()) && (ValueSerializer.class.isAssignableFrom(innerClass)
+				|| ValueDeserializer.class.isAssignableFrom(innerClass)
 				|| KeyDeserializer.class.isAssignableFrom(innerClass));
 	}
 
 	@SuppressWarnings("unchecked")
-	private  void addJsonSerializerBean(JsonSerializer serializer, JsonComponent.Scope scope, Class[] types) {
-		Class baseType = (Class) ResolvableType.forClass(JsonSerializer.class, serializer.getClass())
+	private  void addValueSerializerBean(ValueSerializer serializer, JsonComponent.Scope scope,
+			Class[] types) {
+		Class baseType = (Class) ResolvableType.forClass(ValueSerializer.class, serializer.getClass())
 			.resolveGeneric();
 		Assert.state(baseType != null, "'baseType' must not be null");
 		addBeanToModule(serializer, baseType, types,
@@ -138,8 +138,8 @@ public class JsonComponentModule extends SimpleModule implements BeanFactoryAwar
 	}
 
 	@SuppressWarnings("unchecked")
-	private  void addJsonDeserializerBean(JsonDeserializer deserializer, Class[] types) {
-		Class baseType = (Class) ResolvableType.forClass(JsonDeserializer.class, deserializer.getClass())
+	private  void addValueDeserializerBean(ValueDeserializer deserializer, Class[] types) {
+		Class baseType = (Class) ResolvableType.forClass(ValueDeserializer.class, deserializer.getClass())
 			.resolveGeneric();
 		Assert.state(baseType != null, "'baseType' must not be null");
 		addBeanToModule(deserializer, baseType, types, this::addDeserializer);
diff --git a/module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/JsonMixinModule.java b/module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/JsonMixinModule.java
index 8741a1534c4..5d28cd048e8 100644
--- a/module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/JsonMixinModule.java
+++ b/module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/JsonMixinModule.java
@@ -16,9 +16,8 @@
 
 package org.springframework.boot.jackson;
 
-import com.fasterxml.jackson.databind.Module;
-import com.fasterxml.jackson.databind.module.SimpleModule;
 import org.jspecify.annotations.Nullable;
+import tools.jackson.databind.module.SimpleModule;
 
 /**
  * Spring Bean and Jackson {@link Module} to find and
diff --git a/module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/JsonObjectDeserializer.java b/module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/ObjectValueDeserializer.java
similarity index 78%
rename from module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/JsonObjectDeserializer.java
rename to module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/ObjectValueDeserializer.java
index 7d31133885a..53d5a8f74bf 100644
--- a/module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/JsonObjectDeserializer.java
+++ b/module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/ObjectValueDeserializer.java
@@ -16,47 +16,39 @@
 
 package org.springframework.boot.jackson;
 
-import java.io.IOException;
 import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.util.function.Function;
 
-import com.fasterxml.jackson.core.JsonParser;
-import com.fasterxml.jackson.core.ObjectCodec;
-import com.fasterxml.jackson.core.TreeNode;
-import com.fasterxml.jackson.databind.DeserializationContext;
-import com.fasterxml.jackson.databind.JsonDeserializer;
-import com.fasterxml.jackson.databind.JsonMappingException;
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.node.NullNode;
 import org.jspecify.annotations.Nullable;
+import tools.jackson.core.JsonParser;
+import tools.jackson.core.TreeNode;
+import tools.jackson.databind.DeserializationContext;
+import tools.jackson.databind.JsonNode;
+import tools.jackson.databind.ValueDeserializer;
+import tools.jackson.databind.node.NullNode;
 
 import org.springframework.util.Assert;
 
 /**
- * Helper base class for {@link JsonDeserializer} implementations that deserialize
+ * Helper base class for {@link ValueDeserializer} implementations that deserialize
  * objects.
  *
  * @param  the supported object type
  * @author Phillip Webb
  * @since 4.0.0
- * @see JsonObjectSerializer
+ * @see ObjectValueSerializer
  */
-public abstract class JsonObjectDeserializer extends com.fasterxml.jackson.databind.JsonDeserializer {
+public abstract class ObjectValueDeserializer extends ValueDeserializer {
 
 	@Override
-	public final T deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
-		try {
-			ObjectCodec codec = jp.getCodec();
-			JsonNode tree = codec.readTree(jp);
-			return deserializeObject(jp, ctxt, codec, tree);
-		}
-		catch (Exception ex) {
-			if (ex instanceof IOException ioException) {
-				throw ioException;
-			}
-			throw new JsonMappingException(jp, "Object deserialize error", ex);
+	public final T deserialize(JsonParser jp, DeserializationContext ctxt) {
+		TreeNode tree = jp.readValueAsTree();
+		if (tree instanceof JsonNode jsonNode) {
+			return deserializeObject(jp, ctxt, jsonNode);
 		}
+		throw new IllegalStateException(
+				"JsonParser " + jp + " produced " + tree.getClass() + " that is not a JsonNode");
 	}
 
 	/**
@@ -64,15 +56,12 @@ public abstract class JsonObjectDeserializer extends com.fasterxml.jackson.da
 	 * @param jsonParser the source parser used for reading JSON content
 	 * @param context context that can be used to access information about this
 	 * deserialization activity
-	 * @param codec the {@link ObjectCodec} associated with the parser
 	 * @param tree deserialized JSON content as tree expressed using set of
 	 * {@link TreeNode} instances
 	 * @return the deserialized object
-	 * @throws IOException on error
 	 * @see #deserialize(JsonParser, DeserializationContext)
 	 */
-	protected abstract T deserializeObject(JsonParser jsonParser, DeserializationContext context, ObjectCodec codec,
-			JsonNode tree) throws IOException;
+	protected abstract T deserializeObject(JsonParser jsonParser, DeserializationContext context, JsonNode tree);
 
 	/**
 	 * Helper method to extract a value from the given {@code jsonNode} or return
@@ -110,7 +99,7 @@ public abstract class JsonObjectDeserializer extends com.fasterxml.jackson.da
 			return null;
 		}
 		if (type == String.class) {
-			return (D) jsonNode.textValue();
+			return (D) jsonNode.stringValue();
 		}
 		if (type == Boolean.class) {
 			return (D) Boolean.valueOf(jsonNode.booleanValue());
diff --git a/module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/JsonObjectSerializer.java b/module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/ObjectValueSerializer.java
similarity index 53%
rename from module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/JsonObjectSerializer.java
rename to module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/ObjectValueSerializer.java
index ef6254375f6..f8ca80b24cc 100644
--- a/module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/JsonObjectSerializer.java
+++ b/module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/ObjectValueSerializer.java
@@ -16,46 +16,33 @@
 
 package org.springframework.boot.jackson;
 
-import java.io.IOException;
-
-import com.fasterxml.jackson.core.JsonGenerator;
-import com.fasterxml.jackson.databind.JsonMappingException;
-import com.fasterxml.jackson.databind.JsonSerializer;
-import com.fasterxml.jackson.databind.SerializerProvider;
+import tools.jackson.core.JsonGenerator;
+import tools.jackson.databind.SerializationContext;
+import tools.jackson.databind.ValueSerializer;
 
 /**
- * Helper base class for {@link JsonSerializer} implementations that serialize objects.
+ * Helper base class for {@link ValueSerializer} implementations that serialize objects.
  *
  * @param  the supported object type
  * @author Phillip Webb
  * @since 4.0.0
- * @see JsonObjectDeserializer
+ * @see ObjectValueDeserializer
  */
-public abstract class JsonObjectSerializer extends JsonSerializer {
+public abstract class ObjectValueSerializer extends ValueSerializer {
 
 	@Override
-	public final void serialize(T value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
-		try {
-			jgen.writeStartObject();
-			serializeObject(value, jgen, provider);
-			jgen.writeEndObject();
-		}
-		catch (Exception ex) {
-			if (ex instanceof IOException ioException) {
-				throw ioException;
-			}
-			throw new JsonMappingException(jgen, "Object serialize error", ex);
-		}
+	public final void serialize(T value, JsonGenerator jgen, SerializationContext context) {
+		jgen.writeStartObject();
+		serializeObject(value, jgen, context);
+		jgen.writeEndObject();
 	}
 
 	/**
 	 * Serialize JSON content into the value type this serializer handles.
 	 * @param value the source value
 	 * @param jgen the JSON generator
-	 * @param provider the serializer provider
-	 * @throws IOException on error
+	 * @param context the serialization context
 	 */
-	protected abstract void serializeObject(T value, JsonGenerator jgen, SerializerProvider provider)
-			throws IOException;
+	protected abstract void serializeObject(T value, JsonGenerator jgen, SerializationContext context);
 
 }
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 ba55554bdf1..4c9301ded70 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
@@ -21,24 +21,22 @@ import java.text.DateFormat;
 import java.text.SimpleDateFormat;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.TimeZone;
+import java.util.function.BiConsumer;
 import java.util.stream.Stream;
 
 import com.fasterxml.jackson.annotation.JsonAutoDetect;
-import com.fasterxml.jackson.annotation.JsonCreator;
 import com.fasterxml.jackson.annotation.PropertyAccessor;
-import com.fasterxml.jackson.databind.Module;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.PropertyNamingStrategies;
-import com.fasterxml.jackson.databind.PropertyNamingStrategy;
-import com.fasterxml.jackson.databind.SerializationFeature;
-import com.fasterxml.jackson.databind.cfg.ConstructorDetector;
-import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
 import org.jspecify.annotations.Nullable;
+import tools.jackson.databind.JacksonModule;
+import tools.jackson.databind.ObjectMapper;
+import tools.jackson.databind.PropertyNamingStrategies;
+import tools.jackson.databind.PropertyNamingStrategy;
+import tools.jackson.databind.cfg.ConstructorDetector;
+import tools.jackson.databind.json.JsonMapper;
 
 import org.springframework.aot.hint.ReflectionHints;
 import org.springframework.aot.hint.RuntimeHints;
@@ -61,7 +59,6 @@ import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Primary;
 import org.springframework.context.annotation.Scope;
 import org.springframework.core.Ordered;
-import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
 import org.springframework.util.Assert;
 import org.springframework.util.ClassUtils;
 import org.springframework.util.ReflectionUtils;
@@ -69,10 +66,11 @@ import org.springframework.util.ReflectionUtils;
 /**
  * Auto configuration for Jackson. The following auto-configuration will get applied:
  * 
    - *
  • an {@link ObjectMapper} in case none is already configured.
  • - *
  • a {@link Jackson2ObjectMapperBuilder} in case none is already configured.
  • - *
  • auto-registration for all {@link Module} beans with all {@link ObjectMapper} beans - * (including the defaulted ones).
  • + *
  • an {@link JsonMapper} in case none is already configured.
  • + *
  • a {@link tools.jackson.databind.json.JsonMapper.Builder} in case none is already + * configured.
  • + *
  • auto-registration for all {@link JacksonModule} beans with all {@link ObjectMapper} + * beans (including the defaulted ones).
  • *
* * @author Oliver Gierke @@ -86,24 +84,36 @@ import org.springframework.util.ReflectionUtils; * @since 4.0.0 */ @AutoConfiguration -@ConditionalOnClass(ObjectMapper.class) -@SuppressWarnings("removal") +@ConditionalOnClass(JsonMapper.class) public final class JacksonAutoConfiguration { - private static final Map FEATURE_DEFAULTS; - - static { - Map featureDefaults = new HashMap<>(); - featureDefaults.put(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); - featureDefaults.put(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS, false); - FEATURE_DEFAULTS = Collections.unmodifiableMap(featureDefaults); - } - @Bean JsonComponentModule jsonComponentModule() { return new JsonComponentModule(); } + @Bean + @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) + @ConditionalOnMissingBean + JsonMapper.Builder jsonMapperBuilder(List customizers) { + JsonMapper.Builder builder = JsonMapper.builder(); + customize(builder, customizers); + return builder; + } + + private void customize(JsonMapper.Builder builder, List customizers) { + for (JsonMapperBuilderCustomizer customizer : customizers) { + customizer.customize(builder); + } + } + + @Bean + @Primary + @ConditionalOnMissingBean(ObjectMapper.class) + JsonMapper jacksonJsonMapper(JsonMapper.Builder builder) { + return builder.build(); + } + @Configuration(proxyBeanMethods = false) static class JacksonMixinConfiguration { @@ -124,74 +134,23 @@ public final class JacksonAutoConfiguration { } @Configuration(proxyBeanMethods = false) - @ConditionalOnClass(Jackson2ObjectMapperBuilder.class) - static class JacksonObjectMapperConfiguration { - - @Bean - @Primary - @ConditionalOnMissingBean - ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) { - return builder.createXmlMapper(false).build(); - } - - } - - @Configuration(proxyBeanMethods = false) - @ConditionalOnClass(ParameterNamesModule.class) - static class ParameterNamesModuleConfiguration { - - @Bean - @ConditionalOnMissingBean - ParameterNamesModule parameterNamesModule() { - return new ParameterNamesModule(JsonCreator.Mode.DEFAULT); - } - - } - - @Configuration(proxyBeanMethods = false) - @ConditionalOnClass(Jackson2ObjectMapperBuilder.class) - static class JacksonObjectMapperBuilderConfiguration { - - @Bean - @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) - @ConditionalOnMissingBean - Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder(ApplicationContext applicationContext, - List customizers) { - Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder(); - builder.applicationContext(applicationContext); - customize(builder, customizers); - return builder; - } - - private void customize(Jackson2ObjectMapperBuilder builder, - List customizers) { - for (Jackson2ObjectMapperBuilderCustomizer customizer : customizers) { - customizer.customize(builder); - } - } - - } - - @Configuration(proxyBeanMethods = false) - @ConditionalOnClass(Jackson2ObjectMapperBuilder.class) @EnableConfigurationProperties(JacksonProperties.class) - static class Jackson2ObjectMapperBuilderCustomizerConfiguration { + static class JacksonJsonMapperBuilderCustomizerConfiguration { @Bean - StandardJackson2ObjectMapperBuilderCustomizer standardJacksonObjectMapperBuilderCustomizer( - JacksonProperties jacksonProperties, ObjectProvider modules) { - return new StandardJackson2ObjectMapperBuilderCustomizer(jacksonProperties, modules.stream().toList()); + StandardJsonMapperBuilderCustomizer standardJsonMapperBuilderCustomizer(JacksonProperties jacksonProperties, + ObjectProvider modules) { + return new StandardJsonMapperBuilderCustomizer(jacksonProperties, modules.stream().toList()); } - static final class StandardJackson2ObjectMapperBuilderCustomizer - implements Jackson2ObjectMapperBuilderCustomizer, Ordered { + static final class StandardJsonMapperBuilderCustomizer implements JsonMapperBuilderCustomizer, Ordered { private final JacksonProperties jacksonProperties; - private final Collection modules; + private final Collection modules; - StandardJackson2ObjectMapperBuilderCustomizer(JacksonProperties jacksonProperties, - Collection modules) { + StandardJsonMapperBuilderCustomizer(JacksonProperties jacksonProperties, + Collection modules) { this.jacksonProperties = jacksonProperties; this.modules = modules; } @@ -202,22 +161,22 @@ public final class JacksonAutoConfiguration { } @Override - public void customize(Jackson2ObjectMapperBuilder builder) { + public void customize(JsonMapper.Builder builder) { if (this.jacksonProperties.getDefaultPropertyInclusion() != null) { - builder.serializationInclusion(this.jacksonProperties.getDefaultPropertyInclusion()); + builder.changeDefaultPropertyInclusion((handler) -> handler + .withValueInclusion(this.jacksonProperties.getDefaultPropertyInclusion())); } if (this.jacksonProperties.getTimeZone() != null) { - builder.timeZone(this.jacksonProperties.getTimeZone()); + builder.defaultTimeZone(this.jacksonProperties.getTimeZone()); } - configureFeatures(builder, FEATURE_DEFAULTS); configureVisibility(builder, this.jacksonProperties.getVisibility()); - configureFeatures(builder, this.jacksonProperties.getDeserialization()); - configureFeatures(builder, this.jacksonProperties.getSerialization()); - configureFeatures(builder, this.jacksonProperties.getMapper()); - configureFeatures(builder, this.jacksonProperties.getParser()); - configureFeatures(builder, this.jacksonProperties.getGenerator()); - configureFeatures(builder, this.jacksonProperties.getDatatype().getEnum()); - configureFeatures(builder, this.jacksonProperties.getDatatype().getJsonNode()); + configureFeatures(builder, this.jacksonProperties.getDeserialization(), builder::configure); + configureFeatures(builder, this.jacksonProperties.getSerialization(), builder::configure); + configureFeatures(builder, this.jacksonProperties.getMapper(), builder::configure); + configureFeatures(builder, this.jacksonProperties.getRead(), builder::configure); + configureFeatures(builder, this.jacksonProperties.getWrite(), builder::configure); + configureFeatures(builder, this.jacksonProperties.getDatatype().getEnum(), builder::configure); + configureFeatures(builder, this.jacksonProperties.getDatatype().getJsonNode(), builder::configure); configureDateFormat(builder); configurePropertyNamingStrategy(builder); configureModules(builder); @@ -226,49 +185,50 @@ public final class JacksonAutoConfiguration { configureConstructorDetector(builder); } - private void configureFeatures(Jackson2ObjectMapperBuilder builder, Map features) { + private void configureFeatures(JsonMapper.Builder builder, Map features, + BiConsumer configure) { features.forEach((feature, value) -> { if (value != null) { - if (value) { - builder.featuresToEnable(feature); - } - else { - builder.featuresToDisable(feature); - } + configure.accept(feature, value); } }); } - private void configureVisibility(Jackson2ObjectMapperBuilder builder, + private void configureVisibility(JsonMapper.Builder builder, Map visibilities) { - visibilities.forEach(builder::visibility); + builder.changeDefaultVisibility((visibilityChecker) -> { + for (Map.Entry entry : visibilities.entrySet()) { + visibilityChecker = visibilityChecker.withVisibility(entry.getKey(), entry.getValue()); + } + return visibilityChecker; + }); } - private void configureDateFormat(Jackson2ObjectMapperBuilder builder) { + private void configureDateFormat(JsonMapper.Builder builder) { // We support a fully qualified class name extending DateFormat or a date // pattern string value String dateFormat = this.jacksonProperties.getDateFormat(); if (dateFormat != null) { try { Class dateFormatClass = ClassUtils.forName(dateFormat, null); - builder.dateFormat((DateFormat) BeanUtils.instantiateClass(dateFormatClass)); + builder.defaultDateFormat((DateFormat) BeanUtils.instantiateClass(dateFormatClass)); } catch (ClassNotFoundException ex) { SimpleDateFormat simpleDateFormat = new SimpleDateFormat(dateFormat); // Since Jackson 2.6.3 we always need to set a TimeZone (see - // gh-4170). If none in our properties fallback to the Jackson's + // gh-4170). If none in our properties fallback to Jackson's // default TimeZone timeZone = this.jacksonProperties.getTimeZone(); if (timeZone == null) { - timeZone = new ObjectMapper().getSerializationConfig().getTimeZone(); + timeZone = new ObjectMapper().serializationConfig().getTimeZone(); } simpleDateFormat.setTimeZone(timeZone); - builder.dateFormat(simpleDateFormat); + builder.defaultDateFormat(simpleDateFormat); } } } - private void configurePropertyNamingStrategy(Jackson2ObjectMapperBuilder builder) { + private void configurePropertyNamingStrategy(JsonMapper.Builder builder) { // We support a fully qualified class name extending Jackson's // PropertyNamingStrategy or a string value corresponding to the constant // names in PropertyNamingStrategy which hold default provided @@ -284,13 +244,13 @@ public final class JacksonAutoConfiguration { } } - private void configurePropertyNamingStrategyClass(Jackson2ObjectMapperBuilder builder, + private void configurePropertyNamingStrategyClass(JsonMapper.Builder builder, Class propertyNamingStrategyClass) { builder.propertyNamingStrategy( (PropertyNamingStrategy) BeanUtils.instantiateClass(propertyNamingStrategyClass)); } - private void configurePropertyNamingStrategyField(Jackson2ObjectMapperBuilder builder, String fieldName) { + private void configurePropertyNamingStrategyField(JsonMapper.Builder builder, String fieldName) { // Find the field (this way we automatically support new constants // that may be added by Jackson in the future) Field field = findPropertyNamingStrategyField(fieldName); @@ -309,38 +269,34 @@ public final class JacksonAutoConfiguration { return field; } - private void configureModules(Jackson2ObjectMapperBuilder builder) { - builder.modulesToInstall((modules) -> modules.addAll(this.modules)); + private void configureModules(JsonMapper.Builder builder) { + builder.addModules(this.modules); } - private void configureLocale(Jackson2ObjectMapperBuilder builder) { + private void configureLocale(JsonMapper.Builder builder) { Locale locale = this.jacksonProperties.getLocale(); if (locale != null) { - builder.locale(locale); + builder.defaultLocale(locale); } } - private void configureDefaultLeniency(Jackson2ObjectMapperBuilder builder) { + private void configureDefaultLeniency(JsonMapper.Builder builder) { Boolean defaultLeniency = this.jacksonProperties.getDefaultLeniency(); if (defaultLeniency != null) { - builder.postConfigurer((objectMapper) -> objectMapper.setDefaultLeniency(defaultLeniency)); + builder.defaultLeniency(defaultLeniency); } } - private void configureConstructorDetector(Jackson2ObjectMapperBuilder builder) { + private void configureConstructorDetector(JsonMapper.Builder builder) { ConstructorDetectorStrategy strategy = this.jacksonProperties.getConstructorDetector(); if (strategy != null) { - builder.postConfigurer((objectMapper) -> { - switch (strategy) { - case USE_PROPERTIES_BASED -> - objectMapper.setConstructorDetector(ConstructorDetector.USE_PROPERTIES_BASED); - case USE_DELEGATING -> - objectMapper.setConstructorDetector(ConstructorDetector.USE_DELEGATING); - case EXPLICIT_ONLY -> - objectMapper.setConstructorDetector(ConstructorDetector.EXPLICIT_ONLY); - default -> objectMapper.setConstructorDetector(ConstructorDetector.DEFAULT); - } - }); + switch (strategy) { + case USE_PROPERTIES_BASED -> + builder.constructorDetector(ConstructorDetector.USE_PROPERTIES_BASED); + case USE_DELEGATING -> builder.constructorDetector(ConstructorDetector.USE_DELEGATING); + case EXPLICIT_ONLY -> builder.constructorDetector(ConstructorDetector.EXPLICIT_ONLY); + default -> builder.constructorDetector(ConstructorDetector.DEFAULT); + } } } @@ -352,7 +308,7 @@ public final class JacksonAutoConfiguration { @Override public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) { - if (ClassUtils.isPresent("com.fasterxml.jackson.databind.PropertyNamingStrategy", classLoader)) { + if (ClassUtils.isPresent("tools.jackson.databind.PropertyNamingStrategy", classLoader)) { registerPropertyNamingStrategyHints(hints.reflection()); } } diff --git a/module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/autoconfigure/JacksonBackgroundPreinitializer.java b/module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/autoconfigure/JacksonBackgroundPreinitializer.java index 6763ba9d6ae..cefbd1252a6 100644 --- a/module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/autoconfigure/JacksonBackgroundPreinitializer.java +++ b/module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/autoconfigure/JacksonBackgroundPreinitializer.java @@ -16,20 +16,20 @@ package org.springframework.boot.jackson.autoconfigure; +import tools.jackson.databind.json.JsonMapper; + import org.springframework.boot.autoconfigure.preinitialize.BackgroundPreinitializer; -import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; /** - * {@link JacksonBackgroundPreinitializer} for Jackson. + * {@link BackgroundPreinitializer} for Jackson. * * @author Phillip Webb */ -@SuppressWarnings("removal") final class JacksonBackgroundPreinitializer implements BackgroundPreinitializer { @Override public void preinitialize() throws Exception { - Jackson2ObjectMapperBuilder.json().build(); + JsonMapper.builder().build(); } } diff --git a/module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/autoconfigure/JacksonProperties.java b/module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/autoconfigure/JacksonProperties.java index f80ec16fd72..eab4402de25 100644 --- a/module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/autoconfigure/JacksonProperties.java +++ b/module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/autoconfigure/JacksonProperties.java @@ -24,14 +24,14 @@ import java.util.TimeZone; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.PropertyAccessor; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.MapperFeature; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.databind.cfg.EnumFeature; -import com.fasterxml.jackson.databind.cfg.JsonNodeFeature; import org.jspecify.annotations.Nullable; +import tools.jackson.core.json.JsonReadFeature; +import tools.jackson.core.json.JsonWriteFeature; +import tools.jackson.databind.DeserializationFeature; +import tools.jackson.databind.MapperFeature; +import tools.jackson.databind.SerializationFeature; +import tools.jackson.databind.cfg.EnumFeature; +import tools.jackson.databind.cfg.JsonNodeFeature; import org.springframework.boot.context.properties.ConfigurationProperties; @@ -81,14 +81,14 @@ public class JacksonProperties { private final Map mapper = new EnumMap<>(MapperFeature.class); /** - * Jackson on/off features for parsers. + * Jackson on/off features for readers. */ - private final Map parser = new EnumMap<>(JsonParser.Feature.class); + private final Map read = new EnumMap<>(JsonReadFeature.class); /** - * Jackson on/off features for generators. + * Jackson on/off features for writers. */ - private final Map generator = new EnumMap<>(JsonGenerator.Feature.class); + private final Map write = new EnumMap<>(JsonWriteFeature.class); /** * Controls the inclusion of properties during serialization. Configured with one of @@ -152,12 +152,12 @@ public class JacksonProperties { return this.mapper; } - public Map getParser() { - return this.parser; + public Map getRead() { + return this.read; } - public Map getGenerator() { - return this.generator; + public Map getWrite() { + return this.write; } public JsonInclude.@Nullable Include getDefaultPropertyInclusion() { diff --git a/module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/autoconfigure/Jackson2ObjectMapperBuilderCustomizer.java b/module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/autoconfigure/JsonMapperBuilderCustomizer.java similarity index 62% rename from module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/autoconfigure/Jackson2ObjectMapperBuilderCustomizer.java rename to module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/autoconfigure/JsonMapperBuilderCustomizer.java index 6a354e3eea6..c73921eeb04 100644 --- a/module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/autoconfigure/Jackson2ObjectMapperBuilderCustomizer.java +++ b/module/spring-boot-jackson/src/main/java/org/springframework/boot/jackson/autoconfigure/JsonMapperBuilderCustomizer.java @@ -16,26 +16,23 @@ package org.springframework.boot.jackson.autoconfigure; -import com.fasterxml.jackson.databind.ObjectMapper; - -import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; +import tools.jackson.databind.json.JsonMapper; /** * Callback interface that can be implemented by beans wishing to further customize the - * {@link ObjectMapper} through {@link Jackson2ObjectMapperBuilder} to fine-tune its - * auto-configuration. + * {@link JsonMapper} through {@link tools.jackson.databind.json.JsonMapper.Builder} to + * fine-tune its auto-configuration. * * @author Grzegorz Poznachowski * @since 4.0.0 */ @FunctionalInterface -@SuppressWarnings("removal") -public interface Jackson2ObjectMapperBuilderCustomizer { +public interface JsonMapperBuilderCustomizer { /** - * Customize the JacksonObjectMapperBuilder. - * @param jacksonObjectMapperBuilder the JacksonObjectMapperBuilder to customize + * Customize the JsonMapper.Builder. + * @param jsonMapperBuilder the builder to customize */ - void customize(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder); + void customize(JsonMapper.Builder jsonMapperBuilder); } diff --git a/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/JsonComponentModuleTests.java b/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/JsonComponentModuleTests.java index 1e01f4cb448..ffccec3d0b7 100644 --- a/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/JsonComponentModuleTests.java +++ b/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/JsonComponentModuleTests.java @@ -16,16 +16,15 @@ package org.springframework.boot.jackson; -import java.io.IOException; import java.util.HashMap; import java.util.Map; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.Module; -import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; +import tools.jackson.core.JacksonException; +import tools.jackson.core.type.TypeReference; +import tools.jackson.databind.JacksonModule; +import tools.jackson.databind.json.JsonMapper; import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.RuntimeHints; @@ -121,7 +120,7 @@ class JsonComponentModuleTests { load(NameAndCareerJsonComponent.class); JsonComponentModule module = this.context.getBean(JsonComponentModule.class); assertSerialize(module, new NameAndCareer("spring", "developer"), "{\"name\":\"spring\"}"); - assertSerialize(module); + assertSerialize(module, NameAndAge.create("spring", 100), "{\"age\":100,\"name\":\"spring\"}"); assertDeserializeForSpecifiedClasses(module); } @@ -157,29 +156,26 @@ class JsonComponentModuleTests { this.context = context; } - private void assertSerialize(Module module, Name value, String expectedJson) throws Exception { - ObjectMapper mapper = new ObjectMapper(); - mapper.registerModule(module); + private void assertSerialize(JacksonModule module, Name value, String expectedJson) throws Exception { + JsonMapper mapper = JsonMapper.builder().addModule(module).build(); String json = mapper.writeValueAsString(value); assertThat(json).isEqualToIgnoringWhitespace(expectedJson); } - private void assertSerialize(Module module) throws Exception { - assertSerialize(module, new NameAndAge("spring", 100), "{\"name\":\"spring\",\"age\":100}"); + private void assertSerialize(JacksonModule module) throws Exception { + assertSerialize(module, NameAndAge.create("spring", 100), "{\"theName\":\"spring\",\"theAge\":100}"); } - private void assertDeserialize(Module module) throws Exception { - ObjectMapper mapper = new ObjectMapper(); - mapper.registerModule(module); + private void assertDeserialize(JacksonModule module) throws Exception { + JsonMapper mapper = JsonMapper.builder().addModule(module).build(); NameAndAge nameAndAge = mapper.readValue("{\"name\":\"spring\",\"age\":100}", NameAndAge.class); assertThat(nameAndAge.getName()).isEqualTo("spring"); assertThat(nameAndAge.getAge()).isEqualTo(100); } - private void assertDeserializeForSpecifiedClasses(JsonComponentModule module) throws IOException { - ObjectMapper mapper = new ObjectMapper(); - mapper.registerModule(module); - assertThatExceptionOfType(JsonMappingException.class) + private void assertDeserializeForSpecifiedClasses(JsonComponentModule module) { + JsonMapper mapper = JsonMapper.builder().addModule(module).build(); + assertThatExceptionOfType(JacksonException.class) .isThrownBy(() -> mapper.readValue("{\"name\":\"spring\",\"age\":100}", NameAndAge.class)); NameAndCareer nameAndCareer = mapper.readValue("{\"name\":\"spring\",\"career\":\"developer\"}", NameAndCareer.class); @@ -187,22 +183,20 @@ class JsonComponentModuleTests { assertThat(nameAndCareer.getCareer()).isEqualTo("developer"); } - private void assertKeySerialize(Module module) throws Exception { - ObjectMapper mapper = new ObjectMapper(); - mapper.registerModule(module); + private void assertKeySerialize(JacksonModule module) { + JsonMapper mapper = JsonMapper.builder().addModule(module).build(); Map map = new HashMap<>(); - map.put(new NameAndAge("spring", 100), true); + map.put(NameAndAge.create("spring", 100), true); String json = mapper.writeValueAsString(map); assertThat(json).isEqualToIgnoringWhitespace("{\"spring is 100\": true}"); } - private void assertKeyDeserialize(Module module) throws IOException { - ObjectMapper mapper = new ObjectMapper(); - mapper.registerModule(module); + private void assertKeyDeserialize(JacksonModule module) { + JsonMapper mapper = JsonMapper.builder().addModule(module).build(); TypeReference> typeRef = new TypeReference<>() { }; Map map = mapper.readValue("{\"spring is 100\": true}", typeRef); - assertThat(map).containsEntry(new NameAndAge("spring", 100), true); + assertThat(map).containsEntry(NameAndAge.create("spring", 100), true); } @JsonComponent diff --git a/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/JsonMixinModuleTests.java b/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/JsonMixinModuleTests.java index eb04af16d07..43ffbd66a48 100644 --- a/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/JsonMixinModuleTests.java +++ b/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/JsonMixinModuleTests.java @@ -19,10 +19,10 @@ package org.springframework.boot.jackson; import java.util.Arrays; import java.util.List; -import com.fasterxml.jackson.databind.Module; -import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; +import tools.jackson.databind.JacksonModule; +import tools.jackson.databind.json.JsonMapper; import org.springframework.beans.factory.BeanCreationException; import org.springframework.boot.jackson.scan.a.RenameMixInClass; @@ -67,7 +67,7 @@ class JsonMixinModuleTests { load(RenameMixInClass.class); JsonMixinModule module = this.context.getBean(JsonMixinModule.class); assertMixIn(module, new Name("spring"), "{\"username\":\"spring\"}"); - assertMixIn(module, new NameAndAge("spring", 100), "{\"age\":100,\"username\":\"spring\"}"); + assertMixIn(module, NameAndAge.create("spring", 100), "{\"age\":100,\"username\":\"spring\"}"); } @Test @@ -75,21 +75,21 @@ class JsonMixinModuleTests { load(EmptyMixInClass.class); JsonMixinModule module = this.context.getBean(JsonMixinModule.class); assertMixIn(module, new Name("spring"), "{\"name\":\"spring\"}"); - assertMixIn(module, new NameAndAge("spring", 100), "{\"name\":\"spring\",\"age\":100}"); + assertMixIn(module, NameAndAge.create("spring", 100), "{\"age\":100,\"name\":\"spring\"}"); } @Test void jsonWithModuleWithRenameMixInAbstractClassShouldBeMixedIn() throws Exception { load(RenameMixInAbstractClass.class); JsonMixinModule module = this.context.getBean(JsonMixinModule.class); - assertMixIn(module, new NameAndAge("spring", 100), "{\"age\":100,\"username\":\"spring\"}"); + assertMixIn(module, NameAndAge.create("spring", 100), "{\"age\":100,\"username\":\"spring\"}"); } @Test void jsonWithModuleWithRenameMixInInterfaceShouldBeMixedIn() throws Exception { load(RenameMixInInterface.class); JsonMixinModule module = this.context.getBean(JsonMixinModule.class); - assertMixIn(module, new NameAndAge("spring", 100), "{\"age\":100,\"username\":\"spring\"}"); + assertMixIn(module, NameAndAge.create("spring", 100), "{\"age\":100,\"username\":\"spring\"}"); } private void load(Class... basePackageClasses) { @@ -108,9 +108,8 @@ class JsonMixinModuleTests { return jsonMixinModule; } - private void assertMixIn(Module module, Name value, String expectedJson) throws Exception { - ObjectMapper mapper = new ObjectMapper(); - mapper.registerModule(module); + private void assertMixIn(JacksonModule module, Name value, String expectedJson) throws Exception { + JsonMapper mapper = JsonMapper.builder().addModule(module).build(); String json = mapper.writeValueAsString(value); assertThat(json).isEqualToIgnoringWhitespace(expectedJson); } diff --git a/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/NameAndAgeJsonComponent.java b/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/NameAndAgeJsonComponent.java index 087e95f9361..f15673a24ad 100644 --- a/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/NameAndAgeJsonComponent.java +++ b/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/NameAndAgeJsonComponent.java @@ -16,14 +16,11 @@ package org.springframework.boot.jackson; -import java.io.IOException; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.ObjectCodec; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.SerializerProvider; +import tools.jackson.core.JsonGenerator; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.SerializationContext; import org.springframework.boot.jackson.types.NameAndAge; @@ -35,25 +32,23 @@ import org.springframework.boot.jackson.types.NameAndAge; @JsonComponent public class NameAndAgeJsonComponent { - static class Serializer extends JsonObjectSerializer { + static class Serializer extends ObjectValueSerializer { @Override - protected void serializeObject(NameAndAge value, JsonGenerator jgen, SerializerProvider provider) - throws IOException { - jgen.writeStringField("name", value.getName()); - jgen.writeNumberField("age", value.getAge()); + protected void serializeObject(NameAndAge value, JsonGenerator jgen, SerializationContext context) { + jgen.writeStringProperty("theName", value.getName()); + jgen.writeNumberProperty("theAge", value.getAge()); } } - static class Deserializer extends JsonObjectDeserializer { + static class Deserializer extends ObjectValueDeserializer { @Override - protected NameAndAge deserializeObject(JsonParser jsonParser, DeserializationContext context, ObjectCodec codec, - JsonNode tree) throws IOException { + protected NameAndAge deserializeObject(JsonParser jsonParser, DeserializationContext context, JsonNode tree) { String name = nullSafeValue(tree.get("name"), String.class); Integer age = nullSafeValue(tree.get("age"), Integer.class); - return new NameAndAge(name, age); + return NameAndAge.create(name, age); } } diff --git a/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/NameAndAgeJsonKeyComponent.java b/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/NameAndAgeJsonKeyComponent.java index a1fc53e82d5..53b35d8f6ae 100644 --- a/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/NameAndAgeJsonKeyComponent.java +++ b/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/NameAndAgeJsonKeyComponent.java @@ -16,13 +16,11 @@ package org.springframework.boot.jackson; -import java.io.IOException; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.KeyDeserializer; -import com.fasterxml.jackson.databind.SerializerProvider; +import tools.jackson.core.JsonGenerator; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.KeyDeserializer; +import tools.jackson.databind.SerializationContext; +import tools.jackson.databind.ValueSerializer; import org.springframework.boot.jackson.types.NameAndAge; @@ -34,11 +32,11 @@ import org.springframework.boot.jackson.types.NameAndAge; @JsonComponent(type = NameAndAge.class, scope = JsonComponent.Scope.KEYS) public class NameAndAgeJsonKeyComponent { - static class Serializer extends JsonSerializer { + static class Serializer extends ValueSerializer { @Override - public void serialize(NameAndAge value, JsonGenerator jgen, SerializerProvider serializers) throws IOException { - jgen.writeFieldName(value.asKey()); + public void serialize(NameAndAge value, JsonGenerator jgen, SerializationContext serializers) { + jgen.writeName(value.asKey()); } } @@ -46,9 +44,9 @@ public class NameAndAgeJsonKeyComponent { static class Deserializer extends KeyDeserializer { @Override - public NameAndAge deserializeKey(String key, DeserializationContext ctxt) throws IOException { + public NameAndAge deserializeKey(String key, DeserializationContext ctxt) { String[] keys = key.split("is"); - return new NameAndAge(keys[0].trim(), Integer.parseInt(keys[1].trim())); + return NameAndAge.create(keys[0].trim(), Integer.parseInt(keys[1].trim())); } } diff --git a/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/NameAndCareerJsonComponent.java b/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/NameAndCareerJsonComponent.java index daed1bef0dd..0b68ef98179 100644 --- a/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/NameAndCareerJsonComponent.java +++ b/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/NameAndCareerJsonComponent.java @@ -16,14 +16,11 @@ package org.springframework.boot.jackson; -import java.io.IOException; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.ObjectCodec; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.SerializerProvider; +import tools.jackson.core.JsonGenerator; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.SerializationContext; import org.springframework.boot.jackson.types.Name; import org.springframework.boot.jackson.types.NameAndCareer; @@ -36,20 +33,19 @@ import org.springframework.boot.jackson.types.NameAndCareer; @JsonComponent(type = NameAndCareer.class) public class NameAndCareerJsonComponent { - static class Serializer extends JsonObjectSerializer { + static class Serializer extends ObjectValueSerializer { @Override - protected void serializeObject(Name value, JsonGenerator jgen, SerializerProvider provider) throws IOException { - jgen.writeStringField("name", value.getName()); + protected void serializeObject(Name value, JsonGenerator jgen, SerializationContext context) { + jgen.writeStringProperty("name", value.getName()); } } - static class Deserializer extends JsonObjectDeserializer { + static class Deserializer extends ObjectValueDeserializer { @Override - protected Name deserializeObject(JsonParser jsonParser, DeserializationContext context, ObjectCodec codec, - JsonNode tree) throws IOException { + protected Name deserializeObject(JsonParser jsonParser, DeserializationContext context, JsonNode tree) { String name = nullSafeValue(tree.get("name"), String.class); String career = nullSafeValue(tree.get("career"), String.class); return new NameAndCareer(name, career); diff --git a/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/JsonObjectDeserializerTests.java b/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/ObjectValueDeserializerTests.java similarity index 90% rename from module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/JsonObjectDeserializerTests.java rename to module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/ObjectValueDeserializerTests.java index 76ebaf49d4a..56c8bfb6fdd 100644 --- a/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/JsonObjectDeserializerTests.java +++ b/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/ObjectValueDeserializerTests.java @@ -22,14 +22,14 @@ import java.math.BigInteger; import java.time.LocalDate; import java.util.function.Function; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.ObjectCodec; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.module.SimpleModule; -import com.fasterxml.jackson.databind.node.NullNode; import org.junit.jupiter.api.Test; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; +import tools.jackson.databind.module.SimpleModule; +import tools.jackson.databind.node.NullNode; import org.springframework.boot.jackson.NameAndAgeJsonComponent.Deserializer; import org.springframework.boot.jackson.types.NameAndAge; @@ -41,11 +41,11 @@ import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; /** - * Tests for {@link JsonObjectDeserializer}. + * Tests for {@link ObjectValueDeserializer}. * * @author Phillip Webb */ -class JsonObjectDeserializerTests { +class ObjectValueDeserializerTests { private final TestJsonObjectDeserializer testDeserializer = new TestJsonObjectDeserializer<>(); @@ -54,8 +54,7 @@ class JsonObjectDeserializerTests { Deserializer deserializer = new NameAndAgeJsonComponent.Deserializer(); SimpleModule module = new SimpleModule(); module.addDeserializer(NameAndAge.class, deserializer); - ObjectMapper mapper = new ObjectMapper(); - mapper.registerModule(module); + ObjectMapper mapper = JsonMapper.builder().addModule(module).build(); NameAndAge nameAndAge = mapper.readValue("{\"name\":\"spring\",\"age\":100}", NameAndAge.class); assertThat(nameAndAge.getName()).isEqualTo("spring"); assertThat(nameAndAge.getAge()).isEqualTo(100); @@ -77,7 +76,7 @@ class JsonObjectDeserializerTests { @Test void nullSafeValueWhenClassIsStringShouldReturnString() { JsonNode node = mock(JsonNode.class); - given(node.textValue()).willReturn("abc"); + given(node.stringValue()).willReturn("abc"); String value = this.testDeserializer.testNullSafeValue(node, String.class); assertThat(value).isEqualTo("abc"); } @@ -149,7 +148,7 @@ class JsonObjectDeserializerTests { @Test void nullSafeValueWithMapperShouldTransformValue() { JsonNode node = mock(JsonNode.class); - given(node.textValue()).willReturn("2023-12-01"); + given(node.stringValue()).willReturn("2023-12-01"); LocalDate result = this.testDeserializer.testNullSafeValue(node, String.class, LocalDate::parse); assertThat(result).isEqualTo(LocalDate.of(2023, 12, 1)); } @@ -191,11 +190,10 @@ class JsonObjectDeserializerTests { assertThat(this.testDeserializer.testGetRequiredNode(node, "test")).isEqualTo(node); } - static class TestJsonObjectDeserializer extends JsonObjectDeserializer { + static class TestJsonObjectDeserializer extends ObjectValueDeserializer { @Override - protected T deserializeObject(JsonParser jsonParser, DeserializationContext context, ObjectCodec codec, - JsonNode tree) { + protected T deserializeObject(JsonParser jsonParser, DeserializationContext context, JsonNode tree) { return null; } diff --git a/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/JsonObjectSerializerTests.java b/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/ObjectValueSerializerTests.java similarity index 73% rename from module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/JsonObjectSerializerTests.java rename to module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/ObjectValueSerializerTests.java index 63607d30087..5d3dff1b3de 100644 --- a/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/JsonObjectSerializerTests.java +++ b/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/ObjectValueSerializerTests.java @@ -16,9 +16,9 @@ package org.springframework.boot.jackson; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.module.SimpleModule; import org.junit.jupiter.api.Test; +import tools.jackson.databind.json.JsonMapper; +import tools.jackson.databind.module.SimpleModule; import org.springframework.boot.jackson.NameAndAgeJsonComponent.Serializer; import org.springframework.boot.jackson.types.NameAndAge; @@ -26,21 +26,20 @@ import org.springframework.boot.jackson.types.NameAndAge; import static org.assertj.core.api.Assertions.assertThat; /** - * Tests for {@link JsonObjectSerializer}. + * Tests for {@link ObjectValueSerializer}. * * @author Phillip Webb */ -class JsonObjectSerializerTests { +class ObjectValueSerializerTests { @Test void serializeObjectShouldWriteJson() throws Exception { Serializer serializer = new NameAndAgeJsonComponent.Serializer(); SimpleModule module = new SimpleModule(); module.addSerializer(NameAndAge.class, serializer); - ObjectMapper mapper = new ObjectMapper(); - mapper.registerModule(module); - String json = mapper.writeValueAsString(new NameAndAge("spring", 100)); - assertThat(json).isEqualToIgnoringWhitespace("{\"name\":\"spring\",\"age\":100}"); + JsonMapper mapper = JsonMapper.builder().addModule(module).build(); + String json = mapper.writeValueAsString(NameAndAge.create("spring", 100)); + assertThat(json).isEqualToIgnoringWhitespace("{\"theName\":\"spring\",\"theAge\":100}"); } } 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 d3faca5ab20..3b3e08d8a5a 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 @@ -16,7 +16,6 @@ package org.springframework.boot.jackson.autoconfigure; -import java.io.IOException; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.time.Duration; @@ -24,35 +23,31 @@ import java.util.Date; import java.util.HashSet; import java.util.Set; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonCreator.Mode; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.ObjectCodec; -import com.fasterxml.jackson.databind.AnnotationIntrospector; -import com.fasterxml.jackson.databind.DeserializationConfig; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.MapperFeature; -import com.fasterxml.jackson.databind.Module; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.PropertyNamingStrategies; -import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.cfg.ConstructorDetector; -import com.fasterxml.jackson.databind.cfg.ConstructorDetector.SingleArgConstructor; -import com.fasterxml.jackson.databind.cfg.EnumFeature; -import com.fasterxml.jackson.databind.cfg.JsonNodeFeature; -import com.fasterxml.jackson.databind.exc.InvalidFormatException; -import com.fasterxml.jackson.databind.module.SimpleModule; -import com.fasterxml.jackson.databind.ser.DefaultSerializerProvider; -import com.fasterxml.jackson.databind.util.StdDateFormat; -import com.fasterxml.jackson.module.paramnames.ParameterNamesModule; import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.Test; +import tools.jackson.core.JsonGenerator; +import tools.jackson.core.json.JsonReadFeature; +import tools.jackson.core.json.JsonWriteFeature; +import tools.jackson.databind.DeserializationFeature; +import tools.jackson.databind.JacksonModule; +import tools.jackson.databind.MapperFeature; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.PropertyNamingStrategies; +import tools.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy; +import tools.jackson.databind.SerializationContext; +import tools.jackson.databind.SerializationFeature; +import tools.jackson.databind.ValueSerializer; +import tools.jackson.databind.cfg.ConstructorDetector; +import tools.jackson.databind.cfg.ConstructorDetector.SingleArgConstructor; +import tools.jackson.databind.cfg.EnumFeature; +import tools.jackson.databind.cfg.JsonNodeFeature; +import tools.jackson.databind.exc.InvalidFormatException; +import tools.jackson.databind.json.JsonMapper; +import tools.jackson.databind.json.JsonMapper.Builder; +import tools.jackson.databind.module.SimpleModule; +import tools.jackson.databind.util.StdDateFormat; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; @@ -63,7 +58,7 @@ import org.springframework.boot.jackson.JsonComponent; import org.springframework.boot.jackson.JsonMixin; import org.springframework.boot.jackson.JsonMixinModule; import org.springframework.boot.jackson.JsonMixinModuleEntries; -import org.springframework.boot.jackson.JsonObjectSerializer; +import org.springframework.boot.jackson.ObjectValueSerializer; import org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration.JacksonAutoConfigurationRuntimeHints; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; @@ -71,7 +66,6 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Primary; import org.springframework.core.annotation.Order; -import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -119,7 +113,8 @@ class JacksonAutoConfigurationTests { void noCustomDateFormat() { this.contextRunner.run((context) -> { ObjectMapper mapper = context.getBean(ObjectMapper.class); - assertThat(mapper.getDateFormat()).isInstanceOf(StdDateFormat.class); + assertThat(mapper.serializationConfig().getDateFormat()).isInstanceOf(StdDateFormat.class); + assertThat(mapper.deserializationConfig().getDateFormat()).isInstanceOf(StdDateFormat.class); }); } @@ -127,9 +122,12 @@ class JacksonAutoConfigurationTests { void customDateFormat() { this.contextRunner.withPropertyValues("spring.jackson.date-format:yyyyMMddHHmmss").run((context) -> { ObjectMapper mapper = context.getBean(ObjectMapper.class); - DateFormat dateFormat = mapper.getDateFormat(); - assertThat(dateFormat).isInstanceOf(SimpleDateFormat.class); - assertThat(((SimpleDateFormat) dateFormat).toPattern()).isEqualTo("yyyyMMddHHmmss"); + DateFormat serializationDateFormat = mapper.serializationConfig().getDateFormat(); + assertThat(serializationDateFormat).isInstanceOf(SimpleDateFormat.class); + assertThat(((SimpleDateFormat) serializationDateFormat).toPattern()).isEqualTo("yyyyMMddHHmmss"); + DateFormat deserializationDateFormat = mapper.serializationConfig().getDateFormat(); + assertThat(deserializationDateFormat).isInstanceOf(SimpleDateFormat.class); + assertThat(((SimpleDateFormat) deserializationDateFormat).toPattern()).isEqualTo("yyyyMMddHHmmss"); }); } @@ -138,7 +136,8 @@ class JacksonAutoConfigurationTests { this.contextRunner.withPropertyValues("spring.jackson.date-format:" + MyDateFormat.class.getName()) .run((context) -> { ObjectMapper mapper = context.getBean(ObjectMapper.class); - assertThat(mapper.getDateFormat()).isInstanceOf(MyDateFormat.class); + assertThat(mapper.serializationConfig().getDateFormat()).isInstanceOf(MyDateFormat.class); + assertThat(mapper.deserializationConfig().getDateFormat()).isInstanceOf(MyDateFormat.class); }); } @@ -146,7 +145,7 @@ class JacksonAutoConfigurationTests { void noCustomPropertyNamingStrategy() { this.contextRunner.run((context) -> { ObjectMapper mapper = context.getBean(ObjectMapper.class); - assertThat(mapper.getPropertyNamingStrategy()).isNull(); + assertThat(mapper.serializationConfig().getPropertyNamingStrategy()).isNull(); }); } @@ -154,17 +153,18 @@ class JacksonAutoConfigurationTests { void customPropertyNamingStrategyField() { this.contextRunner.withPropertyValues("spring.jackson.property-naming-strategy:SNAKE_CASE").run((context) -> { ObjectMapper mapper = context.getBean(ObjectMapper.class); - assertThat(mapper.getPropertyNamingStrategy()).isInstanceOf(SnakeCaseStrategy.class); + assertThat(mapper.serializationConfig().getPropertyNamingStrategy()).isInstanceOf(SnakeCaseStrategy.class); }); } @Test void customPropertyNamingStrategyClass() { this.contextRunner.withPropertyValues( - "spring.jackson.property-naming-strategy:com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy") + "spring.jackson.property-naming-strategy:tools.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy") .run((context) -> { ObjectMapper mapper = context.getBean(ObjectMapper.class); - assertThat(mapper.getPropertyNamingStrategy()).isInstanceOf(SnakeCaseStrategy.class); + assertThat(mapper.serializationConfig().getPropertyNamingStrategy()) + .isInstanceOf(SnakeCaseStrategy.class); }); } @@ -173,20 +173,19 @@ class JacksonAutoConfigurationTests { this.contextRunner.withPropertyValues("spring.jackson.serialization.indent_output:true").run((context) -> { ObjectMapper mapper = context.getBean(ObjectMapper.class); assertThat(SerializationFeature.INDENT_OUTPUT.enabledByDefault()).isFalse(); - assertThat(mapper.getSerializationConfig() - .hasSerializationFeatures(SerializationFeature.INDENT_OUTPUT.getMask())).isTrue(); + assertThat( + mapper.serializationConfig().hasSerializationFeatures(SerializationFeature.INDENT_OUTPUT.getMask())) + .isTrue(); }); } @Test void disableSerializationFeature() { - this.contextRunner.withPropertyValues("spring.jackson.serialization.write_dates_as_timestamps:false") - .run((context) -> { - ObjectMapper mapper = context.getBean(ObjectMapper.class); - assertThat(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS.enabledByDefault()).isTrue(); - assertThat(mapper.getSerializationConfig() - .hasSerializationFeatures(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS.getMask())).isFalse(); - }); + this.contextRunner.withPropertyValues("spring.jackson.serialization.wrap_exceptions:false").run((context) -> { + ObjectMapper mapper = context.getBean(ObjectMapper.class); + assertThat(SerializationFeature.WRAP_EXCEPTIONS.enabledByDefault()).isTrue(); + assertThat(mapper.isEnabled(SerializationFeature.WRAP_EXCEPTIONS)).isFalse(); + }); } @Test @@ -195,19 +194,20 @@ class JacksonAutoConfigurationTests { .run((context) -> { ObjectMapper mapper = context.getBean(ObjectMapper.class); assertThat(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS.enabledByDefault()).isFalse(); - assertThat(mapper.getDeserializationConfig() + assertThat(mapper.deserializationConfig() .hasDeserializationFeatures(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS.getMask())).isTrue(); }); } @Test void disableDeserializationFeature() { - this.contextRunner.withPropertyValues("spring.jackson.deserialization.fail-on-unknown-properties:false") + this.contextRunner.withPropertyValues("spring.jackson.deserialization.fail-on-null-for-primitives:false") .run((context) -> { ObjectMapper mapper = context.getBean(ObjectMapper.class); - assertThat(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES.enabledByDefault()).isTrue(); - assertThat(mapper.getDeserializationConfig() - .hasDeserializationFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES.getMask())).isFalse(); + assertThat(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES.enabledByDefault()).isTrue(); + assertThat(mapper.deserializationConfig() + .hasDeserializationFeatures(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES.getMask())) + .isFalse(); }); } @@ -218,9 +218,8 @@ class JacksonAutoConfigurationTests { ObjectMapper mapper = context.getBean(ObjectMapper.class); assertThat(MapperFeature.REQUIRE_SETTERS_FOR_GETTERS.enabledByDefault()).isFalse(); - assertThat(mapper.getSerializationConfig().isEnabled(MapperFeature.REQUIRE_SETTERS_FOR_GETTERS)) - .isTrue(); - assertThat(mapper.getDeserializationConfig().isEnabled(MapperFeature.REQUIRE_SETTERS_FOR_GETTERS)) + assertThat(mapper.serializationConfig().isEnabled(MapperFeature.REQUIRE_SETTERS_FOR_GETTERS)).isTrue(); + assertThat(mapper.deserializationConfig().isEnabled(MapperFeature.REQUIRE_SETTERS_FOR_GETTERS)) .isTrue(); }); } @@ -230,62 +229,36 @@ class JacksonAutoConfigurationTests { this.contextRunner.withPropertyValues("spring.jackson.mapper.use_annotations:false").run((context) -> { ObjectMapper mapper = context.getBean(ObjectMapper.class); assertThat(MapperFeature.USE_ANNOTATIONS.enabledByDefault()).isTrue(); - assertThat(mapper.getDeserializationConfig().isEnabled(MapperFeature.USE_ANNOTATIONS)).isFalse(); - assertThat(mapper.getSerializationConfig().isEnabled(MapperFeature.USE_ANNOTATIONS)).isFalse(); + assertThat(mapper.deserializationConfig().isEnabled(MapperFeature.USE_ANNOTATIONS)).isFalse(); + assertThat(mapper.serializationConfig().isEnabled(MapperFeature.USE_ANNOTATIONS)).isFalse(); }); } @Test - void enableParserFeature() { - this.contextRunner.withPropertyValues("spring.jackson.parser.allow_single_quotes:true").run((context) -> { - ObjectMapper mapper = context.getBean(ObjectMapper.class); - assertThat(JsonParser.Feature.ALLOW_SINGLE_QUOTES.enabledByDefault()).isFalse(); - assertThat(mapper.getFactory().isEnabled(JsonParser.Feature.ALLOW_SINGLE_QUOTES)).isTrue(); + void enableReadFeature() { + this.contextRunner.withPropertyValues("spring.jackson.read.allow_single_quotes:true").run((context) -> { + JsonMapper mapper = context.getBean(JsonMapper.class); + assertThat(JsonReadFeature.ALLOW_SINGLE_QUOTES.enabledByDefault()).isFalse(); + assertThat(mapper.isEnabled(JsonReadFeature.ALLOW_SINGLE_QUOTES)).isTrue(); }); } @Test - void disableParserFeature() { - this.contextRunner.withPropertyValues("spring.jackson.parser.auto_close_source:false").run((context) -> { - ObjectMapper mapper = context.getBean(ObjectMapper.class); - assertThat(JsonParser.Feature.AUTO_CLOSE_SOURCE.enabledByDefault()).isTrue(); - assertThat(mapper.getFactory().isEnabled(JsonParser.Feature.AUTO_CLOSE_SOURCE)).isFalse(); + void enableWriteFeature() { + this.contextRunner.withPropertyValues("spring.jackson.write.write_numbers_as_strings:true").run((context) -> { + JsonMapper mapper = context.getBean(JsonMapper.class); + JsonWriteFeature feature = JsonWriteFeature.WRITE_NUMBERS_AS_STRINGS; + assertThat(feature.enabledByDefault()).isFalse(); + assertThat(mapper.isEnabled(feature)).isTrue(); }); } @Test - void enableGeneratorFeature() { - this.contextRunner.withPropertyValues("spring.jackson.generator.strict_duplicate_detection:true") - .run((context) -> { - ObjectMapper mapper = context.getBean(ObjectMapper.class); - JsonGenerator.Feature feature = JsonGenerator.Feature.STRICT_DUPLICATE_DETECTION; - assertThat(feature.enabledByDefault()).isFalse(); - assertThat(mapper.getFactory().isEnabled(feature)).isTrue(); - }); - } - - @Test - void disableGeneratorFeature() { - this.contextRunner.withPropertyValues("spring.jackson.generator.auto_close_target:false").run((context) -> { - ObjectMapper mapper = context.getBean(ObjectMapper.class); - assertThat(JsonGenerator.Feature.AUTO_CLOSE_TARGET.enabledByDefault()).isTrue(); - assertThat(mapper.getFactory().isEnabled(JsonGenerator.Feature.AUTO_CLOSE_TARGET)).isFalse(); - }); - } - - @Test - void defaultObjectMapperBuilder() { - this.contextRunner.run((context) -> { - Jackson2ObjectMapperBuilder builder = context.getBean(Jackson2ObjectMapperBuilder.class); - ObjectMapper mapper = builder.build(); - assertThat(MapperFeature.DEFAULT_VIEW_INCLUSION.enabledByDefault()).isTrue(); - assertThat(mapper.getDeserializationConfig().isEnabled(MapperFeature.DEFAULT_VIEW_INCLUSION)).isFalse(); - assertThat(MapperFeature.DEFAULT_VIEW_INCLUSION.enabledByDefault()).isTrue(); - assertThat(mapper.getDeserializationConfig().isEnabled(MapperFeature.DEFAULT_VIEW_INCLUSION)).isFalse(); - assertThat(mapper.getSerializationConfig().isEnabled(MapperFeature.DEFAULT_VIEW_INCLUSION)).isFalse(); - assertThat(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES.enabledByDefault()).isTrue(); - assertThat(mapper.getDeserializationConfig().isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)) - .isFalse(); + void disableWriteFeature() { + this.contextRunner.withPropertyValues("spring.jackson.write.write_hex_upper_case:false").run((context) -> { + JsonMapper mapper = context.getBean(JsonMapper.class); + assertThat(JsonWriteFeature.WRITE_HEX_UPPER_CASE.enabledByDefault()).isTrue(); + assertThat(mapper.isEnabled(JsonWriteFeature.WRITE_HEX_UPPER_CASE)).isFalse(); }); } @@ -295,7 +268,7 @@ class JacksonAutoConfigurationTests { .run((context) -> { ObjectMapper mapper = context.getBean(ObjectMapper.class); assertThat(EnumFeature.WRITE_ENUMS_TO_LOWERCASE.enabledByDefault()).isFalse(); - assertThat(mapper.getSerializationConfig().isEnabled(EnumFeature.WRITE_ENUMS_TO_LOWERCASE)).isTrue(); + assertThat(mapper.serializationConfig().isEnabled(EnumFeature.WRITE_ENUMS_TO_LOWERCASE)).isTrue(); }); } @@ -305,18 +278,17 @@ class JacksonAutoConfigurationTests { .run((context) -> { ObjectMapper mapper = context.getBean(ObjectMapper.class); assertThat(JsonNodeFeature.WRITE_NULL_PROPERTIES.enabledByDefault()).isTrue(); - assertThat(mapper.getDeserializationConfig().isEnabled(JsonNodeFeature.WRITE_NULL_PROPERTIES)) - .isFalse(); + assertThat(mapper.deserializationConfig().isEnabled(JsonNodeFeature.WRITE_NULL_PROPERTIES)).isFalse(); }); } @Test - void moduleBeansAndWellKnownModulesAreRegisteredWithTheObjectMapperBuilder() { + void moduleBeansAndWellKnownModulesAreRegisteredWithTheJsonMapperBuilder() { this.contextRunner.withUserConfiguration(ModuleConfig.class).run((context) -> { - ObjectMapper objectMapper = context.getBean(Jackson2ObjectMapperBuilder.class).build(); - assertThat(context.getBean(CustomModule.class).getOwners()).contains(objectMapper); - assertThat(((DefaultSerializerProvider) objectMapper.getSerializerProviderInstance()) - .hasSerializerFor(Baz.class, null)).isTrue(); + Builder jsonMapperBuilder = context.getBean(JsonMapper.Builder.class); + JsonMapper jsonMapper = jsonMapperBuilder.build(); + assertThat(context.getBean(CustomModule.class).getOwners()).contains(jsonMapperBuilder); + assertThat(jsonMapper._serializationContext().findValueSerializer(Baz.class)).isNotNull(); }); } @@ -324,18 +296,17 @@ class JacksonAutoConfigurationTests { void customModulesRegisteredByBuilderCustomizerShouldBeRetained() { this.contextRunner.withUserConfiguration(ModuleConfig.class, CustomModuleBuilderCustomizerConfig.class) .run((context) -> { - ObjectMapper objectMapper = context.getBean(Jackson2ObjectMapperBuilder.class).build(); - assertThat(context.getBean(CustomModule.class).getOwners()).contains(objectMapper); - assertThat(objectMapper.getRegisteredModuleIds()).contains("module-A", "module-B", - CustomModule.class.getName()); + JsonMapper jsonMapper = context.getBean(JsonMapper.class); + assertThat(jsonMapper.getRegisteredModules()).extracting((module) -> module.getModuleName()) + .contains("module-A", "module-B", CustomModule.class.getName()); }); } @Test void defaultSerializationInclusion() { this.contextRunner.run((context) -> { - ObjectMapper objectMapper = context.getBean(Jackson2ObjectMapperBuilder.class).build(); - assertThat(objectMapper.getSerializationConfig().getDefaultPropertyInclusion().getValueInclusion()) + ObjectMapper objectMapper = context.getBean(JsonMapper.Builder.class).build(); + assertThat(objectMapper.serializationConfig().getDefaultPropertyInclusion().getValueInclusion()) .isEqualTo(JsonInclude.Include.USE_DEFAULTS); }); } @@ -343,8 +314,8 @@ class JacksonAutoConfigurationTests { @Test void customSerializationInclusion() { this.contextRunner.withPropertyValues("spring.jackson.default-property-inclusion:non_null").run((context) -> { - ObjectMapper objectMapper = context.getBean(Jackson2ObjectMapperBuilder.class).build(); - assertThat(objectMapper.getSerializationConfig().getDefaultPropertyInclusion().getValueInclusion()) + ObjectMapper objectMapper = context.getBean(JsonMapper.Builder.class).build(); + assertThat(objectMapper.serializationConfig().getDefaultPropertyInclusion().getValueInclusion()) .isEqualTo(JsonInclude.Include.NON_NULL); }); } @@ -353,7 +324,7 @@ class JacksonAutoConfigurationTests { void customTimeZoneFormattingADate() { this.contextRunner.withPropertyValues("spring.jackson.time-zone:GMT+10", "spring.jackson.date-format:z") .run((context) -> { - ObjectMapper objectMapper = context.getBean(Jackson2ObjectMapperBuilder.class).build(); + ObjectMapper objectMapper = context.getBean(JsonMapper.Builder.class).build(); Date date = new Date(1436966242231L); assertThat(objectMapper.writeValueAsString(date)).isEqualTo("\"GMT+10:00\""); }); @@ -383,7 +354,7 @@ class JacksonAutoConfigurationTests { void constructorDetectorWithNoStrategyUseDefault() { this.contextRunner.run((context) -> { ObjectMapper mapper = context.getBean(ObjectMapper.class); - ConstructorDetector cd = mapper.getDeserializationConfig().getConstructorDetector(); + ConstructorDetector cd = mapper.deserializationConfig().getConstructorDetector(); assertThat(cd.singleArgMode()).isEqualTo(SingleArgConstructor.HEURISTIC); assertThat(cd.requireCtorAnnotation()).isFalse(); assertThat(cd.allowJDKTypeConstructors()).isFalse(); @@ -394,7 +365,7 @@ class JacksonAutoConfigurationTests { void constructorDetectorWithDefaultStrategy() { this.contextRunner.withPropertyValues("spring.jackson.constructor-detector=default").run((context) -> { ObjectMapper mapper = context.getBean(ObjectMapper.class); - ConstructorDetector cd = mapper.getDeserializationConfig().getConstructorDetector(); + ConstructorDetector cd = mapper.deserializationConfig().getConstructorDetector(); assertThat(cd.singleArgMode()).isEqualTo(SingleArgConstructor.HEURISTIC); assertThat(cd.requireCtorAnnotation()).isFalse(); assertThat(cd.allowJDKTypeConstructors()).isFalse(); @@ -406,7 +377,7 @@ class JacksonAutoConfigurationTests { this.contextRunner.withPropertyValues("spring.jackson.constructor-detector=use-properties-based") .run((context) -> { ObjectMapper mapper = context.getBean(ObjectMapper.class); - ConstructorDetector cd = mapper.getDeserializationConfig().getConstructorDetector(); + ConstructorDetector cd = mapper.deserializationConfig().getConstructorDetector(); assertThat(cd.singleArgMode()).isEqualTo(SingleArgConstructor.PROPERTIES); assertThat(cd.requireCtorAnnotation()).isFalse(); assertThat(cd.allowJDKTypeConstructors()).isFalse(); @@ -417,7 +388,7 @@ class JacksonAutoConfigurationTests { void constructorDetectorWithUseDelegatingStrategy() { this.contextRunner.withPropertyValues("spring.jackson.constructor-detector=use-delegating").run((context) -> { ObjectMapper mapper = context.getBean(ObjectMapper.class); - ConstructorDetector cd = mapper.getDeserializationConfig().getConstructorDetector(); + ConstructorDetector cd = mapper.deserializationConfig().getConstructorDetector(); assertThat(cd.singleArgMode()).isEqualTo(SingleArgConstructor.DELEGATING); assertThat(cd.requireCtorAnnotation()).isFalse(); assertThat(cd.allowJDKTypeConstructors()).isFalse(); @@ -428,7 +399,7 @@ class JacksonAutoConfigurationTests { void constructorDetectorWithExplicitOnlyStrategy() { this.contextRunner.withPropertyValues("spring.jackson.constructor-detector=explicit-only").run((context) -> { ObjectMapper mapper = context.getBean(ObjectMapper.class); - ConstructorDetector cd = mapper.getDeserializationConfig().getConstructorDetector(); + ConstructorDetector cd = mapper.deserializationConfig().getConstructorDetector(); assertThat(cd.singleArgMode()).isEqualTo(SingleArgConstructor.REQUIRE_MODE); assertThat(cd.requireCtorAnnotation()).isFalse(); assertThat(cd.allowJDKTypeConstructors()).isFalse(); @@ -437,23 +408,13 @@ class JacksonAutoConfigurationTests { @Test void additionalJacksonBuilderCustomization() { - this.contextRunner.withUserConfiguration(ObjectMapperBuilderCustomConfig.class).run((context) -> { + this.contextRunner.withUserConfiguration(JsonMapperBuilderCustomConfig.class).run((context) -> { ObjectMapper mapper = context.getBean(ObjectMapper.class); - assertThat(mapper.getDateFormat()).isInstanceOf(MyDateFormat.class); + assertThat(mapper.deserializationConfig().getDateFormat()).isInstanceOf(MyDateFormat.class); + assertThat(mapper.serializationConfig().getDateFormat()).isInstanceOf(MyDateFormat.class); }); } - @Test - void parameterNamesModuleIsAutoConfigured() { - assertParameterNamesModuleCreatorBinding(Mode.DEFAULT, JacksonAutoConfiguration.class); - } - - @Test - void customParameterNamesModuleCanBeConfigured() { - assertParameterNamesModuleCreatorBinding(Mode.DELEGATING, ParameterNamesModuleConfig.class, - JacksonAutoConfiguration.class); - } - @Test void writeDurationAsTimestampsDefault() { this.contextRunner.run((context) -> { @@ -478,8 +439,8 @@ class JacksonAutoConfigurationTests { @Test void builderIsNotSharedAcrossMultipleInjectionPoints() { - this.contextRunner.withUserConfiguration(ObjectMapperBuilderConsumerConfig.class).run((context) -> { - ObjectMapperBuilderConsumerConfig consumer = context.getBean(ObjectMapperBuilderConsumerConfig.class); + this.contextRunner.withUserConfiguration(JsonMapperBuilderConsumerConfig.class).run((context) -> { + JsonMapperBuilderConsumerConfig consumer = context.getBean(JsonMapperBuilderConsumerConfig.class); assertThat(consumer.builderOne).isNotNull(); assertThat(consumer.builderTwo).isNotNull(); assertThat(consumer.builderOne).isNotSameAs(consumer.builderTwo); @@ -501,18 +462,6 @@ class JacksonAutoConfigurationTests { assertThat(RuntimeHintsPredicates.reflection().onType(PropertyNamingStrategies.class)).accepts(hints); } - private void assertParameterNamesModuleCreatorBinding(Mode expectedMode, Class... configClasses) { - this.contextRunner.withUserConfiguration(configClasses).run((context) -> { - DeserializationConfig deserializationConfig = context.getBean(ObjectMapper.class) - .getDeserializationConfig(); - AnnotationIntrospector annotationIntrospector = deserializationConfig.getAnnotationIntrospector() - .allIntrospectors() - .iterator() - .next(); - assertThat(annotationIntrospector).hasFieldOrPropertyWithValue("creatorBinding", expectedMode); - }); - } - static class MyDateFormat extends SimpleDateFormat { MyDateFormat() { @@ -547,14 +496,14 @@ class JacksonAutoConfigurationTests { static class DoubleModulesConfig { @Bean - Module jacksonModule() { + JacksonModule jacksonModule() { SimpleModule module = new SimpleModule(); - module.addSerializer(Foo.class, new JsonSerializer<>() { + module.addSerializer(Foo.class, new ValueSerializer<>() { @Override - public void serialize(Foo value, JsonGenerator jgen, SerializerProvider provider) throws IOException { + public void serialize(Foo value, JsonGenerator jgen, SerializationContext context) { jgen.writeStartObject(); - jgen.writeStringField("foo", "bar"); + jgen.writeStringProperty("foo", "bar"); jgen.writeEndObject(); } }); @@ -564,29 +513,17 @@ class JacksonAutoConfigurationTests { @Bean @Primary ObjectMapper objectMapper() { - ObjectMapper mapper = new ObjectMapper(); - mapper.registerModule(jacksonModule()); - return mapper; + return JsonMapper.builder().addModule(jacksonModule()).build(); } } @Configuration(proxyBeanMethods = false) - static class ParameterNamesModuleConfig { + static class JsonMapperBuilderCustomConfig { @Bean - ParameterNamesModule parameterNamesModule() { - return new ParameterNamesModule(JsonCreator.Mode.DELEGATING); - } - - } - - @Configuration(proxyBeanMethods = false) - static class ObjectMapperBuilderCustomConfig { - - @Bean - Jackson2ObjectMapperBuilderCustomizer customDateFormat() { - return (builder) -> builder.dateFormat(new MyDateFormat()); + JsonMapperBuilderCustomizer customDateFormat() { + return (builder) -> builder.defaultDateFormat(new MyDateFormat()); } } @@ -596,33 +533,33 @@ class JacksonAutoConfigurationTests { @Bean @Order(-1) - Jackson2ObjectMapperBuilderCustomizer highPrecedenceCustomizer() { - return (builder) -> builder.modulesToInstall((modules) -> modules.add(new SimpleModule("module-A"))); + JsonMapperBuilderCustomizer highPrecedenceCustomizer() { + return (builder) -> builder.addModule(new SimpleModule("module-A")); } @Bean @Order(1) - Jackson2ObjectMapperBuilderCustomizer lowPrecedenceCustomizer() { - return (builder) -> builder.modulesToInstall((modules) -> modules.add(new SimpleModule("module-B"))); + JsonMapperBuilderCustomizer lowPrecedenceCustomizer() { + return (builder) -> builder.addModule(new SimpleModule("module-B")); } } @Configuration(proxyBeanMethods = false) - static class ObjectMapperBuilderConsumerConfig { + static class JsonMapperBuilderConsumerConfig { - Jackson2ObjectMapperBuilder builderOne; + JsonMapper.Builder builderOne; - Jackson2ObjectMapperBuilder builderTwo; + JsonMapper.Builder builderTwo; @Bean - String consumerOne(Jackson2ObjectMapperBuilder builder) { + String consumerOne(JsonMapper.Builder builder) { this.builderOne = builder; return "one"; } @Bean - String consumerTwo(Jackson2ObjectMapperBuilder builder) { + String consumerTwo(JsonMapper.Builder builder) { this.builderTwo = builder; return "two"; } @@ -665,10 +602,10 @@ class JacksonAutoConfigurationTests { } @JsonComponent - static class BazSerializer extends JsonObjectSerializer { + static class BazSerializer extends ObjectValueSerializer { @Override - protected void serializeObject(Baz value, JsonGenerator jgen, SerializerProvider provider) { + protected void serializeObject(Baz value, JsonGenerator jgen, SerializationContext context) { } } @@ -679,14 +616,14 @@ class JacksonAutoConfigurationTests { static class CustomModule extends SimpleModule { - private final Set owners = new HashSet<>(); + private final Set owners = new HashSet<>(); @Override public void setupModule(SetupContext context) { this.owners.add(context.getOwner()); } - Set getOwners() { + Set getOwners() { return this.owners; } @@ -731,14 +668,14 @@ class JacksonAutoConfigurationTests { } @JsonComponent - static class CircularDependencySerializer extends JsonSerializer { + static class CircularDependencySerializer extends ValueSerializer { CircularDependencySerializer(ObjectMapper objectMapper) { } @Override - public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + public void serialize(String value, JsonGenerator gen, SerializationContext context) { } diff --git a/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/types/NameAndAge.java b/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/types/NameAndAge.java index ac52e078933..b429aba9a15 100644 --- a/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/types/NameAndAge.java +++ b/module/spring-boot-jackson/src/test/java/org/springframework/boot/jackson/types/NameAndAge.java @@ -28,7 +28,7 @@ public final class NameAndAge extends Name { private final int age; - public NameAndAge(String name, int age) { + private NameAndAge(String name, int age) { super(name); this.age = age; } @@ -67,4 +67,8 @@ public final class NameAndAge extends Name { return result; } + public static NameAndAge create(String name, int age) { + return new NameAndAge(name, age); + } + } diff --git a/module/spring-boot-jdbc/build.gradle b/module/spring-boot-jdbc/build.gradle index ef4e9facf0b..70cc1b17a01 100644 --- a/module/spring-boot-jdbc/build.gradle +++ b/module/spring-boot-jdbc/build.gradle @@ -68,7 +68,6 @@ dependencies { testImplementation(project(":test-support:spring-boot-test-support")) testImplementation(testFixtures(project(":core:spring-boot-autoconfigure"))) testImplementation(testFixtures(project(":module:spring-boot-sql"))) - testImplementation("com.fasterxml.jackson.core:jackson-databind") testImplementation("com.ibm.db2:jcc") testImplementation("com.microsoft.sqlserver:mssql-jdbc") testImplementation("io.r2dbc:r2dbc-spi") @@ -76,6 +75,7 @@ dependencies { testImplementation("org.mariadb.jdbc:mariadb-java-client") { exclude group: "org.slf4j", module: "jcl-over-slf4j" } + testImplementation("tools.jackson.core:jackson-databind") testRuntimeOnly("ch.qos.logback:logback-classic") testRuntimeOnly("com.clickhouse:clickhouse-jdbc") diff --git a/module/spring-boot-jdbc/src/test/java/org/springframework/boot/jdbc/autoconfigure/DataSourceJsonSerializationTests.java b/module/spring-boot-jdbc/src/test/java/org/springframework/boot/jdbc/autoconfigure/DataSourceJsonSerializationTests.java index 84ab5bcb950..101df5f2d76 100644 --- a/module/spring-boot-jdbc/src/test/java/org/springframework/boot/jdbc/autoconfigure/DataSourceJsonSerializationTests.java +++ b/module/spring-boot-jdbc/src/test/java/org/springframework/boot/jdbc/autoconfigure/DataSourceJsonSerializationTests.java @@ -17,25 +17,24 @@ package org.springframework.boot.jdbc.autoconfigure; import java.beans.PropertyDescriptor; -import java.io.IOException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.BeanDescription; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationConfig; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.fasterxml.jackson.databind.introspect.AnnotatedMethod; -import com.fasterxml.jackson.databind.ser.BeanPropertyWriter; -import com.fasterxml.jackson.databind.ser.BeanSerializerFactory; -import com.fasterxml.jackson.databind.ser.BeanSerializerModifier; -import com.fasterxml.jackson.databind.ser.SerializerFactory; import org.apache.tomcat.jdbc.pool.DataSource; import org.junit.jupiter.api.Test; +import tools.jackson.core.JsonGenerator; +import tools.jackson.databind.BeanDescription; +import tools.jackson.databind.SerializationConfig; +import tools.jackson.databind.SerializationContext; +import tools.jackson.databind.ValueSerializer; +import tools.jackson.databind.annotation.JsonSerialize; +import tools.jackson.databind.introspect.AnnotatedMethod; +import tools.jackson.databind.json.JsonMapper; +import tools.jackson.databind.ser.BeanPropertyWriter; +import tools.jackson.databind.ser.BeanSerializerFactory; +import tools.jackson.databind.ser.SerializerFactory; +import tools.jackson.databind.ser.ValueSerializerModifier; import org.springframework.beans.BeanUtils; import org.springframework.core.convert.ConversionService; @@ -57,8 +56,7 @@ class DataSourceJsonSerializationTests { DataSource dataSource = new DataSource(); SerializerFactory factory = BeanSerializerFactory.instance .withSerializerModifier(new GenericSerializerModifier()); - ObjectMapper mapper = new ObjectMapper(); - mapper.setSerializerFactory(factory); + JsonMapper mapper = JsonMapper.builder().serializerFactory(factory).build(); String value = mapper.writeValueAsString(dataSource); assertThat(value).contains("\"url\":"); } @@ -66,8 +64,7 @@ class DataSourceJsonSerializationTests { @Test void serializerWithMixin() throws Exception { DataSource dataSource = new DataSource(); - ObjectMapper mapper = new ObjectMapper(); - mapper.addMixIn(DataSource.class, DataSourceJson.class); + JsonMapper mapper = JsonMapper.builder().addMixIn(DataSource.class, DataSourceJson.class).build(); String value = mapper.writeValueAsString(dataSource); assertThat(value).contains("\"url\":"); assertThat(StringUtils.countOccurrencesOf(value, "\"url\"")).isOne(); @@ -78,18 +75,18 @@ class DataSourceJsonSerializationTests { } - static class TomcatDataSourceSerializer extends JsonSerializer { + static class TomcatDataSourceSerializer extends ValueSerializer { private final ConversionService conversionService = new DefaultConversionService(); @Override - public void serialize(DataSource value, JsonGenerator jgen, SerializerProvider provider) throws IOException { + public void serialize(DataSource value, JsonGenerator jgen, SerializationContext context) { jgen.writeStartObject(); for (PropertyDescriptor property : BeanUtils.getPropertyDescriptors(DataSource.class)) { Method reader = property.getReadMethod(); if (reader != null && property.getWriteMethod() != null && this.conversionService.canConvert(String.class, property.getPropertyType())) { - jgen.writeObjectField(property.getName(), ReflectionUtils.invokeMethod(reader, value)); + jgen.writePOJOProperty(property.getName(), ReflectionUtils.invokeMethod(reader, value)); } } jgen.writeEndObject(); @@ -97,17 +94,18 @@ class DataSourceJsonSerializationTests { } - static class GenericSerializerModifier extends BeanSerializerModifier { + static class GenericSerializerModifier extends ValueSerializerModifier { private final ConversionService conversionService = new DefaultConversionService(); @Override - public List changeProperties(SerializationConfig config, BeanDescription beanDesc, + public List changeProperties(SerializationConfig config, BeanDescription.Supplier beanDesc, List beanProperties) { List result = new ArrayList<>(); for (BeanPropertyWriter writer : beanProperties) { - AnnotatedMethod setter = beanDesc.findMethod("set" + StringUtils.capitalize(writer.getName()), - new Class[] { writer.getType().getRawClass() }); + AnnotatedMethod setter = beanDesc.get() + .findMethod("set" + StringUtils.capitalize(writer.getName()), + new Class[] { writer.getType().getRawClass() }); if (setter != null && this.conversionService.canConvert(String.class, writer.getType().getRawClass())) { result.add(writer); } diff --git a/module/spring-boot-micrometer-metrics/build.gradle b/module/spring-boot-micrometer-metrics/build.gradle index 965ad267f64..e0520d6257a 100644 --- a/module/spring-boot-micrometer-metrics/build.gradle +++ b/module/spring-boot-micrometer-metrics/build.gradle @@ -73,12 +73,12 @@ dependencies { testImplementation(project(":core:spring-boot-test")) testImplementation(project(":test-support:spring-boot-test-support")) - testImplementation("com.fasterxml.jackson.core:jackson-databind") testImplementation("io.micrometer:micrometer-registry-atlas") testImplementation("io.micrometer:micrometer-registry-new-relic") testImplementation("io.micrometer:micrometer-registry-prometheus") testImplementation("org.apache.logging.log4j:log4j-to-slf4j") testImplementation("org.springframework:spring-webflux") + testImplementation("tools.jackson.core:jackson-databind") testRuntimeOnly("ch.qos.logback:logback-classic") } diff --git a/module/spring-boot-rsocket/build.gradle b/module/spring-boot-rsocket/build.gradle index 263a87aedc3..f220eeffa22 100644 --- a/module/spring-boot-rsocket/build.gradle +++ b/module/spring-boot-rsocket/build.gradle @@ -36,8 +36,8 @@ dependencies { optional(project(":core:spring-boot-autoconfigure")) optional(project(":module:spring-boot-jackson")) optional(project(":module:spring-boot-reactor-netty")) - optional("com.fasterxml.jackson.dataformat:jackson-dataformat-cbor") optional("io.rsocket:rsocket-transport-netty") + optional("tools.jackson.dataformat:jackson-dataformat-cbor") testImplementation(project(":core:spring-boot-test")) testImplementation(project(":test-support:spring-boot-test-support")) diff --git a/module/spring-boot-rsocket/src/main/java/org/springframework/boot/rsocket/autoconfigure/RSocketStrategiesAutoConfiguration.java b/module/spring-boot-rsocket/src/main/java/org/springframework/boot/rsocket/autoconfigure/RSocketStrategiesAutoConfiguration.java index c5a1cc2784a..63532f6e90a 100644 --- a/module/spring-boot-rsocket/src/main/java/org/springframework/boot/rsocket/autoconfigure/RSocketStrategiesAutoConfiguration.java +++ b/module/spring-boot-rsocket/src/main/java/org/springframework/boot/rsocket/autoconfigure/RSocketStrategiesAutoConfiguration.java @@ -16,9 +16,11 @@ package org.springframework.boot.rsocket.autoconfigure; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.dataformat.cbor.CBORFactory; import io.netty.buffer.PooledByteBufAllocator; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; +import tools.jackson.dataformat.cbor.CBORFactory; +import tools.jackson.dataformat.cbor.CBORMapper; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; @@ -31,6 +33,10 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import org.springframework.http.MediaType; +import org.springframework.http.codec.cbor.JacksonCborDecoder; +import org.springframework.http.codec.cbor.JacksonCborEncoder; +import org.springframework.http.codec.json.JacksonJsonDecoder; +import org.springframework.http.codec.json.JacksonJsonEncoder; import org.springframework.messaging.rsocket.RSocketStrategies; import org.springframework.util.ClassUtils; import org.springframework.web.util.pattern.PathPatternRouteMatcher; @@ -59,23 +65,19 @@ public final class RSocketStrategiesAutoConfiguration { } @Configuration(proxyBeanMethods = false) + @ConditionalOnBean(JsonMapper.Builder.class) @ConditionalOnClass({ ObjectMapper.class, CBORFactory.class }) - @SuppressWarnings({ "removal", "deprecation" }) protected static class JacksonCborStrategyConfiguration { private static final MediaType[] SUPPORTED_TYPES = { MediaType.APPLICATION_CBOR }; @Bean @Order(0) - @ConditionalOnBean(org.springframework.http.converter.json.Jackson2ObjectMapperBuilder.class) - RSocketStrategiesCustomizer jacksonCborRSocketStrategyCustomizer( - org.springframework.http.converter.json.Jackson2ObjectMapperBuilder builder) { + RSocketStrategiesCustomizer jacksonCborRSocketStrategyCustomizer(JsonMapper.Builder builder) { return (strategy) -> { - ObjectMapper objectMapper = builder.createXmlMapper(false).factory(new CBORFactory()).build(); - strategy.decoder( - new org.springframework.http.codec.cbor.Jackson2CborDecoder(objectMapper, SUPPORTED_TYPES)); - strategy.encoder( - new org.springframework.http.codec.cbor.Jackson2CborEncoder(objectMapper, SUPPORTED_TYPES)); + CBORMapper cborMapper = CBORMapper.builder().baseSettings(builder.baseSettings()).build(); + strategy.decoder(new JacksonCborDecoder(cborMapper, SUPPORTED_TYPES)); + strategy.encoder(new JacksonCborEncoder(cborMapper, SUPPORTED_TYPES)); }; } @@ -83,7 +85,6 @@ public final class RSocketStrategiesAutoConfiguration { @Configuration(proxyBeanMethods = false) @ConditionalOnClass(ObjectMapper.class) - @SuppressWarnings({ "removal", "deprecation" }) protected static class JacksonJsonStrategyConfiguration { private static final MediaType[] SUPPORTED_TYPES = { MediaType.APPLICATION_JSON, @@ -91,13 +92,11 @@ public final class RSocketStrategiesAutoConfiguration { @Bean @Order(1) - @ConditionalOnBean(ObjectMapper.class) - RSocketStrategiesCustomizer jacksonJsonRSocketStrategyCustomizer(ObjectMapper objectMapper) { + @ConditionalOnBean(JsonMapper.class) + RSocketStrategiesCustomizer jacksonJsonRSocketStrategyCustomizer(JsonMapper jsonMapper) { return (strategy) -> { - strategy.decoder( - new org.springframework.http.codec.json.Jackson2JsonDecoder(objectMapper, SUPPORTED_TYPES)); - strategy.encoder( - new org.springframework.http.codec.json.Jackson2JsonEncoder(objectMapper, SUPPORTED_TYPES)); + strategy.decoder(new JacksonJsonDecoder(jsonMapper, SUPPORTED_TYPES)); + strategy.encoder(new JacksonJsonEncoder(jsonMapper, SUPPORTED_TYPES)); }; } diff --git a/module/spring-boot-rsocket/src/test/java/org/springframework/boot/rsocket/autoconfigure/RSocketStrategiesAutoConfigurationTests.java b/module/spring-boot-rsocket/src/test/java/org/springframework/boot/rsocket/autoconfigure/RSocketStrategiesAutoConfigurationTests.java index 8fce7072dd0..626bf162d07 100644 --- a/module/spring-boot-rsocket/src/test/java/org/springframework/boot/rsocket/autoconfigure/RSocketStrategiesAutoConfigurationTests.java +++ b/module/spring-boot-rsocket/src/test/java/org/springframework/boot/rsocket/autoconfigure/RSocketStrategiesAutoConfigurationTests.java @@ -16,8 +16,8 @@ package org.springframework.boot.rsocket.autoconfigure; -import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; +import tools.jackson.databind.json.JsonMapper; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.rsocket.messaging.RSocketStrategiesCustomizer; @@ -28,6 +28,10 @@ import org.springframework.core.codec.CharSequenceEncoder; import org.springframework.core.codec.Decoder; import org.springframework.core.codec.Encoder; import org.springframework.core.codec.StringDecoder; +import org.springframework.http.codec.cbor.JacksonCborDecoder; +import org.springframework.http.codec.cbor.JacksonCborEncoder; +import org.springframework.http.codec.json.JacksonJsonDecoder; +import org.springframework.http.codec.json.JacksonJsonEncoder; import org.springframework.messaging.rsocket.RSocketStrategies; import org.springframework.web.util.pattern.PathPatternRouteMatcher; @@ -41,24 +45,20 @@ import static org.mockito.Mockito.mock; */ class RSocketStrategiesAutoConfigurationTests { - @SuppressWarnings("removal") private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(RSocketStrategiesAutoConfiguration.class)) - .withBean(org.springframework.http.converter.json.Jackson2ObjectMapperBuilder.class) - .withBean(ObjectMapper.class); + .withBean(JsonMapper.Builder.class, () -> JsonMapper.builder()) + .withBean(JsonMapper.class, () -> JsonMapper.builder().build()); @Test - @SuppressWarnings("removal") void shouldCreateDefaultBeans() { this.contextRunner.run((context) -> { assertThat(context).getBeans(RSocketStrategies.class).hasSize(1); RSocketStrategies strategies = context.getBean(RSocketStrategies.class); - assertThat(strategies.decoders()) - .hasAtLeastOneElementOfType(org.springframework.http.codec.cbor.Jackson2CborDecoder.class) - .hasAtLeastOneElementOfType(org.springframework.http.codec.json.Jackson2JsonDecoder.class); - assertThat(strategies.encoders()) - .hasAtLeastOneElementOfType(org.springframework.http.codec.cbor.Jackson2CborEncoder.class) - .hasAtLeastOneElementOfType(org.springframework.http.codec.json.Jackson2JsonEncoder.class); + assertThat(strategies.decoders()).hasAtLeastOneElementOfType(JacksonCborDecoder.class) + .hasAtLeastOneElementOfType(JacksonJsonDecoder.class); + assertThat(strategies.encoders()).hasAtLeastOneElementOfType(JacksonCborEncoder.class) + .hasAtLeastOneElementOfType(JacksonJsonEncoder.class); assertThat(strategies.routeMatcher()).isInstanceOf(PathPatternRouteMatcher.class); }); } diff --git a/module/spring-boot-rsocket/src/test/java/org/springframework/boot/rsocket/autoconfigure/RSocketWebSocketNettyRouteProviderTests.java b/module/spring-boot-rsocket/src/test/java/org/springframework/boot/rsocket/autoconfigure/RSocketWebSocketNettyRouteProviderTests.java index ece69aaac03..d47fa008f1f 100644 --- a/module/spring-boot-rsocket/src/test/java/org/springframework/boot/rsocket/autoconfigure/RSocketWebSocketNettyRouteProviderTests.java +++ b/module/spring-boot-rsocket/src/test/java/org/springframework/boot/rsocket/autoconfigure/RSocketWebSocketNettyRouteProviderTests.java @@ -19,8 +19,8 @@ package org.springframework.boot.rsocket.autoconfigure; import java.net.URI; import java.time.Duration; -import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; +import tools.jackson.databind.json.JsonMapper; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; @@ -121,14 +121,13 @@ class RSocketWebSocketNettyRouteProviderTests { } @Bean - ObjectMapper objectMapper() { - return new ObjectMapper(); + JsonMapper jsonMapper() { + return JsonMapper.builder().build(); } @Bean - @SuppressWarnings({ "removal", "deprecation" }) - org.springframework.http.converter.json.Jackson2ObjectMapperBuilder objectMapperBuilder() { - return new org.springframework.http.converter.json.Jackson2ObjectMapperBuilder(); + JsonMapper.Builder jsonMapperBuilder() { + return JsonMapper.builder(); } } diff --git a/module/spring-boot-security-oauth2-client/build.gradle b/module/spring-boot-security-oauth2-client/build.gradle index 8772de8c983..2c3979ef1c0 100644 --- a/module/spring-boot-security-oauth2-client/build.gradle +++ b/module/spring-boot-security-oauth2-client/build.gradle @@ -37,8 +37,8 @@ dependencies { testImplementation(project(":module:spring-boot-tomcat")) testImplementation(project(":test-support:spring-boot-test-support")) testImplementation(project(":module:spring-boot-webmvc")) - testImplementation("com.fasterxml.jackson.core:jackson-databind") testImplementation("com.squareup.okhttp3:mockwebserver") + testImplementation("tools.jackson.core:jackson-databind") testRuntimeOnly("ch.qos.logback:logback-classic") testRuntimeOnly("org.springframework:spring-webflux") diff --git a/module/spring-boot-security-oauth2-client/src/test/java/org/springframework/boot/security/oauth2/client/autoconfigure/OAuth2ClientPropertiesMapperTests.java b/module/spring-boot-security-oauth2-client/src/test/java/org/springframework/boot/security/oauth2/client/autoconfigure/OAuth2ClientPropertiesMapperTests.java index 6f0edc9f80a..af1eb9e163f 100644 --- a/module/spring-boot-security-oauth2-client/src/test/java/org/springframework/boot/security/oauth2/client/autoconfigure/OAuth2ClientPropertiesMapperTests.java +++ b/module/spring-boot-security-oauth2-client/src/test/java/org/springframework/boot/security/oauth2/client/autoconfigure/OAuth2ClientPropertiesMapperTests.java @@ -20,12 +20,11 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; +import tools.jackson.databind.ObjectMapper; import org.springframework.boot.security.oauth2.client.autoconfigure.OAuth2ClientProperties.Provider; import org.springframework.boot.security.oauth2.client.autoconfigure.OAuth2ClientProperties.Registration; @@ -320,14 +319,14 @@ class OAuth2ClientPropertiesMapperTests { assertThat(this.server.getRequestCount()).isEqualTo(numberOfRequests); } - private void setupMockResponse(String issuer) throws JsonProcessingException { + private void setupMockResponse(String issuer) { MockResponse mockResponse = new MockResponse().setResponseCode(HttpStatus.OK.value()) .setBody(new ObjectMapper().writeValueAsString(getResponse(issuer))) .setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); this.server.enqueue(mockResponse); } - private void setupMockResponsesWithErrors(String issuer, int errorResponseCount) throws JsonProcessingException { + private void setupMockResponsesWithErrors(String issuer, int errorResponseCount) { for (int i = 0; i < errorResponseCount; i++) { MockResponse emptyResponse = new MockResponse().setResponseCode(HttpStatus.NOT_FOUND.value()); this.server.enqueue(emptyResponse); diff --git a/module/spring-boot-security-oauth2-resource-server/build.gradle b/module/spring-boot-security-oauth2-resource-server/build.gradle index 917bb22d976..55968bbc103 100644 --- a/module/spring-boot-security-oauth2-resource-server/build.gradle +++ b/module/spring-boot-security-oauth2-resource-server/build.gradle @@ -37,10 +37,10 @@ dependencies { testImplementation(project(":core:spring-boot-test")) testImplementation(project(":module:spring-boot-actuator-autoconfigure")) - testImplementation(project(":test-support:spring-boot-test-support")) - testImplementation(project(":module:spring-boot-webmvc")) + testImplementation(project(":module:spring-boot-jackson")) testImplementation(project(":module:spring-boot-webflux")) - testImplementation("com.fasterxml.jackson.core:jackson-databind") + testImplementation(project(":module:spring-boot-webmvc")) + testImplementation(project(":test-support:spring-boot-test-support")) testImplementation("com.squareup.okhttp3:mockwebserver") testRuntimeOnly("ch.qos.logback:logback-classic") diff --git a/module/spring-boot-security-oauth2-resource-server/src/test/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/reactive/ReactiveOAuth2ResourceServerAutoConfigurationTests.java b/module/spring-boot-security-oauth2-resource-server/src/test/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/reactive/ReactiveOAuth2ResourceServerAutoConfigurationTests.java index 37297a267b4..a02896a214b 100644 --- a/module/spring-boot-security-oauth2-resource-server/src/test/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/reactive/ReactiveOAuth2ResourceServerAutoConfigurationTests.java +++ b/module/spring-boot-security-oauth2-resource-server/src/test/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/reactive/ReactiveOAuth2ResourceServerAutoConfigurationTests.java @@ -34,8 +34,6 @@ import java.util.Map; import java.util.function.Consumer; import java.util.stream.Stream; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import com.nimbusds.jose.JWSAlgorithm; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; @@ -47,6 +45,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ArgumentsSource; import org.mockito.InOrder; import reactor.core.publisher.Mono; +import tools.jackson.databind.ObjectMapper; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.security.autoconfigure.actuate.reactive.ReactiveManagementWebSecurityAutoConfiguration; @@ -757,7 +756,7 @@ class ReactiveOAuth2ResourceServerAutoConfigurationTests { return issuer; } - private void setupMockResponse(String issuer) throws JsonProcessingException { + private void setupMockResponse(String issuer) { MockResponse mockResponse = new MockResponse().setResponseCode(HttpStatus.OK.value()) .setBody(new ObjectMapper().writeValueAsString(getResponse(issuer))) .setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); @@ -766,7 +765,7 @@ class ReactiveOAuth2ResourceServerAutoConfigurationTests { new MockResponse().setResponseCode(200).setHeader("Content-Type", "application/json").setBody(JWK_SET)); } - private void setupMockResponsesWithErrors(String issuer, int errorResponseCount) throws JsonProcessingException { + private void setupMockResponsesWithErrors(String issuer, int errorResponseCount) { for (int i = 0; i < errorResponseCount; i++) { MockResponse emptyResponse = new MockResponse().setResponseCode(HttpStatus.NOT_FOUND.value()); this.server.enqueue(emptyResponse); diff --git a/module/spring-boot-security-oauth2-resource-server/src/test/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/servlet/OAuth2ResourceServerAutoConfigurationTests.java b/module/spring-boot-security-oauth2-resource-server/src/test/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/servlet/OAuth2ResourceServerAutoConfigurationTests.java index 089544745c8..08021f4afb0 100644 --- a/module/spring-boot-security-oauth2-resource-server/src/test/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/servlet/OAuth2ResourceServerAutoConfigurationTests.java +++ b/module/spring-boot-security-oauth2-resource-server/src/test/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/servlet/OAuth2ResourceServerAutoConfigurationTests.java @@ -32,8 +32,6 @@ import java.util.Map; import java.util.function.Consumer; import java.util.function.Supplier; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import com.nimbusds.jose.JWSAlgorithm; import jakarta.servlet.Filter; import okhttp3.mockwebserver.MockResponse; @@ -45,6 +43,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ArgumentsSource; import org.mockito.InOrder; +import tools.jackson.databind.ObjectMapper; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.security.autoconfigure.actuate.servlet.ManagementWebSecurityAutoConfiguration; @@ -748,7 +747,7 @@ class OAuth2ResourceServerAutoConfigurationTests { return issuer; } - private void setupMockResponse(String issuer) throws JsonProcessingException { + private void setupMockResponse(String issuer) { MockResponse mockResponse = new MockResponse().setResponseCode(HttpStatus.OK.value()) .setBody(new ObjectMapper().writeValueAsString(getResponse(issuer))) .setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); @@ -757,7 +756,7 @@ class OAuth2ResourceServerAutoConfigurationTests { new MockResponse().setResponseCode(200).setHeader("Content-Type", "application/json").setBody(JWK_SET)); } - private void setupMockResponsesWithErrors(String issuer, int errorResponseCount) throws JsonProcessingException { + private void setupMockResponsesWithErrors(String issuer, int errorResponseCount) { for (int i = 0; i < errorResponseCount; i++) { MockResponse emptyResponse = new MockResponse().setResponseCode(HttpStatus.NOT_FOUND.value()); this.server.enqueue(emptyResponse); diff --git a/module/spring-boot-security-saml2/build.gradle b/module/spring-boot-security-saml2/build.gradle index ad2f8542c88..df03af040fe 100644 --- a/module/spring-boot-security-saml2/build.gradle +++ b/module/spring-boot-security-saml2/build.gradle @@ -36,9 +36,9 @@ dependencies { testImplementation(project(":module:spring-boot-actuator-autoconfigure")) testImplementation(project(":test-support:spring-boot-test-support")) testImplementation(project(":module:spring-boot-webmvc")) - testImplementation("com.fasterxml.jackson.core:jackson-databind") testImplementation("com.squareup.okhttp3:mockwebserver") testImplementation("jakarta.servlet:jakarta.servlet-api") + testImplementation("tools.jackson.core:jackson-databind") testRuntimeOnly("ch.qos.logback:logback-classic") } diff --git a/module/spring-boot-session/build.gradle b/module/spring-boot-session/build.gradle index 615dac3c144..10899e1965b 100644 --- a/module/spring-boot-session/build.gradle +++ b/module/spring-boot-session/build.gradle @@ -55,5 +55,4 @@ dependencies { testImplementation("io.projectreactor:reactor-test") testRuntimeOnly("ch.qos.logback:logback-classic") - testRuntimeOnly("com.fasterxml.jackson.datatype:jackson-datatype-jsr310") } diff --git a/module/spring-boot-test-autoconfigure/build.gradle b/module/spring-boot-test-autoconfigure/build.gradle index d769c99fc4c..4c6caef2b96 100644 --- a/module/spring-boot-test-autoconfigure/build.gradle +++ b/module/spring-boot-test-autoconfigure/build.gradle @@ -79,7 +79,6 @@ dependencies { optional("jakarta.persistence:jakarta.persistence-api") optional("jakarta.servlet:jakarta.servlet-api") optional("jakarta.transaction:jakarta.transaction-api") - optional("com.fasterxml.jackson.core:jackson-databind") optional("com.google.code.gson:gson") optional("com.jayway.jsonpath:json-path") optional("com.sun.xml.messaging.saaj:saaj-impl") diff --git a/module/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/tester/GraphQlTesterAutoConfiguration.java b/module/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/tester/GraphQlTesterAutoConfiguration.java index 42b3ae099b5..1bb6c0de349 100644 --- a/module/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/tester/GraphQlTesterAutoConfiguration.java +++ b/module/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/graphql/tester/GraphQlTesterAutoConfiguration.java @@ -16,8 +16,8 @@ package org.springframework.boot.test.autoconfigure.graphql.tester; -import com.fasterxml.jackson.databind.ObjectMapper; import graphql.GraphQL; +import tools.jackson.databind.json.JsonMapper; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; @@ -30,6 +30,8 @@ import org.springframework.graphql.ExecutionGraphQlService; import org.springframework.graphql.test.tester.ExecutionGraphQlServiceTester; import org.springframework.graphql.test.tester.GraphQlTester; import org.springframework.http.MediaType; +import org.springframework.http.codec.json.JacksonJsonDecoder; +import org.springframework.http.codec.json.JacksonJsonEncoder; /** * Auto-configuration for {@link GraphQlTester}. @@ -46,15 +48,13 @@ public final class GraphQlTesterAutoConfiguration { @Bean @ConditionalOnBean(ExecutionGraphQlService.class) @ConditionalOnMissingBean - @SuppressWarnings({ "removal", "deprecation" }) ExecutionGraphQlServiceTester graphQlTester(ExecutionGraphQlService graphQlService, - ObjectProvider objectMapperProvider) { + ObjectProvider jsonMapperProvider) { ExecutionGraphQlServiceTester.Builder builder = ExecutionGraphQlServiceTester.builder(graphQlService); - objectMapperProvider.ifAvailable((objectMapper) -> { - builder.encoder(new org.springframework.http.codec.json.Jackson2JsonEncoder(objectMapper, - MediaType.APPLICATION_GRAPHQL_RESPONSE, MediaType.APPLICATION_JSON, APPLICATION_GRAPHQL)); - builder.decoder(new org.springframework.http.codec.json.Jackson2JsonDecoder(objectMapper, - MediaType.APPLICATION_JSON)); + jsonMapperProvider.ifAvailable((objectMapper) -> { + builder.encoder(new JacksonJsonEncoder(objectMapper, MediaType.APPLICATION_GRAPHQL_RESPONSE, + MediaType.APPLICATION_JSON, APPLICATION_GRAPHQL)); + builder.decoder(new JacksonJsonDecoder(objectMapper, MediaType.APPLICATION_JSON)); }); return builder.build(); } diff --git a/module/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/json/JsonTestersAutoConfiguration.java b/module/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/json/JsonTestersAutoConfiguration.java index ded98aa1ff6..d6e89b8029e 100644 --- a/module/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/json/JsonTestersAutoConfiguration.java +++ b/module/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/json/JsonTestersAutoConfiguration.java @@ -20,10 +20,10 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; -import com.fasterxml.jackson.databind.ObjectMapper; import com.google.gson.Gson; import jakarta.json.bind.Jsonb; import org.jspecify.annotations.Nullable; +import tools.jackson.databind.json.JsonMapper; import org.springframework.aot.hint.ExecutableMode; import org.springframework.aot.hint.MemberCategory; @@ -82,14 +82,14 @@ public final class JsonTestersAutoConfiguration { } @Configuration(proxyBeanMethods = false) - @ConditionalOnClass(ObjectMapper.class) + @ConditionalOnClass(JsonMapper.class) static class JacksonJsonTestersConfiguration { @Bean @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) - @ConditionalOnBean(ObjectMapper.class) + @ConditionalOnBean(JsonMapper.class) @ImportRuntimeHints(JacksonTesterRuntimeHints.class) - FactoryBean> jacksonTesterFactoryBean(ObjectMapper mapper) { + FactoryBean> jacksonTesterFactoryBean(JsonMapper mapper) { return new JsonTesterFactoryBean<>(JacksonTester.class, mapper); } diff --git a/module/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/json/app/ExampleJsonComponent.java b/module/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/json/app/ExampleJsonComponent.java index 874882d9ab6..bea9d6585e9 100644 --- a/module/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/json/app/ExampleJsonComponent.java +++ b/module/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/json/app/ExampleJsonComponent.java @@ -16,20 +16,18 @@ package org.springframework.boot.test.autoconfigure.json.app; -import java.io.IOException; import java.util.Date; import java.util.UUID; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.ObjectCodec; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.SerializerProvider; +import tools.jackson.core.JsonGenerator; +import tools.jackson.core.JsonParser; +import tools.jackson.databind.DeserializationContext; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.SerializationContext; import org.springframework.boot.jackson.JsonComponent; -import org.springframework.boot.jackson.JsonObjectDeserializer; -import org.springframework.boot.jackson.JsonObjectSerializer; +import org.springframework.boot.jackson.ObjectValueDeserializer; +import org.springframework.boot.jackson.ObjectValueSerializer; import org.springframework.boot.test.autoconfigure.json.JsonTest; /** @@ -41,23 +39,22 @@ import org.springframework.boot.test.autoconfigure.json.JsonTest; @JsonComponent public class ExampleJsonComponent { - static class Serializer extends JsonObjectSerializer { + static class Serializer extends ObjectValueSerializer { @Override - protected void serializeObject(ExampleCustomObject value, JsonGenerator jgen, SerializerProvider provider) - throws IOException { - jgen.writeStringField("value", value.value()); - jgen.writeNumberField("date", value.date().getTime()); - jgen.writeStringField("uuid", value.uuid().toString()); + protected void serializeObject(ExampleCustomObject value, JsonGenerator jgen, SerializationContext context) { + jgen.writeStringProperty("value", value.value()); + jgen.writeNumberProperty("date", value.date().getTime()); + jgen.writeStringProperty("uuid", value.uuid().toString()); } } - static class Deserializer extends JsonObjectDeserializer { + static class Deserializer extends ObjectValueDeserializer { @Override protected ExampleCustomObject deserializeObject(JsonParser jsonParser, DeserializationContext context, - ObjectCodec codec, JsonNode tree) throws IOException { + JsonNode tree) { String value = nullSafeValue(tree.get("value"), String.class); Date date = nullSafeValue(tree.get("date"), Long.class, Date::new); UUID uuid = nullSafeValue(tree.get("uuid"), String.class, UUID::fromString); diff --git a/module/spring-boot-webflux/build.gradle b/module/spring-boot-webflux/build.gradle index 2d71de57ec3..c038068f92e 100644 --- a/module/spring-boot-webflux/build.gradle +++ b/module/spring-boot-webflux/build.gradle @@ -37,8 +37,8 @@ dependencies { optional(project(":module:spring-boot-health")) optional(project(":module:spring-boot-micrometer-metrics")) optional(project(":module:spring-boot-micrometer-observation")) + optional(project(":module:spring-boot-jackson")) optional(project(":module:spring-boot-validation")) - optional("com.fasterxml.jackson.core:jackson-databind") optional("org.springframework.security:spring-security-core") testFixturesApi(testFixtures(project(":module:spring-boot-actuator"))) @@ -57,6 +57,5 @@ dependencies { testImplementation("org.aspectj:aspectjweaver") testRuntimeOnly("ch.qos.logback:logback-classic") - testRuntimeOnly("com.fasterxml.jackson.core:jackson-databind") testRuntimeOnly("jakarta.servlet:jakarta.servlet-api") } diff --git a/module/spring-boot-webflux/src/main/java/org/springframework/boot/webflux/autoconfigure/actuate/web/WebFluxEndpointManagementContextConfiguration.java b/module/spring-boot-webflux/src/main/java/org/springframework/boot/webflux/autoconfigure/actuate/web/WebFluxEndpointManagementContextConfiguration.java index 263dacf2cc9..14f8fe21e7c 100644 --- a/module/spring-boot-webflux/src/main/java/org/springframework/boot/webflux/autoconfigure/actuate/web/WebFluxEndpointManagementContextConfiguration.java +++ b/module/spring-boot-webflux/src/main/java/org/springframework/boot/webflux/autoconfigure/actuate/web/WebFluxEndpointManagementContextConfiguration.java @@ -24,7 +24,7 @@ import java.util.List; import java.util.Map; import java.util.function.Supplier; -import com.fasterxml.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; import org.springframework.beans.BeansException; import org.springframework.beans.factory.ObjectProvider; @@ -41,7 +41,7 @@ import org.springframework.boot.actuate.endpoint.EndpointAccessResolver; import org.springframework.boot.actuate.endpoint.ExposableEndpoint; import org.springframework.boot.actuate.endpoint.OperationResponseBody; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; -import org.springframework.boot.actuate.endpoint.jackson.EndpointObjectMapper; +import org.springframework.boot.actuate.endpoint.jackson.EndpointJsonMapper; import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver; import org.springframework.boot.actuate.endpoint.web.EndpointMapping; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; @@ -66,6 +66,7 @@ import org.springframework.http.MediaType; import org.springframework.http.codec.EncoderHttpMessageWriter; import org.springframework.http.codec.HttpMessageWriter; import org.springframework.http.codec.ServerCodecConfigurer; +import org.springframework.http.codec.json.JacksonJsonEncoder; import org.springframework.http.server.reactive.HttpHandler; import org.springframework.util.MimeType; import org.springframework.util.StringUtils; @@ -141,29 +142,28 @@ public class WebFluxEndpointManagementContextConfiguration { } @Bean - @ConditionalOnBean(EndpointObjectMapper.class) + @ConditionalOnBean(EndpointJsonMapper.class) @Role(BeanDefinition.ROLE_INFRASTRUCTURE) - static ServerCodecConfigurerEndpointObjectMapperBeanPostProcessor serverCodecConfigurerEndpointObjectMapperBeanPostProcessor( - ObjectProvider endpointObjectMapper) { - return new ServerCodecConfigurerEndpointObjectMapperBeanPostProcessor( - SingletonSupplier.of(endpointObjectMapper::getObject)); + static ServerCodecConfigurerEndpointJsonMapperBeanPostProcessor serverCodecConfigurerEndpointJsonMapperBeanPostProcessor( + ObjectProvider endpointJsonMapper) { + return new ServerCodecConfigurerEndpointJsonMapperBeanPostProcessor( + SingletonSupplier.of(endpointJsonMapper::getObject)); } /** - * {@link BeanPostProcessor} to apply {@link EndpointObjectMapper} for + * {@link BeanPostProcessor} to apply {@link EndpointJsonMapper} for * {@link OperationResponseBody} to * {@link org.springframework.http.codec.json.Jackson2JsonEncoder} instances. */ - static class ServerCodecConfigurerEndpointObjectMapperBeanPostProcessor implements BeanPostProcessor { + static class ServerCodecConfigurerEndpointJsonMapperBeanPostProcessor implements BeanPostProcessor { private static final List MEDIA_TYPES = Collections .unmodifiableList(Arrays.asList(MediaType.APPLICATION_JSON, new MediaType("application", "*+json"))); - private final Supplier endpointObjectMapper; + private final Supplier endpointJsonMapper; - ServerCodecConfigurerEndpointObjectMapperBeanPostProcessor( - Supplier endpointObjectMapper) { - this.endpointObjectMapper = endpointObjectMapper; + ServerCodecConfigurerEndpointJsonMapperBeanPostProcessor(Supplier endpointJsonMapper) { + this.endpointJsonMapper = endpointJsonMapper; } @Override @@ -182,19 +182,17 @@ public class WebFluxEndpointManagementContextConfiguration { } } - @SuppressWarnings({ "removal", "deprecation" }) private void process(Encoder encoder) { - if (encoder instanceof org.springframework.http.codec.json.Jackson2JsonEncoder jackson2JsonEncoder) { - this.endpointObjectMapper.get() + if (encoder instanceof JacksonJsonEncoder jacksonJsonEncoder) { + this.endpointJsonMapper.get() .getSupportedTypes() - .forEach((type) -> jackson2JsonEncoder.registerObjectMappersForType(type, - this::registerForAllMimeTypes)); + .forEach((type) -> jacksonJsonEncoder.registerMappersForType(type, this::registerForAllMimeTypes)); } } - private void registerForAllMimeTypes(Map registrar) { - ObjectMapper objectMapper = this.endpointObjectMapper.get().get(); - MEDIA_TYPES.forEach((mimeType) -> registrar.put(mimeType, objectMapper)); + private void registerForAllMimeTypes(Map registrar) { + JsonMapper jsonMapper = this.endpointJsonMapper.get().get(); + MEDIA_TYPES.forEach((mimeType) -> registrar.put(mimeType, jsonMapper)); } } diff --git a/module/spring-boot-webflux/src/test/java/org/springframework/boot/webflux/autoconfigure/actuate/web/WebFluxEndpointIntegrationTests.java b/module/spring-boot-webflux/src/test/java/org/springframework/boot/webflux/autoconfigure/actuate/web/WebFluxEndpointIntegrationTests.java index 105b4ca9a09..32f43fc8b1b 100644 --- a/module/spring-boot-webflux/src/test/java/org/springframework/boot/webflux/autoconfigure/actuate/web/WebFluxEndpointIntegrationTests.java +++ b/module/spring-boot-webflux/src/test/java/org/springframework/boot/webflux/autoconfigure/actuate/web/WebFluxEndpointIntegrationTests.java @@ -16,22 +16,21 @@ package org.springframework.boot.webflux.autoconfigure.actuate.web; -import java.io.IOException; import java.nio.charset.StandardCharsets; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.jsontype.TypeSerializer; -import com.fasterxml.jackson.databind.module.SimpleModule; -import com.fasterxml.jackson.databind.ser.std.StdScalarSerializer; import org.junit.jupiter.api.Test; +import tools.jackson.core.JsonGenerator; +import tools.jackson.databind.SerializationContext; +import tools.jackson.databind.json.JsonMapper; +import tools.jackson.databind.jsontype.TypeSerializer; +import tools.jackson.databind.module.SimpleModule; +import tools.jackson.databind.ser.std.StdScalarSerializer; import org.springframework.boot.actuate.autoconfigure.beans.BeansEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration; -import org.springframework.boot.actuate.endpoint.jackson.EndpointObjectMapper; +import org.springframework.boot.actuate.endpoint.jackson.EndpointJsonMapper; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.http.codec.autoconfigure.CodecsAutoConfiguration; import org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration; @@ -87,8 +86,8 @@ class WebFluxEndpointIntegrationTests { } @Test - void endpointObjectMapperCanBeApplied() { - this.contextRunner.withUserConfiguration(EndpointObjectMapperConfiguration.class) + void endpointJsonMapperCanBeApplied() { + this.contextRunner.withUserConfiguration(EndpointJsonMapperConfiguration.class) .withPropertyValues("management.endpoints.web.exposure.include:*") .run((context) -> { WebTestClient client = createWebTestClient(context); @@ -140,17 +139,14 @@ class WebFluxEndpointIntegrationTests { } @Configuration - @SuppressWarnings({ "deprecation", "removal" }) - static class EndpointObjectMapperConfiguration { + static class EndpointJsonMapperConfiguration { @Bean - EndpointObjectMapper endpointObjectMapper() { + EndpointJsonMapper endpointJsonMapper() { SimpleModule module = new SimpleModule(); module.addSerializer(String.class, new ReverseStringSerializer()); - ObjectMapper objectMapper = org.springframework.http.converter.json.Jackson2ObjectMapperBuilder.json() - .modules(module) - .build(); - return () -> objectMapper; + JsonMapper jsonMapper = JsonMapper.builder().addModule(module).build(); + return () -> jsonMapper; } static class ReverseStringSerializer extends StdScalarSerializer { @@ -160,22 +156,22 @@ class WebFluxEndpointIntegrationTests { } @Override - public boolean isEmpty(SerializerProvider prov, Object value) { + public boolean isEmpty(SerializationContext context, Object value) { return ((String) value).isEmpty(); } @Override - public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException { + public void serialize(Object value, JsonGenerator gen, SerializationContext context) { serialize(value, gen); } @Override - public final void serializeWithType(Object value, JsonGenerator gen, SerializerProvider provider, - TypeSerializer typeSer) throws IOException { + public final void serializeWithType(Object value, JsonGenerator gen, SerializationContext context, + TypeSerializer typeSer) { serialize(value, gen); } - private void serialize(Object value, JsonGenerator gen) throws IOException { + private void serialize(Object value, JsonGenerator gen) { StringBuilder builder = new StringBuilder((String) value); gen.writeString(builder.reverse().toString()); } diff --git a/module/spring-boot-webmvc/build.gradle b/module/spring-boot-webmvc/build.gradle index eaf2af2a5e4..3510f57f7f9 100644 --- a/module/spring-boot-webmvc/build.gradle +++ b/module/spring-boot-webmvc/build.gradle @@ -37,12 +37,12 @@ dependencies { optional(project(":core:spring-boot-autoconfigure")) optional(project(":module:spring-boot-actuator-autoconfigure")) optional(project(":module:spring-boot-health")) + optional(project(":module:spring-boot-jackson")) optional(project(":module:spring-boot-micrometer-metrics")) optional(project(":module:spring-boot-micrometer-observation")) optional(project(":module:spring-boot-tomcat")) optional(project(":module:spring-boot-validation")) optional(project(":module:spring-boot-web-server")) - optional("com.fasterxml.jackson.core:jackson-databind") optional("io.projectreactor:reactor-core") testFixturesApi(testFixtures(project(":module:spring-boot-actuator"))) diff --git a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/actuate/web/WebMvcEndpointManagementContextConfiguration.java b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/actuate/web/WebMvcEndpointManagementContextConfiguration.java index 1452690924f..da2947bb3eb 100644 --- a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/actuate/web/WebMvcEndpointManagementContextConfiguration.java +++ b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/actuate/web/WebMvcEndpointManagementContextConfiguration.java @@ -23,7 +23,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; -import com.fasterxml.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint; @@ -37,7 +37,7 @@ import org.springframework.boot.actuate.endpoint.EndpointAccessResolver; import org.springframework.boot.actuate.endpoint.ExposableEndpoint; import org.springframework.boot.actuate.endpoint.OperationResponseBody; import org.springframework.boot.actuate.endpoint.annotation.Endpoint; -import org.springframework.boot.actuate.endpoint.jackson.EndpointObjectMapper; +import org.springframework.boot.actuate.endpoint.jackson.EndpointJsonMapper; import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver; import org.springframework.boot.actuate.endpoint.web.EndpointMapping; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; @@ -59,6 +59,7 @@ import org.springframework.context.annotation.Role; import org.springframework.core.env.Environment; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter; import org.springframework.util.StringUtils; import org.springframework.web.servlet.DispatcherServlet; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @@ -150,49 +151,48 @@ public class WebMvcEndpointManagementContextConfiguration { } @Bean - @ConditionalOnBean(EndpointObjectMapper.class) + @ConditionalOnBean(EndpointJsonMapper.class) @Role(BeanDefinition.ROLE_INFRASTRUCTURE) - static EndpointObjectMapperWebMvcConfigurer endpointObjectMapperWebMvcConfigurer( - EndpointObjectMapper endpointObjectMapper) { - return new EndpointObjectMapperWebMvcConfigurer(endpointObjectMapper); + static EndpointJsonMapperWebMvcConfigurer endpointJsonMapperWebMvcConfigurer( + EndpointJsonMapper endpointJsonMapper) { + return new EndpointJsonMapperWebMvcConfigurer(endpointJsonMapper); } /** - * {@link WebMvcConfigurer} to apply {@link EndpointObjectMapper} for + * {@link WebMvcConfigurer} to apply {@link EndpointJsonMapper} for * {@link OperationResponseBody} to * {@link org.springframework.http.converter.json.MappingJackson2HttpMessageConverter} * instances. */ @SuppressWarnings("removal") - static class EndpointObjectMapperWebMvcConfigurer implements WebMvcConfigurer { + static class EndpointJsonMapperWebMvcConfigurer implements WebMvcConfigurer { private static final List MEDIA_TYPES = Collections .unmodifiableList(Arrays.asList(MediaType.APPLICATION_JSON, new MediaType("application", "*+json"))); - private final EndpointObjectMapper endpointObjectMapper; + private final EndpointJsonMapper endpointJsonMapper; - EndpointObjectMapperWebMvcConfigurer(EndpointObjectMapper endpointObjectMapper) { - this.endpointObjectMapper = endpointObjectMapper; + EndpointJsonMapperWebMvcConfigurer(EndpointJsonMapper endpointJsonMapper) { + this.endpointJsonMapper = endpointJsonMapper; } @Override public void configureMessageConverters(List> converters) { for (HttpMessageConverter converter : converters) { - if (converter instanceof org.springframework.http.converter.json.MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) { - configure(mappingJackson2HttpMessageConverter); + if (converter instanceof JacksonJsonHttpMessageConverter jacksonJsonHttpMessageConverter) { + configure(jacksonJsonHttpMessageConverter); } } } - @SuppressWarnings("removal") - private void configure(org.springframework.http.converter.json.MappingJackson2HttpMessageConverter converter) { - this.endpointObjectMapper.getSupportedTypes() - .forEach((type) -> converter.registerObjectMappersForType(type, this::registerForAllMimeTypes)); + private void configure(JacksonJsonHttpMessageConverter converter) { + this.endpointJsonMapper.getSupportedTypes() + .forEach((type) -> converter.registerMappersForType(type, this::registerForAllMimeTypes)); } - private void registerForAllMimeTypes(Map registrar) { - ObjectMapper objectMapper = this.endpointObjectMapper.get(); - MEDIA_TYPES.forEach((mimeType) -> registrar.put(mimeType, objectMapper)); + private void registerForAllMimeTypes(Map registrar) { + JsonMapper jsonMapper = this.endpointJsonMapper.get(); + MEDIA_TYPES.forEach((mimeType) -> registrar.put(mimeType, jsonMapper)); } } diff --git a/module/spring-boot-webmvc/src/test/java/org/springframework/boot/webmvc/autoconfigure/actuate/web/WebMvcEndpointIntegrationTests.java b/module/spring-boot-webmvc/src/test/java/org/springframework/boot/webmvc/autoconfigure/actuate/web/WebMvcEndpointIntegrationTests.java index 5e4b278f51a..053996b1442 100644 --- a/module/spring-boot-webmvc/src/test/java/org/springframework/boot/webmvc/autoconfigure/actuate/web/WebMvcEndpointIntegrationTests.java +++ b/module/spring-boot-webmvc/src/test/java/org/springframework/boot/webmvc/autoconfigure/actuate/web/WebMvcEndpointIntegrationTests.java @@ -16,24 +16,23 @@ package org.springframework.boot.webmvc.autoconfigure.actuate.web; -import java.io.IOException; import java.util.function.Supplier; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.jsontype.TypeSerializer; -import com.fasterxml.jackson.databind.module.SimpleModule; -import com.fasterxml.jackson.databind.ser.std.StdScalarSerializer; import jakarta.servlet.http.HttpServlet; import org.junit.jupiter.api.Test; +import tools.jackson.core.JsonGenerator; +import tools.jackson.databind.SerializationContext; +import tools.jackson.databind.json.JsonMapper; +import tools.jackson.databind.jsontype.TypeSerializer; +import tools.jackson.databind.module.SimpleModule; +import tools.jackson.databind.ser.std.StdScalarSerializer; import org.springframework.boot.actuate.autoconfigure.audit.AuditAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.beans.BeansEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration; -import org.springframework.boot.actuate.endpoint.jackson.EndpointObjectMapper; +import org.springframework.boot.actuate.endpoint.jackson.EndpointJsonMapper; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.http.converter.autoconfigure.HttpMessageConvertersAutoConfiguration; @@ -95,9 +94,9 @@ class WebMvcEndpointIntegrationTests { } @Test - void endpointObjectMapperCanBeApplied() { + void endpointJsonMapperCanBeApplied() { this.context = new AnnotationConfigServletWebApplicationContext(); - this.context.register(EndpointObjectMapperConfiguration.class, DefaultConfiguration.class); + this.context.register(EndpointJsonMapperConfiguration.class, DefaultConfiguration.class); TestPropertyValues.of("management.endpoints.web.exposure.include=*").applyTo(this.context); MockMvcTester mvc = doCreateMockMvcTester(); assertThat(mvc.get().uri("/actuator/beans")).hasStatusOk().bodyText().contains("\"scope\":\"notelgnis\""); @@ -165,17 +164,14 @@ class WebMvcEndpointIntegrationTests { } @Configuration - @SuppressWarnings({ "deprecation", "removal" }) - static class EndpointObjectMapperConfiguration { + static class EndpointJsonMapperConfiguration { @Bean - EndpointObjectMapper endpointObjectMapper() { + EndpointJsonMapper endpointJsonMapper() { SimpleModule module = new SimpleModule(); module.addSerializer(String.class, new ReverseStringSerializer()); - ObjectMapper objectMapper = org.springframework.http.converter.json.Jackson2ObjectMapperBuilder.json() - .modules(module) - .build(); - return () -> objectMapper; + JsonMapper jsonMapper = JsonMapper.builder().addModule(module).build(); + return () -> jsonMapper; } static class ReverseStringSerializer extends StdScalarSerializer { @@ -185,22 +181,22 @@ class WebMvcEndpointIntegrationTests { } @Override - public boolean isEmpty(SerializerProvider prov, Object value) { + public boolean isEmpty(SerializationContext context, Object value) { return ((String) value).isEmpty(); } @Override - public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException { + public void serialize(Object value, JsonGenerator gen, SerializationContext context) { serialize(value, gen); } @Override - public final void serializeWithType(Object value, JsonGenerator gen, SerializerProvider provider, - TypeSerializer typeSer) throws IOException { + public final void serializeWithType(Object value, JsonGenerator gen, SerializationContext context, + TypeSerializer typeSer) { serialize(value, gen); } - private void serialize(Object value, JsonGenerator gen) throws IOException { + private void serialize(Object value, JsonGenerator gen) { StringBuilder builder = new StringBuilder((String) value); gen.writeString(builder.reverse().toString()); } diff --git a/module/spring-boot-websocket/src/main/java/org/springframework/boot/websocket/autoconfigure/servlet/WebSocketMessagingAutoConfiguration.java b/module/spring-boot-websocket/src/main/java/org/springframework/boot/websocket/autoconfigure/servlet/WebSocketMessagingAutoConfiguration.java index 00f03cdd098..ee669500304 100644 --- a/module/spring-boot-websocket/src/main/java/org/springframework/boot/websocket/autoconfigure/servlet/WebSocketMessagingAutoConfiguration.java +++ b/module/spring-boot-websocket/src/main/java/org/springframework/boot/websocket/autoconfigure/servlet/WebSocketMessagingAutoConfiguration.java @@ -19,8 +19,8 @@ package org.springframework.boot.websocket.autoconfigure.servlet; import java.util.List; import java.util.Map; -import com.fasterxml.jackson.databind.ObjectMapper; import org.jspecify.annotations.Nullable; +import tools.jackson.databind.json.JsonMapper; import org.springframework.boot.LazyInitializationExcludeFilter; import org.springframework.boot.autoconfigure.AutoConfiguration; @@ -36,6 +36,7 @@ import org.springframework.core.annotation.Order; import org.springframework.core.task.AsyncTaskExecutor; import org.springframework.messaging.converter.ByteArrayMessageConverter; import org.springframework.messaging.converter.DefaultContentTypeResolver; +import org.springframework.messaging.converter.JacksonJsonMessageConverter; import org.springframework.messaging.converter.MessageConverter; import org.springframework.messaging.converter.StringMessageConverter; import org.springframework.messaging.simp.config.AbstractMessageBrokerConfiguration; @@ -58,18 +59,17 @@ import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerCo public final class WebSocketMessagingAutoConfiguration { @Configuration(proxyBeanMethods = false) - @ConditionalOnBean({ DelegatingWebSocketMessageBrokerConfiguration.class, ObjectMapper.class }) - @ConditionalOnClass({ ObjectMapper.class, AbstractMessageBrokerConfiguration.class }) + @ConditionalOnBean({ DelegatingWebSocketMessageBrokerConfiguration.class, JsonMapper.class }) + @ConditionalOnClass({ JsonMapper.class, AbstractMessageBrokerConfiguration.class }) @Order(0) static class WebSocketMessageConverterConfiguration implements WebSocketMessageBrokerConfigurer { - private final ObjectMapper objectMapper; + private final JsonMapper jsonMapper; private final @Nullable AsyncTaskExecutor executor; - WebSocketMessageConverterConfiguration(ObjectMapper objectMapper, - Map taskExecutors) { - this.objectMapper = objectMapper; + WebSocketMessageConverterConfiguration(JsonMapper jsonMapper, Map taskExecutors) { + this.jsonMapper = jsonMapper; this.executor = determineAsyncTaskExecutor(taskExecutors); } @@ -83,9 +83,7 @@ public final class WebSocketMessagingAutoConfiguration { @Override public boolean configureMessageConverters(List messageConverters) { - @SuppressWarnings({ "removal", "deprecation" }) - org.springframework.messaging.converter.MappingJackson2MessageConverter converter = new org.springframework.messaging.converter.MappingJackson2MessageConverter( - this.objectMapper); + JacksonJsonMessageConverter converter = new JacksonJsonMessageConverter(this.jsonMapper); DefaultContentTypeResolver resolver = new DefaultContentTypeResolver(); resolver.setDefaultMimeType(MimeTypeUtils.APPLICATION_JSON); converter.setContentTypeResolver(resolver); diff --git a/module/spring-boot-websocket/src/test/java/org/springframework/boot/websocket/autoconfigure/servlet/WebSocketMessagingAutoConfigurationTests.java b/module/spring-boot-websocket/src/test/java/org/springframework/boot/websocket/autoconfigure/servlet/WebSocketMessagingAutoConfigurationTests.java index 718727b1f2e..b7090ab7b2f 100644 --- a/module/spring-boot-websocket/src/test/java/org/springframework/boot/websocket/autoconfigure/servlet/WebSocketMessagingAutoConfigurationTests.java +++ b/module/spring-boot-websocket/src/test/java/org/springframework/boot/websocket/autoconfigure/servlet/WebSocketMessagingAutoConfigurationTests.java @@ -27,13 +27,13 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; -import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.tomcat.websocket.WsWebSocketContainer; import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.skyscreamer.jsonassert.JSONAssert; +import tools.jackson.databind.json.JsonMapper; import org.springframework.boot.LazyInitializationBeanFactoryPostProcessor; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; @@ -147,7 +147,7 @@ class WebSocketMessagingAutoConfigurationTests { AsyncTaskExecutor expectedExecutor = new SimpleAsyncTaskExecutor(); ChannelRegistration registration = new ChannelRegistration(); WebSocketMessageConverterConfiguration configuration = new WebSocketMessagingAutoConfiguration.WebSocketMessageConverterConfiguration( - new ObjectMapper(), + new JsonMapper(), Map.of(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME, expectedExecutor)); configuration.configureClientInboundChannel(registration); assertThat(registration).extracting("executor").isEqualTo(expectedExecutor); @@ -158,7 +158,7 @@ class WebSocketMessagingAutoConfigurationTests { AsyncTaskExecutor expectedExecutor = new SimpleAsyncTaskExecutor(); ChannelRegistration registration = new ChannelRegistration(); WebSocketMessagingAutoConfiguration.WebSocketMessageConverterConfiguration configuration = new WebSocketMessagingAutoConfiguration.WebSocketMessageConverterConfiguration( - new ObjectMapper(), + new JsonMapper(), Map.of(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME, expectedExecutor)); configuration.configureClientOutboundChannel(registration); assertThat(registration).extracting("executor").isEqualTo(expectedExecutor); @@ -187,7 +187,7 @@ class WebSocketMessagingAutoConfigurationTests { private List getCustomizedConverters() { List customizedConverters = new ArrayList<>(); WebSocketMessagingAutoConfiguration.WebSocketMessageConverterConfiguration configuration = new WebSocketMessagingAutoConfiguration.WebSocketMessageConverterConfiguration( - new ObjectMapper(), Collections.emptyMap()); + new JsonMapper(), Collections.emptyMap()); configuration.configureMessageConverters(customizedConverters); return customizedConverters; } diff --git a/platform/spring-boot-dependencies/build.gradle b/platform/spring-boot-dependencies/build.gradle index ef97280fb5c..64cde740839 100644 --- a/platform/spring-boot-dependencies/build.gradle +++ b/platform/spring-boot-dependencies/build.gradle @@ -681,7 +681,7 @@ bom { releaseNotes("https://github.com/influxdata/influxdb-java/releases/tag/influxdb-java-{version}") } } - library("Jackson Bom", "${jacksonVersion}") { + library("Jackson 2 Bom", "${jackson2Version}") { group("com.fasterxml.jackson") { bom("jackson-bom") } @@ -689,6 +689,16 @@ bom { releaseNotes("https://github.com/FasterXML/jackson/wiki/Jackson-Release-{version}") } } + library("Jackson Bom", "${jacksonVersion}") { + group("tools.jackson") { + bom("jackson-bom") { + permit("com.fasterxml.jackson.core:jackson-annotations") + } + } + links { + releaseNotes("https://github.com/FasterXML/jackson/wiki/Jackson-Release-{version}") + } + } library("Jakarta Activation", "2.1.3") { group("jakarta.activation") { modules = [ diff --git a/smoke-test/spring-boot-smoke-test-actuator/src/main/resources/application.properties b/smoke-test/spring-boot-smoke-test-actuator/src/main/resources/application.properties index 794f9563c2a..9c19904c839 100644 --- a/smoke-test/spring-boot-smoke-test-actuator/src/main/resources/application.properties +++ b/smoke-test/spring-boot-smoke-test-actuator/src/main/resources/application.properties @@ -19,8 +19,6 @@ server.tomcat.accesslog.pattern=%h %t "%r" %s %b #spring.jackson.serialization.INDENT_OUTPUT=true spring.jmx.enabled=true -spring.jackson.serialization.write_dates_as_timestamps=false - management.httpexchanges.recording.include=request-headers,response-headers,principal,remote-address,session-id management.endpoint.health.show-details=always @@ -33,4 +31,4 @@ management.endpoint.health.group.comp.show-details=always management.endpoints.migrate-legacy-ids=true management.endpoints.jackson.isolated-object-mapper=true -spring.jackson.visibility.field=any +spring.jackson.mapper.require-setters-for-getters=true diff --git a/smoke-test/spring-boot-smoke-test-actuator/src/test/java/smoketest/actuator/SampleActuatorApplicationIsolatedObjectMapperFalseTests.java b/smoke-test/spring-boot-smoke-test-actuator/src/test/java/smoketest/actuator/SampleActuatorApplicationIsolatedObjectMapperFalseTests.java index 900f145d003..789daae928e 100644 --- a/smoke-test/spring-boot-smoke-test-actuator/src/test/java/smoketest/actuator/SampleActuatorApplicationIsolatedObjectMapperFalseTests.java +++ b/smoke-test/spring-boot-smoke-test-actuator/src/test/java/smoketest/actuator/SampleActuatorApplicationIsolatedObjectMapperFalseTests.java @@ -16,8 +16,8 @@ package smoketest.actuator; -import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; +import tools.jackson.databind.ObjectMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -35,7 +35,8 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Phillip Webb */ @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, - properties = "management.endpoints.jackson.isolated-object-mapper=false") + properties = { "management.endpoints.jackson.isolated-object-mapper=false", + "spring.jackson.mapper.require-setters-for-getters=true" }) @ContextConfiguration(loader = ApplicationStartupSpringBootContextLoader.class) class SampleActuatorApplicationIsolatedObjectMapperFalseTests { @@ -43,10 +44,11 @@ class SampleActuatorApplicationIsolatedObjectMapperFalseTests { private TestRestTemplate testRestTemplate; @Test - void resourceShouldBeAvailableOnMainPort() { + void bodyIsEmptyDueToMainJsonMapperRequiringSettersForGetters() { ResponseEntity entity = this.testRestTemplate.withBasicAuth("user", "password") .getForEntity("/actuator/startup", String.class); - assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR); + assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(entity.getBody()).isEqualTo("{}"); } } diff --git a/smoke-test/spring-boot-smoke-test-actuator/src/test/java/smoketest/actuator/SampleActuatorApplicationIsolatedObjectMapperTrueTests.java b/smoke-test/spring-boot-smoke-test-actuator/src/test/java/smoketest/actuator/SampleActuatorApplicationIsolatedObjectMapperTrueTests.java index 7b1d3be4c88..c5c86bdf5af 100644 --- a/smoke-test/spring-boot-smoke-test-actuator/src/test/java/smoketest/actuator/SampleActuatorApplicationIsolatedObjectMapperTrueTests.java +++ b/smoke-test/spring-boot-smoke-test-actuator/src/test/java/smoketest/actuator/SampleActuatorApplicationIsolatedObjectMapperTrueTests.java @@ -16,8 +16,8 @@ package smoketest.actuator; -import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; +import tools.jackson.databind.ObjectMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -35,7 +35,8 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Phillip Webb */ @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, - properties = "management.endpoints.jackson.isolated-object-mapper=true") + properties = { "management.endpoints.jackson.isolated-object-mapper=true", + "spring.jackson.mapper.require-setters-for-getters=true" }) @ContextConfiguration(loader = ApplicationStartupSpringBootContextLoader.class) class SampleActuatorApplicationIsolatedObjectMapperTrueTests { @@ -43,7 +44,7 @@ class SampleActuatorApplicationIsolatedObjectMapperTrueTests { private TestRestTemplate testRestTemplate; @Test - void resourceShouldBeAvailableOnMainPort() { + void bodyIsPresentAsOnlyMainObjectMapperRequiresSettersForGetters() { ResponseEntity entity = this.testRestTemplate.withBasicAuth("user", "password") .getForEntity("/actuator/startup", String.class); assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK); diff --git a/smoke-test/spring-boot-smoke-test-hateoas/src/test/java/smoketest/hateoas/SampleHateoasApplicationTests.java b/smoke-test/spring-boot-smoke-test-hateoas/src/test/java/smoketest/hateoas/SampleHateoasApplicationTests.java index e01c2d2d967..4b7f5d20b4a 100644 --- a/smoke-test/spring-boot-smoke-test-hateoas/src/test/java/smoketest/hateoas/SampleHateoasApplicationTests.java +++ b/smoke-test/spring-boot-smoke-test-hateoas/src/test/java/smoketest/hateoas/SampleHateoasApplicationTests.java @@ -45,8 +45,8 @@ class SampleHateoasApplicationTests { ResponseEntity entity = this.restTemplate.exchange("/customers/1", HttpMethod.GET, new HttpEntity<>(headers), String.class); assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(entity.getBody()).startsWith("{\"id\":1,\"firstName\":\"Oliver\",\"lastName\":\"Gierke\""); - assertThat(entity.getBody()).contains("_links\":{\"self\":{\"href\""); + assertThat(entity.getBody()).startsWith("{\"_links\":{\"self\":{\"href\""); + assertThat(entity.getBody()).endsWith(",\"id\":1,\"firstName\":\"Oliver\",\"lastName\":\"Gierke\"}"); } @Test @@ -56,8 +56,8 @@ class SampleHateoasApplicationTests { ResponseEntity entity = this.restTemplate.exchange("/customers/1", HttpMethod.GET, new HttpEntity<>(headers), String.class); assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(entity.getBody()).startsWith("{\"id\":1,\"firstName\":\"Oliver\",\"lastName\":\"Gierke\""); - assertThat(entity.getBody()).contains("_links\":{\"self\":{\"href\""); + assertThat(entity.getBody()).startsWith("{\"_links\":{\"self\":{\"href\""); + assertThat(entity.getBody()).endsWith(",\"id\":1,\"firstName\":\"Oliver\",\"lastName\":\"Gierke\"}"); } @Test diff --git a/smoke-test/spring-boot-smoke-test-web-secure/src/test/java/smoketest/web/secure/AbstractErrorPageTests.java b/smoke-test/spring-boot-smoke-test-web-secure/src/test/java/smoketest/web/secure/AbstractErrorPageTests.java index 8f2f64e5a4f..a3183dcb664 100644 --- a/smoke-test/spring-boot-smoke-test-web-secure/src/test/java/smoketest/web/secure/AbstractErrorPageTests.java +++ b/smoke-test/spring-boot-smoke-test-web-secure/src/test/java/smoketest/web/secure/AbstractErrorPageTests.java @@ -16,8 +16,8 @@ package smoketest.web.secure; -import com.fasterxml.jackson.databind.JsonNode; import org.junit.jupiter.api.Test; +import tools.jackson.databind.JsonNode; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.web.server.test.client.TestRestTemplate; @@ -71,7 +71,7 @@ abstract class AbstractErrorPageTests { .exchange(this.pathPrefix + "/public/notfound", HttpMethod.GET, null, JsonNode.class); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); JsonNode jsonResponse = response.getBody(); - assertThat(jsonResponse.get("error").asText()).isEqualTo("Not Found"); + assertThat(jsonResponse.get("error").asString()).isEqualTo("Not Found"); } @Test @@ -89,7 +89,7 @@ abstract class AbstractErrorPageTests { .exchange(this.pathPrefix + "/fail", HttpMethod.GET, null, JsonNode.class); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR); JsonNode jsonResponse = response.getBody(); - assertThat(jsonResponse.get("error").asText()).isEqualTo("Internal Server Error"); + assertThat(jsonResponse.get("error").asString()).isEqualTo("Internal Server Error"); } @Test diff --git a/smoke-test/spring-boot-smoke-test-web-secure/src/test/java/smoketest/web/secure/AbstractUnauthenticatedErrorPageTests.java b/smoke-test/spring-boot-smoke-test-web-secure/src/test/java/smoketest/web/secure/AbstractUnauthenticatedErrorPageTests.java index f0cf5c1606c..28b9cc8fd14 100644 --- a/smoke-test/spring-boot-smoke-test-web-secure/src/test/java/smoketest/web/secure/AbstractUnauthenticatedErrorPageTests.java +++ b/smoke-test/spring-boot-smoke-test-web-secure/src/test/java/smoketest/web/secure/AbstractUnauthenticatedErrorPageTests.java @@ -16,8 +16,8 @@ package smoketest.web.secure; -import com.fasterxml.jackson.databind.JsonNode; import org.junit.jupiter.api.Test; +import tools.jackson.databind.JsonNode; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.web.server.test.client.TestRestTemplate; @@ -50,7 +50,7 @@ abstract class AbstractUnauthenticatedErrorPageTests { .exchange(this.pathPrefix + "/test", HttpMethod.GET, null, JsonNode.class); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED); JsonNode jsonResponse = response.getBody(); - assertThat(jsonResponse.get("error").asText()).isEqualTo("Unauthorized"); + assertThat(jsonResponse.get("error").asString()).isEqualTo("Unauthorized"); } @Test @@ -59,7 +59,7 @@ abstract class AbstractUnauthenticatedErrorPageTests { HttpMethod.GET, null, JsonNode.class); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED); JsonNode jsonResponse = response.getBody(); - assertThat(jsonResponse.get("error").asText()).isEqualTo("Unauthorized"); + assertThat(jsonResponse.get("error").asString()).isEqualTo("Unauthorized"); } @Test @@ -68,7 +68,7 @@ abstract class AbstractUnauthenticatedErrorPageTests { HttpMethod.GET, null, JsonNode.class); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); JsonNode jsonResponse = response.getBody(); - assertThat(jsonResponse.get("error").asText()).isEqualTo("Not Found"); + assertThat(jsonResponse.get("error").asString()).isEqualTo("Not Found"); } @Test @@ -77,7 +77,7 @@ abstract class AbstractUnauthenticatedErrorPageTests { .exchange(this.pathPrefix + "/public/notfound", HttpMethod.GET, null, JsonNode.class); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); JsonNode jsonResponse = response.getBody(); - assertThat(jsonResponse.get("error").asText()).isEqualTo("Not Found"); + assertThat(jsonResponse.get("error").asString()).isEqualTo("Not Found"); } @Test @@ -86,7 +86,7 @@ abstract class AbstractUnauthenticatedErrorPageTests { .exchange(this.pathPrefix + "/public/notfound", HttpMethod.GET, null, JsonNode.class); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED); JsonNode jsonResponse = response.getBody(); - assertThat(jsonResponse.get("error").asText()).isEqualTo("Unauthorized"); + assertThat(jsonResponse.get("error").asString()).isEqualTo("Unauthorized"); } @Test @@ -95,7 +95,7 @@ abstract class AbstractUnauthenticatedErrorPageTests { .exchange(this.pathPrefix + "/fail", HttpMethod.GET, null, JsonNode.class); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR); JsonNode jsonResponse = response.getBody(); - assertThat(jsonResponse.get("error").asText()).isEqualTo("Internal Server Error"); + assertThat(jsonResponse.get("error").asString()).isEqualTo("Internal Server Error"); } @Test diff --git a/smoke-test/spring-boot-smoke-test-webflux/src/main/resources/application.properties b/smoke-test/spring-boot-smoke-test-webflux/src/main/resources/application.properties index 641c39e6572..82e627f6a54 100644 --- a/smoke-test/spring-boot-smoke-test-webflux/src/main/resources/application.properties +++ b/smoke-test/spring-boot-smoke-test-webflux/src/main/resources/application.properties @@ -1,3 +1,2 @@ management.endpoints.web.exposure.include=* -management.endpoints.jackson.isolated-object-mapper=true -spring.jackson.visibility.field=any + diff --git a/smoke-test/spring-boot-smoke-test-webflux/src/test/java/smoketest/webflux/SampleWebFluxApplicationActuatorIsolatedObjectMapperFalseTests.java b/smoke-test/spring-boot-smoke-test-webflux/src/test/java/smoketest/webflux/SampleWebFluxApplicationActuatorIsolatedObjectMapperFalseTests.java index d57ad6940ce..241129eccbe 100644 --- a/smoke-test/spring-boot-smoke-test-webflux/src/test/java/smoketest/webflux/SampleWebFluxApplicationActuatorIsolatedObjectMapperFalseTests.java +++ b/smoke-test/spring-boot-smoke-test-webflux/src/test/java/smoketest/webflux/SampleWebFluxApplicationActuatorIsolatedObjectMapperFalseTests.java @@ -16,8 +16,8 @@ package smoketest.webflux; -import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; +import tools.jackson.databind.ObjectMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -31,7 +31,8 @@ import org.springframework.test.web.reactive.server.WebTestClient; * @author Phillip Webb */ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, - properties = "management.endpoints.jackson.isolated-object-mapper=false") + properties = { "management.endpoints.jackson.isolated-object-mapper=false", + "spring.jackson.mapper.require-setters-for-getters=true" }) @ContextConfiguration(loader = ApplicationStartupSpringBootContextLoader.class) class SampleWebFluxApplicationActuatorIsolatedObjectMapperFalseTests { @@ -39,13 +40,15 @@ class SampleWebFluxApplicationActuatorIsolatedObjectMapperFalseTests { private WebTestClient webClient; @Test - void linksEndpointShouldBeAvailable() { + void bodyIsEmptyDueToMainJsonMapperRequiringSettersForGetters() { this.webClient.get() .uri("/actuator/startup") .accept(MediaType.APPLICATION_JSON) .exchange() .expectStatus() - .is5xxServerError(); + .isOk() + .expectBody() + .json("{}"); } } diff --git a/smoke-test/spring-boot-smoke-test-webflux/src/test/java/smoketest/webflux/SampleWebFluxApplicationActuatorIsolatedObjectMapperTrueTests.java b/smoke-test/spring-boot-smoke-test-webflux/src/test/java/smoketest/webflux/SampleWebFluxApplicationActuatorIsolatedObjectMapperTrueTests.java index a42df28e928..515699edc03 100644 --- a/smoke-test/spring-boot-smoke-test-webflux/src/test/java/smoketest/webflux/SampleWebFluxApplicationActuatorIsolatedObjectMapperTrueTests.java +++ b/smoke-test/spring-boot-smoke-test-webflux/src/test/java/smoketest/webflux/SampleWebFluxApplicationActuatorIsolatedObjectMapperTrueTests.java @@ -18,8 +18,8 @@ package smoketest.webflux; import java.nio.charset.StandardCharsets; -import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; +import tools.jackson.databind.ObjectMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -36,7 +36,8 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Phillip Webb */ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, - properties = "management.endpoints.jackson.isolated-object-mapper=true") + properties = { "management.endpoints.jackson.isolated-object-mapper=true", + "spring.jackson.mapper.require-setters-for-getters=true" }) @ContextConfiguration(loader = ApplicationStartupSpringBootContextLoader.class) class SampleWebFluxApplicationActuatorIsolatedObjectMapperTrueTests { @@ -44,7 +45,7 @@ class SampleWebFluxApplicationActuatorIsolatedObjectMapperTrueTests { private WebTestClient webClient; @Test - void linksEndpointShouldBeAvailable() { + void bodyIsPresentAsOnlyMainObjectMapperRequiresSettersForGetters() { this.webClient.get() .uri("/actuator/startup") .accept(MediaType.APPLICATION_JSON) diff --git a/starter/spring-boot-starter-jackson/build.gradle b/starter/spring-boot-starter-jackson/build.gradle index 69363ab9dee..e2e6285f407 100644 --- a/starter/spring-boot-starter-jackson/build.gradle +++ b/starter/spring-boot-starter-jackson/build.gradle @@ -24,8 +24,4 @@ dependencies { api(project(":starter:spring-boot-starter")) api(project(":module:spring-boot-jackson")) - - api("com.fasterxml.jackson.datatype:jackson-datatype-jdk8") - api("com.fasterxml.jackson.datatype:jackson-datatype-jsr310") - api("com.fasterxml.jackson.module:jackson-module-parameter-names") }