导读 :项目从 Spring Boot 3 升到 4 之后,最常见的一类报错是
程序包com.fasterxml.jackson.databind不存在,或者启动时提示required a bean of type 'com.fasterxml.jackson.databind.ObjectMapper' that could not be found。原因是 Boot 4 默认换成了 Jackson 3。本文在 Spring Boot 4.1.2 + JDK 17 上把这两种报错都复现了一遍,给出正式改法和临时过渡的办法,顺带列出升级后 JSON 输出会悄悄变化的地方。
一、先说清楚:这个报错是怎么回事
Jackson 是 Spring Boot 里负责 Java 对象和 JSON 互转的库,Controller 返回对象变成 JSON、@RequestBody 把 JSON 变成对象,背后都是它。平时注入的 ObjectMapper,就是 Spring Boot 自动创建好、按 spring.jackson.* 配置过的那个实例。
Jackson 出了 3.0 大版本,包名整体从 com.fasterxml.jackson 改成了 tools.jackson (Maven groupId 也从 com.fasterxml.jackson.core 换成 tools.jackson.core)。Spring Boot 4 默认用的就是 Jackson 3,自动配置只创建 Jackson 3 的 JsonMapper,不再创建老包名下的 ObjectMapper。
所以老代码里 import com.fasterxml.jackson.databind.ObjectMapper 的地方会出两种情况:
| 你的依赖 | 现象 |
|---|---|
只引了 Spring Boot 的 starter,没单独引 jackson-databind |
编译失败:程序包com.fasterxml.jackson.databind不存在 |
pom 里单独引过 com.fasterxml.jackson.core:jackson-databind(或者某个三方包带进来了) |
能编译,启动失败:找不到 com.fasterxml.jackson.databind.ObjectMapper 类型的 Bean |
不处理的话应用起不来,没有"带病运行"这一说。什么时候需要动它:只要你准备升 Spring Boot 4,并且代码里有任何地方注入或 import 了 Jackson 2 的类。下面先复现,再说改法。
二、实测环境
| 项 | 版本 |
|---|---|
| Spring Boot | 4.1.2(Maven Central 上当前最新 GA,4.2.0 还在 RC1) |
| Jackson | 3.1.7(Boot 4.1.2 管理的版本) |
| JDK | 17.0.12 |
| Maven | 3.6.3 |
| 依赖 | 只有 spring-boot-starter-webmvc |
先看依赖树里 Jackson 是哪个版本:
bash
# 在项目根目录执行,只看 jackson 相关的行
mvn dependency:tree | grep -i jackson
4.1.2 的输出:
text
+- org.springframework.boot:spring-boot-starter-jackson:jar:4.1.2:compile
| \- org.springframework.boot:spring-boot-jackson:jar:4.1.2:compile
| \- tools.jackson.core:jackson-databind:jar:3.1.7:compile
| +- com.fasterxml.jackson.core:jackson-annotations:jar:2.21:compile
| \- tools.jackson.core:jackson-core:jar:3.1.7:compile
注意最后两行:jackson-annotations 还在老的 com.fasterxml.jackson.core 下面。这是 Jackson 3 唯一保留老包名的模块,所以 @JsonProperty、@JsonIgnore、@JsonFormat 这些注解的 import 不用改 。要改的是 ObjectMapper、JsonNode、JsonProcessingException 这类 databind 和 core 里的类。
三、复现两种报错
3.1 编译失败:程序包不存在
一个最普通的 Service,构造器注入 ObjectMapper:
java
package demo;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.stereotype.Service;
// Boot 3 时代的写法,原样搬到 Boot 4
@Service
class OrderService {
private final ObjectMapper objectMapper;
OrderService(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
}
mvn compile 直接失败:
text
[ERROR] App.java:[3,38] 程序包com.fasterxml.jackson.databind不存在
[ERROR] App.java:[17,19] 找不到符号
符号: 类 ObjectMapper
位置: 类 demo.OrderService
3.2 启动失败:找不到 Bean
很多人看到编译错误的第一反应是在 pom 里补一个 jackson-databind:
xml
<!-- 不带版本号,Boot 4 仍然管理 Jackson 2 的版本,实测解析到 2.21.7 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
这样编译能过,但启动报错:
text
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of constructor in demo.OrderService required a bean of type 'com.fasterxml.jackson.databind.ObjectMapper' that could not be found.
Action:
Consider defining a bean of type 'com.fasterxml.jackson.databind.ObjectMapper' in your configuration.
这就是标题里那个报错。类在 classpath 上,但 Spring Boot 4 的自动配置只认 Jackson 3,没人去创建这个 Bean。
⚠️ 老项目里这个依赖经常是三方 SDK 传递进来的,你自己的 pom 里没写也会走到 3.2 这种情况。先用
mvn dependency:tree -Dincludes=com.fasterxml.jackson.core:jackson-databind看看是谁带进来的。
四、正式改法:换成 Jackson 3 的 JsonMapper
4.1 改 import 和注入类型
把注入的类型换成 tools.jackson.databind.json.JsonMapper。Boot 4 自动配置的就是这个类型:
java
package demo;
import org.springframework.stereotype.Service;
import tools.jackson.databind.json.JsonMapper;
@Service
class OrderService {
private final JsonMapper jsonMapper;
OrderService(JsonMapper jsonMapper) {
this.jsonMapper = jsonMapper;
}
// Jackson 3 的 writeValueAsString 不再抛受检异常,方法签名不用 throws
String toJson(Order order) {
return jsonMapper.writeValueAsString(order);
}
}
注入 tools.jackson.databind.ObjectMapper 也可以。我在启动后打印了两个注入点,是同一个实例:
text
same bean: true, class=tools.jackson.databind.json.JsonMapper
我的建议是统一注入 JsonMapper。官方迁移指南里写了:Boot 4 开始,想替换 自动配置的实例,只声明一个 ObjectMapper Bean 已经不够了,要声明 JsonMapper Bean。业务代码也用 JsonMapper,和配置那边是一个类型,排查时少绕一层。
常用类的对照:
| Jackson 2(Boot 3) | Jackson 3(Boot 4) |
|---|---|
com.fasterxml.jackson.databind.ObjectMapper |
tools.jackson.databind.json.JsonMapper(或 tools.jackson.databind.ObjectMapper) |
com.fasterxml.jackson.databind.JsonNode |
tools.jackson.databind.JsonNode |
com.fasterxml.jackson.core.JsonProcessingException(受检异常) |
tools.jackson.core.JacksonException(运行时异常) |
com.fasterxml.jackson.databind.SerializationFeature |
tools.jackson.databind.SerializationFeature |
com.fasterxml.jackson.annotation.JsonProperty 等注解 |
不变,还是 com.fasterxml.jackson.annotation |
大项目里先用 grep 圈出要改的文件:
bash
# 列出 import 了 Jackson 2 databind / core 的 Java 文件,注解包不在其中
grep -rlE "import com\.fasterxml\.jackson\.(databind|core)\." src/main/java src/test/java
4.2 异常处理会顺带变简单
Jackson 2 里 readValue、writeValueAsString 都抛 JsonProcessingException,是受检异常,到处都是 try-catch 或 throws。Jackson 3 改成了运行时异常 JacksonException。实测解析一段坏 JSON:
java
try {
objectMapper.readValue("{bad json", Map.class);
} catch (JacksonException e) {
// 打印异常类名,确认它是不是 RuntimeException
System.out.println("caught " + e.getClass().getName() + " runtime=" + (e instanceof RuntimeException));
}
输出:
text
caught tools.jackson.core.exc.StreamReadException runtime=true
所以原来那些只为了满足编译器而写的 catch (JsonProcessingException e) { throw new RuntimeException(e); } 可以删掉。全局异常处理器里如果按 JsonProcessingException 做了分支,要改成 JacksonException,否则那个分支永远不会命中。
4.3 Customizer 改名
Boot 3 里常用 Jackson2ObjectMapperBuilderCustomizer 调全局配置。Boot 4 里原来的包 org.springframework.boot.autoconfigure.jackson 已经没了,实测老 import 编译报:
text
[ERROR] App.java:[7,54] 程序包org.springframework.boot.autoconfigure.jackson不存在
换成 JsonMapperBuilderCustomizer,包名也变了:
java
import org.springframework.boot.jackson.autoconfigure.JsonMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import tools.jackson.databind.SerializationFeature;
// 写在任意 @Configuration 类里,作用和 Boot 3 的 Jackson2ObjectMapperBuilderCustomizer 一样
@Bean
JsonMapperBuilderCustomizer indentCustomizer() {
return builder -> builder.enable(SerializationFeature.INDENT_OUTPUT);
}
注入的 JsonMapper 输出确实带了缩进,说明 Customizer 生效了。
官方迁移指南里还列了几个同类改名,有用到的一起改:
| Boot 3 | Boot 4 |
|---|---|
Jackson2ObjectMapperBuilderCustomizer |
JsonMapperBuilderCustomizer |
@JsonComponent |
@JacksonComponent |
@JsonMixin |
@JacksonMixin |
JsonObjectSerializer / JsonValueDeserializer |
ObjectValueSerializer / ObjectValueDeserializer |
spring.jackson.read.* / spring.jackson.write.* |
spring.jackson.json.read.* / spring.jackson.json.write.* |
💡 配置项改名这类问题,可以配合
spring-boot-properties-migrator一起查,启动日志会列出改了名的 key。
五、升级后 JSON 输出会悄悄变的地方
改完能启动了,还要看接口返回的 JSON 有没有变化。前端或下游按字符串解析日期的,最容易在这里出问题。
我用同一个对象分别在默认配置和 spring.jackson.use-jackson2-defaults=true 下序列化:
java
// 测试用的对象:一个 LocalDateTime,一个老式的 java.util.Date
record Order(Long id, LocalDateTime createTime, Date payTime) {}
Boot 4.1.2 默认:
json
{
"id" : 1001,
"createTime" : "2026-10-30T09:00:00",
"payTime" : "1970-01-01T00:00:00.000Z"
}
加上 --spring.jackson.use-jackson2-defaults=true:
json
{
"id" : 1001,
"createTime" : "2026-10-30T09:00:00",
"payTime" : "1970-01-01T00:00:00.000+00:00"
}
LocalDateTime 两种情况一样,java.util.Date 的时区后缀从 +00:00 变成了 Z。两种写法在 ISO 8601 里含义相同,标准的日期库都能解析;但如果下游是用正则或者字符串截取处理的,就会出错。
spring.jackson.use-jackson2-defaults 是 Boot 4 专门加的兼容开关,打开后自动配置的 JsonMapper 会尽量对齐 Boot 3 时代 Jackson 2 的默认行为。我的建议:
- 有对外接口、下游不归你管的服务,升级时先打开这个开关,保证返回格式不变,后面再单独评估要不要关
- 内部服务、前后端都在自己手里的,直接用 Jackson 3 默认值,把日期字段的契约测试补上
⚠️ 上面只对比了
LocalDateTime和Date两种类型。项目里如果有自定义序列化器、Optional、枚举、空值处理的特殊配置,升级后要用自己的接口回归一遍,不要只靠这一个对比。
六、一时改不完:spring-boot-jackson2 过渡方案
老项目里 Jackson 2 的代码到处都是,或者某个三方 SDK 只认 Jackson 2 的 ObjectMapper,一次改不完。Boot 4 提供了一个过渡模块:
xml
<!-- Boot 4 自带的 Jackson 2 兼容模块,版本由 parent 管理 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-jackson2</artifactId>
</dependency>
加上之后,3.2 节那段注入老 ObjectMapper 的代码能正常启动了:
text
jackson2 bean: com.fasterxml.jackson.databind.ObjectMapper
几个要注意的地方,都是这次实测看到的:
| 点 | 实测情况 |
|---|---|
| 模块状态 | 类上标了 @Deprecated(forRemoval=true),编译时提示"已过时, 且标记为待删除";官方说法是给需要更多时间的用户临时用,以后会删 |
| Customizer 包名 | 还叫 Jackson2ObjectMapperBuilderCustomizer,但包名变成 org.springframework.boot.jackson2.autoconfigure,老 import 照样要改 |
| 配置前缀 | 这个 Jackson 2 Bean 读的是 spring.jackson2.*(模块的配置元数据里能看到),不是 spring.jackson.* |
| HTTP 消息转换 | spring.http.converters.preferred-json-mapper 默认值是 jackson,也就是 Controller 的 JSON 仍走 Jackson 3;元数据里写着可以设成 jackson2(已标 deprecated) |
最后一行很容易踩坑:加了过渡模块,你注入的 ObjectMapper 是 Jackson 2 的,但 Controller 返回 JSON 用的还是 Jackson 3。两边配置分别在 spring.jackson2.* 和 spring.jackson.* 下,日期格式之类的配置只写了一边,就会出现"手动序列化和接口返回格式不一样"。preferred-json-mapper=jackson2 这条我只看了配置元数据,没有起 Web 服务实测切换效果。
我的建议是把过渡模块当成"先让应用启动起来"的手段,排期把代码迁到 Jackson 3,而不是长期留着。
七、速查表
| 现象 | 原因 | 处理 |
|---|---|---|
程序包com.fasterxml.jackson.databind不存在 |
Boot 4 只带 Jackson 3 | import 改成 tools.jackson.*,注入 JsonMapper |
启动报 required a bean of type 'com.fasterxml.jackson.databind.ObjectMapper' |
classpath 有 Jackson 2,但没人创建 Bean | 同上;改不完先加 spring-boot-jackson2 |
程序包org.springframework.boot.autoconfigure.jackson不存在 |
Customizer 换了名字和包 | 改用 org.springframework.boot.jackson.autoconfigure.JsonMapperBuilderCustomizer |
catch (JsonProcessingException e) 报错或不再命中 |
Jackson 3 改成运行时异常 | 改成 JacksonException,删掉多余的 try-catch |
接口里 Date 字段的时区后缀从 +00:00 变成 Z |
Jackson 3 默认值变化 | 对外接口先开 spring.jackson.use-jackson2-defaults=true |
@JsonProperty 等注解要不要改 |
不用改 | jackson-annotations 仍在 com.fasterxml.jackson.annotation |
八、常见问题
Q:我只是 Controller 返回对象,没手动注入 ObjectMapper,还用管吗?
代码上不用改,但第五节的输出变化照样会影响你,接口返回格式要回归一遍。
Q:能不能自己 new 一个 Jackson 2 的 ObjectMapper 注册成 Bean,把报错糊过去?
能启动,但这个实例不会读 spring.jackson.* 配置,日期格式、时区、命名策略都跟原来不一样,相当于换了一套没配置过的 Jackson。要临时兼容就用 spring-boot-jackson2,至少它会读 spring.jackson2.*。
Q:三方 SDK 方法参数要求传 Jackson 2 的 ObjectMapper 怎么办?
先看 SDK 有没有出支持 Jackson 3 的新版本。没有的话,用 spring-boot-jackson2 过渡,或者只在调用 SDK 的那个类里单独构建一个 Jackson 2 实例,别让它扩散到业务代码。
Q:4.2 还会有变化吗?
4.2.0 目前还是 RC1,本文只在 4.1.2 GA 上实测。4.2 的 Maven Central 上 spring-boot-jackson2 仍有 RC1 版本,说明过渡模块在 4.2 还没删。
参考链接
- Spring Boot 4.0 Migration Guide(Jackson 3 一节)
- Spring Boot 文档:JSON / Jackson
- Spring 官方博客:Introducing Jackson 3 support in Spring
- Jackson 官方:Migrating to Jackson 3
你们项目升 Boot 4 时,Jackson 这块是一次性迁到 Jackson 3,还是先挂了 spring-boot-jackson2 过渡?有没有碰到日期格式变化被下游发现的情况?评论区说说你的版本和做法,典型的我补进 FAQ。
觉得有用的话点个赞、收个藏,升级时翻得到。