Skip to content

Migrating Spring Boot 4 Workshop Examples to Jackson 3

3D workbench illustration where robot builders move a Jackson 2 block aside and align Spring Boot 4, Jackson 3, tools.jackson, JsonMapper, and a migration checklist
In Spring Boot 4 examples, the Jackson version is part of the example. A wrong classpath fails before the guide can explain anything useful.

When moving examples to Spring Boot 4, the visible changes are usually Spring Framework 7, starter names, and test package moves. The JSON line is easier to miss, but it is often where examples start to fail.

Spring Boot 4 uses Jackson 3 as its preferred default JSON library. Jackson 2 support remains a temporary migration path, not the long-term line for new Spring Boot 4 examples. If a Spring Boot 4 workshop module still depends on bluetape4k-jackson2 or imports Jackson 2 mapper packages, the example carries two JSON baselines at once.

Examples are not throwaway samples. Users copy them when starting real projects. If an example carries the wrong compatibility line, the first failure can appear before the interesting code even runs.

In bluetape4k-workshop, the Spring Kafka request-reply example and Spring Data Elasticsearch example still referenced bluetape4k-jackson2. The migration aligned three boundaries rather than merely increasing a version number.

The rule is not “delete every trace of Jackson 2.” The rule is to make the example line explicit.

CheckSpring Boot 4 example line
bluetape4k JSON helperbluetape4k-jackson3
Jackson packagetools.jackson.*
Jackson BOMtools.jackson:jackson-bom
Spring Boot lineSpring Boot 4 BOM and starters
ExceptionUpstream modules that still expose Jackson 2 APIs, or annotation packages

The workshop root build imports both Spring Boot 4 and Jackson 3 BOMs.

dependencyManagement {
imports {
mavenBom(rootLibs.spring.boot4.dependencies.get().toString())
mavenBom(rootLibs.jackson3.bom.get().toString())
}
}

The version catalog still contains both Jackson lines.

# Jackson 2
jackson-bom = { module = "com.fasterxml.jackson:jackson-bom", version.ref = "jackson" }
jackson-databind = { module = "com.fasterxml.jackson.core:jackson-databind" }
# Jackson 3
jackson3-bom = { module = "tools.jackson:jackson-bom", version.ref = "jackson3" }
jackson3-databind = { module = "tools.jackson.core:jackson-databind" }
jackson3-module-kotlin = { module = "tools.jackson.module:jackson-module-kotlin" }

Having both in the catalog does not mean every module can choose freely. The catalog is the parts shelf. The example baseline decides which line is assembled.

Dark architecture diagram placing dependency, code API, and configuration and verification boundaries below the Spring Boot 4 and Jackson 3 baseline, with allowed Jackson 2 exceptions shown as a separate boundary
Changing dependencies is not enough. Code APIs, configuration, and verification must point to the same baseline, while remaining Jackson 2 requirements must be managed as explicit exceptions.

messaging/kafka-reply demonstrates Spring Kafka request-reply flow. On the Spring Boot 4 line, it declares Jackson 3 directly.

dependencies {
api(libs.bluetape4k.jackson3)
api(libs.jackson3.databind)
api(libs.jackson3.module.kotlin)
api(libs.jackson3.module.blackbird)
implementation(libs.bluetape4k.kafka4)
implementation(libs.bluetape4k.coroutines)
implementation(libs.spring.boot.starter.webflux.lib)
}

This is not a feature add-on. It aligns the example with the world it claims to show: Spring Boot 4, Spring Kafka 4, Jackson 3, and bluetape4k-jackson3.

spring-data/elasticsearch follows the same line.

dependencies {
implementation(libs.spring.boot.starter.data.elasticsearch.lib)
implementation(libs.spring.boot.starter.webflux.lib)
implementation(libs.bluetape4k.jackson3)
implementation(libs.bluetape4k.coroutines)
}

After aligning dependencies, inspect imports and mapper beans.

Jackson 3 changed the dependency group and package names. The Spring Boot migration guide calls out the move from com.fasterxml.jackson to tools.jackson. Workshop code should look like this when it configures a Jackson 3 mapper.

import io.bluetape4k.jackson3.Jackson
import tools.jackson.databind.json.JsonMapper
@Configuration(proxyBeanMethods = false)
class JacksonConfig {
@Bean
fun jsonMapper(): JsonMapper = Jackson.defaultJsonMapper
}

Jackson.defaultJsonMapper from bluetape4k-jackson3 provides a Kotlin-ready default mapper with the common modules already wired. Examples do not have to rebuild the same setup in every module.

When extra settings are needed, use rebuild().

protected val defaultMapper: JsonMapper by lazy {
Jackson.defaultJsonMapper
.rebuild()
.apply {
configure(SerializationFeature.INDENT_OUTPUT, true)
}
.build()
}

That is a useful workshop pattern. The reader sees where to add local configuration without learning how to reassemble every Jackson module first.

com.fasterxml.jackson.annotation Is Different

Section titled “com.fasterxml.jackson.annotation Is Different”

A naive search-and-delete pass over com.fasterxml.jackson is dangerous.

Jackson 3 moved most mapper packages to tools.jackson, but annotations remain under com.fasterxml.jackson.annotation for compatibility. These imports can still be valid:

import com.fasterxml.jackson.annotation.JsonView

Use a classification rule instead.

Search resultAction
com.fasterxml.jackson.databind.*Usually move to tools.jackson.databind.* in Jackson 3 modules
com.fasterxml.jackson.module.*Usually move to tools.jackson.module.* in Jackson 3 modules
com.fasterxml.jackson.annotation.*Do not delete blindly; verify whether the annotation is still valid
bluetape4k-jackson2In Spring Boot 4 examples, evaluate a move to bluetape4k-jackson3

Migration needs classification, not string replacement.

Dependencies and imports are not the whole migration. Spring Boot 4 with Jackson 3 can also change how spring.jackson.* properties behave.

During the Exposed example rewrite, this familiar setting broke application-context loading:

spring:
jackson:
serialization:
write-dates-as-timestamps: false

In that example, enum conversion did not map write-dates-as-timestamps to Jackson 3’s WRITE_DATES_AS_TIMESTAMPS. The fix was to remove the spring.jackson section and configure the mapper in code. This does not mean every spring.jackson.* property is invalid under Jackson 3. Each property must be verified against the Spring Boot 4 and Jackson 3 combination in use.

spring.jackson.use-jackson2-defaults=true does not enable Jackson 2. It adjusts Jackson 3 mapper defaults to behave more like Spring Boot 3 with Jackson 2. Applications that must keep Jackson 2 need to evaluate the separate spring-boot-jackson2 compatibility module and the spring.jackson2.* configuration namespace. Compatibility paths should carry a reason and an exit condition instead of silently becoming the example’s default.

Forcing every module to Jackson 3 may look clean, but upstream APIs decide the real boundary.

The Quarkus Jackson extension modules remained unchanged because their local source still exposed Jackson 2 APIs. The Spring Data Elasticsearch modules also contain disabled tests that document a Jackson 2 client boundary colliding with the Spring Boot 4 Jackson 3 line.

@Disabled("Elasticsearch Client 가 Jackson2 를 사용합니다. Spring Boot 4 는 Jackson 3를 사용해서 충돌이 발생합니다.")

That note identifies a remaining compatibility debt; it is not proof that the migration is complete. Users need to know which boundary moved and which one still waits for upstream support.

Use this checklist when creating or cleaning a Spring Boot 4 workshop module.

StepCheckExample
1Module baselineConfirm it is a Spring Boot 4 module
2bluetape4k JSON helperPrefer bluetape4k-jackson3 over bluetape4k-jackson2
3Jackson dependencyUse jackson3-databind, jackson3-module-kotlin
4ImportsUse tools.jackson.databind.*, tools.jackson.module.*
5Annotation exceptionDo not blindly delete com.fasterxml.jackson.annotation.*
6Mapper beanPrefer JsonMapper and Jackson.defaultJsonMapper
7Config filesRe-check spring.jackson.* properties under Jackson 3
8Upstream exceptionsDocument clients or extensions that still require Jackson 2
9VerificationRun the relevant module testClasses or test task

The related module compilation and test configuration were verified together:

Terminal window
./gradlew :messaging-kafka-reply:testClasses :spring-data-elasticsearch:testClasses

One practical lesson came from a build-configuration failure: a duplicate version-catalog alias blocked Gradle configuration before module validation could even start. When a dependency migration fails before task selection, inspect libs.versions.toml before blaming the module tests.

The point of moving Spring Boot 4 workshop modules to Jackson 3 was not novelty. The point was to give users a clean starting line.

A Spring Boot 4 example that still carries Jackson 2 helpers teaches two things at once: the intended example and a classpath mixture the user should probably avoid. The second lesson is not worth exporting.

Spring Boot 4, Jackson 3, bluetape4k-jackson3, tools.jackson.*, and JsonMapper should point in the same direction. When they do, the example becomes quieter for the right reason: the compatibility line is aligned.

Comments

Leave a note or reaction with your GitHub account.