Spring Boot 中 ObjectMapper 配置踩坑实录:LocalDateTime 无法序列化的终极解决方案

284 阅读3分钟

🌟 Spring Boot 中 ObjectMapper 配置踩坑实录:LocalDateTime 无法序列化的终极解决方案

在 Spring Boot 项目中处理 LocalDateTime 时,如果遇到类似如下错误:

InvalidDefinitionException: Java 8 date/time type `LocalDateTime` not supported

说明 Jackson 没有正确配置对 Java 时间类的支持。

这篇文章结合真实问题,分析了常见误区和错误配置方式,并总结了可行且推荐的全局配置方案。文章中还包含 Kafka、Elasticsearch 等场景下的实践细节,适合作为 Jackson 配置调优的参考。


⚠️ 常见错误写法

❌ 方式一:手动 new ObjectMapper()

ObjectMapper mapper = new ObjectMapper();
mapper.writeValueAsString(LocalDateTime.now());

会抛出异常:

InvalidDefinitionException: Java 8 date/time type `LocalDateTime` not supported

❌ 方式二:手动注册一个 ObjectMapper Bean(致命)

@Bean
public ObjectMapper objectMapper() {
    return new ObjectMapper(); // ❌ 这会覆盖 Spring Boot 默认的配置!
}

这会让你在其它类中 @AutowiredObjectMapper 变成“干净无配置”的版本,导致所有 Jackson 相关配置(如 JavaTimeModule)都失效。


✅ 正确配置(推荐)

1️⃣ 添加依赖

<dependency>
  <groupId>com.fasterxml.jackson.datatype</groupId>
  <artifactId>jackson-datatype-jsr310</artifactId>
  <version>2.18.3</version> <!-- 与 jackson-databind 保持一致版本 -->
</dependency>

2️⃣ 使用 Spring Boot 的推荐扩展方式配置 Jackson

@Configuration
public class JacksonConfig {

    @Bean
    public Jackson2ObjectMapperBuilderCustomizer jacksonCustomizer() {
        return builder -> builder
            .modules(new JavaTimeModule())
            .featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
            .simpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
    }
}

📌 注意:不要再声明一个 @Bean ObjectMapper,Spring 会自动基于这个配置创建全局唯一的 ObjectMapper。

3️⃣ 所有地方统一使用 Spring 注入的 ObjectMapper

@Autowired
private ObjectMapper objectMapper;

❌ 千万不要使用:

new ObjectMapper();

✅ 使用注入的:

String json = objectMapper.writeValueAsString(...);

🧪 从失败到成功的 ObjectMapper 使用过程

一开始,我在 Kafka 消费端使用如下代码对事件对象进行序列化(这里的 objectMapper 是通过 @Autowired 注入的):

import com.fasterxml.jackson.databind.ObjectMapper;

@Service
@RequiredArgsConstructor
public class KafkaConsumerService {

    private final ObjectMapper objectMapper;

    @KafkaListener(topics = "audit-log-topic", groupId = "audit-consumer-group")
    public void consume(AuditLogEvent event) throws IOException {
        printLog(event);

        // 错误发生在这里
        String json = objectMapper.writeValueAsString(event);

        // ...其他逻辑
    }

    private void printLog(AuditLogEvent event) {
        log.info("📥 Received: {}", event.getAction());
    }
}

当执行 writeValueAsString(event) 时始终报错:

InvalidDefinitionException: Java 8 date/time type `LocalDateTime` not supported

我尝试通过注册 JavaTimeModule 来解决问题,也写了如下配置类:

@Configuration
public class JacksonConfig {
    @Bean
    public Jackson2ObjectMapperBuilderCustomizer jacksonCustomizer() {
        return builder -> builder
            .modules(new JavaTimeModule())
            .featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
            .simpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
    }
}

但问题仍然存在。

最终我发现,罪魁祸首是项目中的 Elasticsearch 配置类中定义了如下 Bean:

@Configuration
public class ElasticsearchConfig {

    @Bean
    public ElasticsearchClient elasticsearchClient() {
        RestClient restClient = RestClient.builder(
            new HttpHost("localhost", 9200, "http")
        ).build();

        RestClientTransport transport = new RestClientTransport(
            restClient, new JacksonJsonpMapper()
        );

        return new ElasticsearchClient(transport);
    }

    @Bean
    public ObjectMapper objectMapper() {
        return new ObjectMapper(); // ❌ 这会覆盖 Spring Boot 的默认配置!
    }
}

这个自定义的 ObjectMapper 会完全覆盖掉 Spring Boot 自动配置的那个,导致全局注入的 ObjectMapper 全部变成了"默认裸奔版本",从而引发 LocalDateTime 报错。

✅ 修复方式:删除手动 ObjectMapper Bean

@Configuration
public class ElasticsearchConfig {

    @Bean
    public ElasticsearchClient elasticsearchClient() {
        RestClient restClient = RestClient.builder(
                new HttpHost("localhost", 9200, "http")
        ).build();

        RestClientTransport transport = new RestClientTransport(
                restClient, new JacksonJsonpMapper()
        );

        return new ElasticsearchClient(transport);
    }
}

删除自定义 ObjectMapper 后,所有问题立即消失,Kafka 消费端也不再报错,控制台输出:

✅ Indexed to Elasticsearch with ID

说明 Jackson 配置已经完全生效 ✅


📌 总结表格

做法是否推荐说明
@Autowired ObjectMapper推荐使用 Spring 自动配置的 mapper
Jackson2ObjectMapperBuilderCustomizer推荐方式,可叠加模块和格式
@Bean ObjectMapper(自己声明)会完全覆盖默认配置,导致问题
new ObjectMapper()无任何 Spring 配置,序列化失败风险高

🎉 结语

这次问题看似只是一次 LocalDateTime 的序列化异常,实则是由自定义 ObjectMapper Bean 引发的一连串配置冲突。希望这篇文章能帮你更深入理解 Spring Boot 下 Jackson 的配置机制,减少类似踩坑。

如果你对文中提到的 Kafka、Elasticsearch、MongoDB 集成方案感兴趣,欢迎访问我的开源项目 👉 rapid-crud-generator。这是一个基于 JSON Schema 自动生成后端接口和前端页面的工具,集成审计日志系统,适合中后台系统的快速搭建和技术参考。