Skip to content
返回

Spring Boot 4 的 Jackson 3 把这些 API 全改了

下班之后没人亲亲,敲段代码哄自己开心。写个demo修完bug,誓让女神喊你哥哥。

Spring Boot 4 默认带的 Jackson 升到了 3.0。还没有升级的小伙伴们可以提前了解下变化,这次升级不是加几个方法的小迭代:包名从 com.fasterxml.jackson 换到了 tools.jacksonObjectMapper 的用法变了,十来个默认值变了,如果不知道如何配置,连序列化性能都跟着掉了一截。

如果你还在用 Spring Boot 2,大概率还停留在 new ObjectMapper() + 手动注册 JavaTimeModule 的写法。这篇文章把 Jackson 3 相对 Jackson 2 的变化总结一下,看完能知道改了什么、怎么改、哪些坑别踩。

背景很简单:Jackson 2.x 从 2012 年用到现在,攒了十几年技术债,对 Java 17+ 的 Record、sealed class 这些新特性支持一直别扭。Jackson 3 是一次从底层重构。Spring Boot 3 那次升级主要搞了 javax.*jakarta.* 的 Jakarta 迁移,Spring Boot 4 这次搞的就是 Jackson 迁移。从 Boot 2 到 Boot 4 你跨了两个大版本,Jackson 是和你日常代码贴得最近的一环。


一、包名和依赖全换了

升级 Jackson 3,第一件事就是 import 全报红。

Maven 坐标

项目Jackson 2.xJackson 3.x
GroupIdcom.fasterxml.jacksontools.jackson
corecom.fasterxml.jackson.core:jackson-coretools.jackson.core:jackson-core
databindcom.fasterxml.jackson.core:jackson-databindtools.jackson.core:jackson-databind
annotationscom.fasterxml.jackson.core:jackson-annotations不变,仍用 2.x 版本线

annotations 没换包名,是个例外。Jackson 团队评估后觉得 annotations 模块的 API 够稳定,没必要硬迁,所以 jackson-annotations 还留在 2.x 的包名和版本线上。在项目里继续用 com.fasterxml.jackson.annotation.JsonProperty,不用改。

Java 包名

// Jackson 2.x(Spring Boot 2/3)
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.core.JsonProcessingException;

// Jackson 3.x(Spring Boot 4)
import tools.jackson.databind.ObjectMapper;      // 包名变了,类还在
import tools.jackson.databind.JsonNode;
import tools.jackson.core.JacksonException;       // 异常改名了

迁移第一步就是全局替换包名,IDE 的全局替换能搞定大部分。注意别把 annotations 包也替换了,它还是 com.fasterxml.jackson.annotation

spring-boot-starter-json 在 Spring Boot 4 里底层换成了 Jackson 3,依赖配置不用改。项目使用了 Spring Boot 4 直接会使用 Jackson 3 。


二、ObjectMapper 分家,JsonMapper 成了 JSON 的主角

ObjectMapper 没被删,包名变成了 tools.jackson.databind.ObjectMapper

变化在于设计思路——以前一个 ObjectMapper 干所有格式,现在按格式分家:

格式Jackson 2.xJackson 3.x
JSONObjectMapperJsonMapper(推荐)
XMLXmlMapperXmlMapper(独立模块)
YAMLYAMLMapperYAMLMapper(独立模块)

JsonMapper 继承自 ObjectMapper,专精 JSON,是推荐入口。

不可变 + Builder

// Jackson 2.x:ObjectMapper 可变对象,new 完随便改
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
String json = mapper.writeValueAsString(user);

// Jackson 3.x:JsonMapper 不可变,Builder 一次性构建
JsonMapper mapper = JsonMapper.builder()
    .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)  // disable 而非 configure
    .propertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE)
    .build();                                                     // 必须显式 build
String json = mapper.writeValueAsString(user);

JsonMapper 是不可变的,不能 new 出来再改配置,得通过 Builder 一次性构建。想改配置用 rebuild() 基于现有实例复制一个新的:

JsonMapper newMapper = mapper.rebuild()
    .enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
    .build();

这跟 Java 11 里 HttpClient 的设计思路一样,不可变对象天然线程安全,不用再纠结”ObjectMapper 能不能单例共享”这种老问题。现在它就是必须当单例用,因为你也改不了它。

简单场景下可以直接用 JsonMapper.shared(),返回一个默认配置的共享实例,省得自己 Builder。

其他核心类重命名

Jackson 2.xJackson 3.x说明
JsonFactoryTokenStreamFactory流式 API 的工厂
JsonSerializer / JsonDeserializerValueSerializer / ValueDeserializer命名更直观
SerializerProviderSerializationContext序列化上下文
ModuleJacksonModule模块接口
TextNodeStringNodeJSON 字符串节点
MappingJacksonValue移除.hint() 替代

JsonSerializer 改名 ValueSerializer 挺合理,它序列化的确实是”值”而非”JSON 结构”。代价是所有自定义序列化器的 import 和类声明都得动。


三、十来个默认值变了

这是最容易出 Bug 的地方。Jackson 3 把一批默认值反过来了。

默认值变更对照

特性Jackson 2.x 默认Jackson 3.0 默认影响
FAIL_ON_UNKNOWN_PROPERTIEStruefalseJSON 多字段不再报错,可能吞掉数据问题
DEFAULT_VIEW_INCLUSIONtruefalse没有 @JsonView 的字段默认不序列化
FAIL_ON_EMPTY_BEANStruefalse空对象不再报错,直接序列化成 {}
WRITE_DATES_AS_TIMESTAMPStruefalse日期输出 ISO-8601 字符串而非时间戳
SORT_PROPERTIES_ALPHABETICALLYfalsetrueJSON 字段按字母排序,可能影响接口契约
FAIL_ON_TRAILING_TOKENSfalsetrueJSON 末尾多余内容会报错
ALLOW_FINAL_FIELDS_AS_MUTATORStruefalsefinal 字段不再被当作 setter 反序列化
READ_ENUMS_USING_TO_STRINGfalsetrue枚举用 toString() 而非 name() 反序列化
WRITE_ENUMS_USING_TO_STRINGfalsetrue枚举输出用 toString()
FAIL_ON_NULL_FOR_PRIMITIVESfalsetruenull 赋给基本类型会报错

几个常用的重点:

FAIL_ON_UNKNOWN_PROPERTIES 变成 false:以前 JSON 多字段会报错,现在默默忽略。好处是不用再写 @JsonIgnoreProperties(ignoreUnknown = true);如果你靠这个报错来发现前后端字段不一致,现在它不报了。

WRITE_DATES_AS_TIMESTAMPS 变成 false:以前日期输出 1719504000000,现在输出 "2024-06-28T00:00:00"。前端如果在解析时间戳,升完级直接炸。这是最常见的线上事故来源,升级后第一件事就是检查日期输出格式。

FAIL_ON_NULL_FOR_PRIMITIVES 变成 true:以前 null 反序列化到 int 字段会静默赋 0,现在直接抛异常。改动本身合理,null 和 0 是两码事,但老代码可能正靠这个”特性”苟着。

// Jackson 2.x:没问题
// Jackson 3.x:如果 JSON 中 age 是 null,直接抛异常
public class User {
    public int age;    // 基本类型 + null = 爆
}

改成 Integer,或者显式 disable 这个特性。


四、异常变化,catch IOException 的代码全得改

// Jackson 2.x:JsonProcessingException 继承 IOException
try {
    User user = mapper.readValue(json, User.class);
} catch (IOException e) {       // 能捕获 Jackson 异常
    log.error("反序列化失败", e);
}

// Jackson 3.x:JacksonException 继承 RuntimeException
try {
    User user = mapper.readValue(json, User.class);
} catch (IOException e) {       // 捕获不到了
    log.error("这行永远不会执行", e);
}

// 得显式捕获
try {
    User user = mapper.readValue(json, User.class);
} catch (JacksonException e) {
    log.error("反序列化失败", e);
}

异常类也重命名了:

Jackson 2.xJackson 3.x
JsonProcessingExceptionJacksonException(继承 RuntimeException
JsonMappingExceptionDatabindException
JsonParseExceptionStreamReadException
JsonGenerationExceptionStreamWriteException

官方的解释是:JSON 序列化反序列化不是 I/O 操作,在内存里转个对象不该抛 IOException。逻辑上说得通, catch (IOException e) 全得改。

方法签名上的 throws JsonProcessingException 可以直接删掉。


五、被删的 API

Jackson 3 正式删掉了一批早就该退休的 API,还在用的话编译直接报错。

被移除的 API替代方案
ObjectMapper.canSerialize() / canDeserialize()无直接替代,按需处理
JsonNode.fields()JsonNode.properties()
ObjectMapper.setSerializationInclusion()Builder 中 .serializationInclusion()
MappingJsonFactoryTokenStreamFactory
DefaultTyping.EVERYTHING改用 DefaultTyping.NON_FINAL 或自定义
MapperFeature.USE_STD_BEAN_NAMING已成默认行为,无需配置
MapperFeature.AUTO_DETECT_xxx 系列@JsonAutoDetect 显式控制

MapperFeature.USE_STD_BEAN_NAMING 被移除是因为它现在就是默认行为,Jackson 3 默认用标准 Java Bean 命名,不再有”MVC 风格”和”标准风格”的歧义。


六、Spring Boot 4 里的 Jackson 3 自动配置

注解和类重命名

Spring Boot 2/3Spring Boot 4说明
@JsonComponent@JacksonComponent组件注解
@JsonMixin@JacksonMixinMixin 注解
JsonObjectSerializerObjectValueSerializer序列化器
Jackson2ObjectMapperBuilderCustomizerJsonMapperBuilderCustomizerBuilder 定制器

配置前缀多了一层

# Spring Boot 2/3(Jackson 2)
spring:
  jackson:
    read:
      fail-on-unknown-properties: true
    write:
      dates-as-timestamps: false

# Spring Boot 4(Jackson 3)
spring:
  jackson:
    json:
      read:
        fail-on-unknown-properties: true   # 多了 json 层级
      write:
        dates-as-timestamps: false

Java 8 模块终于不用手动注册了

Jackson 2 时代用 Java 8 日期时间 API,得手动注册三个模块:

// Jackson 2.x
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());        // JSR-310 日期时间
mapper.registerModule(new Jdk8Module());            // Optional 等
mapper.registerModule(new ParameterNamesModule());  // 构造器参数名

Jackson 3 里这三个模块全部内置,通过 JDK Service Loader 自动发现,classpath 里有依赖 JsonMapper 就自动加载。

// Jackson 3.x
JsonMapper mapper = JsonMapper.shared();
// JavaTimeModule、Optional 支持、构造器参数名 都有了,不用注册

两种兼容方案

方案一:恢复 Jackson 2 默认行为

spring:
  jackson:
    use-jackson2-defaults: true # 回退到 Jackson 2.x 的默认值

加了这行,Jackson 3 的默认值会回退到 2.x 行为。过渡方案,别长期依赖。

方案二:临时用回 Jackson 2

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-jackson2</artifactId>
</dependency>

引入后 Spring Boot 回退到 Jackson 2,配置前缀变成 spring.jackson2.*。最保守的路线,先升 Spring Boot 4,Jackson 继续用 2.x,准备好了再迁。

Jackson 2(com.fasterxml.jackson.*)和 Jackson 3(tools.jackson.*)包名不同,两个版本能共存。代价是包体积多 3-4MB,混用容易出 bug。


七、默认配置有性能坑,从别人那看到的坑

Jackson 3 换了内部的 RecyclerPool 实现,从 Jackson 2 的 ThreadLocalPool 换成了基于 deque 的 RecyclerPool

JMH 基准测试

场景Jackson 2Jackson 3(默认)Jackson 3(优化)
序列化7297 ops/ms4631 ops/ms(↓36.5%)6614 ops/ms(恢复至 90.6%)
反序列化2431 ops/ms2280 ops/ms(↓6.2%)2350 ops/ms(恢复至 96.7%)

默认配置下序列化吞吐量下降约 36.5%,反序列化影响小一些(约 6%)。

优化:换回 ThreadLocalPool

import tools.jackson.core.json.JsonFactory;
import tools.jackson.core.util.JsonRecyclerPools;
import tools.jackson.databind.json.JsonMapper;

@Configuration
public class JacksonConfig {

    @Bean
    public JsonMapper jsonMapper() {
        JsonFactory factory = JsonFactory.builder()
            .recyclerPool(JsonRecyclerPools.threadLocalPool())
            .build();

        return JsonMapper.builder(factory).build();
    }
}

换回 threadLocalPool 后,序列化恢复到 Jackson 2 的 90% 以上,反序列化恢复到 96% 以上。

高并发、低延迟的服务升级 Jackson 3 后一定要配 threadLocalPool,否则序列化性能可能掉三成。


八、从 Boot 2 到 Jackson 3,分两步走

中间隔着 Spring Boot 3 的 Jakarta 迁移,别想一步到位。

第一步:Boot 2 → Boot 3

和 Jackson 无关,但绕不开:

第二步:Boot 3 → Boot 4

三条路线:

路线 A:直接迁 Jackson 3(新项目 / 小项目)

  1. 全局替换 com.fasterxml.jacksontools.jackson(排除 annotations)
  2. new ObjectMapper()JsonMapper.builder().build()
  3. 检查默认值变化,调整测试
  4. 替换异常捕获 IOExceptionJacksonException
  5. 配 threadLocalPool 优化性能
  6. 更新注解(@JsonComponent@JacksonComponent
  7. 调整 YAML 配置前缀(加 json: 层级)

路线 B:迁 Jackson 3 + 兼容模式(存量项目)

  1. 升级到 Spring Boot 4,引入 Jackson 3
  2. spring.jackson.use-jackson2-defaults=true,保持旧行为
  3. 逐步替换包名和 API
  4. 移除兼容配置,逐个确认默认值变化的影响

路线 C:暂时用回 Jackson 2(最保守)

  1. 升级到 Spring Boot 4
  2. 引入 spring-boot-jackson2,继续用 Jackson 2
  3. 制定 Jackson 3 迁移计划,分模块推进

九、几个值得用的新特性

除了改名和改默认值,Jackson 3 也有一些新东西。

ObjectNode 支持链式调用

// Jackson 2.x:set 返回旧值,不能链式
node.put("name", "张三");
node.put("age", 25);

// Jackson 3.x:set 返回自身
node.set("name", StringNode.valueOf("张三"))
    .set("age", IntNode.valueOf(25));

支持 java.nio.file.Path

// Jackson 2.x
User user = mapper.readValue(new File("user.json"), User.class);

// Jackson 3.x
User user = mapper.readValue(Path.of("user.json"), User.class);

自动探测 sealed classes

// Jackson 2.x:多态必须加 @JsonSubTypes
@JsonTypeInfo(use = Id.NAME)
@JsonSubTypes({
    @Type(value = Dog.class, name = "dog"),
    @Type(value = Cat.class, name = "cat")
})
public abstract class Animal {}

// Jackson 3.x:sealed class 自动识别,不用 @JsonSubTypes
public sealed interface Animal permits Dog, Cat {}

Jackson 3 通过反射直接拿到 permits 列表,省心。

序列化器缓存有上限了

Jackson 2 的序列化器缓存无上限,类多的时候可能 OOM。Jackson 3 加了缓存大小上限,默认 2000 个,可配置调整。不少生产环境的 OOM 就是 Jackson 缓存撑爆的。


最后

变化确实大:包名换了、API 改了、默认值翻了、异常模型重构了、性能特性也变了。但站在 Jackson 团队的角度,这些改动都是攒了十几年该还的债。一个库用了 12 年,底层架构不动才是问题。

不是 Jackson 变了,是 Java 变了,Jackson 终于追上来了。

你的项目用上Spring Boot 4了吗?是不是你们的技术负责人以稳定为主,不愿意去升级?他们的舞台已经落幕了,不升级会不会影响你将来在舞台上的表演?说说你的看法哈。


Share this post on:

Previous Post
Git Submodule血泪教训建议别碰
Next Post
Spring Boot 4 模块化架构扫盲笔记