Kotlin/Spring Boot中Jackson ObjectMapper单独可用但配置为WebClient组件时JSON解码失败问题
我完全懂你此刻的困惑——明明自定义的ObjectMapper已经注册了JavaTimeModule,直接用它反序列化JSON完全没问题,但WebClient就是报错找不到LocalDate的构造器。这大概率是因为WebClient的ExchangeStrategies配置没正确生效,导致实际反序列化时没用到你配置的自定义ObjectMapper。
问题根源分析
你的报错信息Cannot construct instance of java.time.LocalDate...是Jackson在没有JavaTimeModule时的典型表现。虽然你手动创建了带模块的ObjectMapper并传给了Jackson2JsonDecoder/Encoder,但可能在配置ExchangeStrategies时的写法有细微问题,导致WebClient没有正确应用这些自定义codecs。
看你的代码:
val strategies = ExchangeStrategies.builder().codecs { it.defaultCodecs() .jackson2JsonDecoder(Jackson2JsonDecoder(mapper)) it.defaultCodecs() .jackson2JsonEncoder(Jackson2JsonEncoder(mapper)) }.build()
虽然两次调用it.defaultCodecs()返回的是同一个对象,但这种写法可能让配置没被正确绑定(比如代码块的执行顺序或Spring内部的配置逻辑问题)。
解决方案
方案1:简化WebClient配置,直接在Builder中配置Codecs
跳过单独创建ExchangeStrategies的步骤,直接在WebClient.Builder里配置codecs,这样能确保自定义的解码器/编码器被正确应用:
private val mapper = jacksonObjectMapper().registerModule(JavaTimeModule()) val client = WebClient.builder() .codecs { configurer -> val defaultCodecs = configurer.defaultCodecs() // 替换默认的Jackson解码器 defaultCodecs.jackson2JsonDecoder(Jackson2JsonDecoder(mapper)) // 替换默认的Jackson编码器 defaultCodecs.jackson2JsonEncoder(Jackson2JsonEncoder(mapper)) } .build()
方案2:显式指定媒体类型(可选增强)
如果接口返回的Content-Type有特殊情况(比如带UTF-8后缀),可以显式指定解码器支持的媒体类型,避免WebClient fallback到其他解码器:
val supportedMediaTypes = listOf(MediaType.APPLICATION_JSON, MediaType.APPLICATION_JSON_UTF8) val client = WebClient.builder() .codecs { configurer -> val defaultCodecs = configurer.defaultCodecs() defaultCodecs.jackson2JsonDecoder(Jackson2JsonDecoder(mapper, *supportedMediaTypes.toTypedArray())) defaultCodecs.jackson2JsonEncoder(Jackson2JsonEncoder(mapper, *supportedMediaTypes.toTypedArray())) } .build()
方案3:利用Spring Boot自动配置(推荐)
如果你是在Spring Boot环境中,其实可以不用手动创建WebClient和ObjectMapper,直接注入WebClient.Builder,它会自动使用Spring上下文里配置好的ObjectMapper(包括你注册的JavaTimeModule)。
首先确保你的ObjectMapper被注册为Bean:
@Bean fun objectMapper(): ObjectMapper { return jacksonObjectMapper().registerModule(JavaTimeModule()) }
然后注入WebClient.Builder并构建客户端:
@Autowired lateinit var webClientBuilder: WebClient.Builder val client = webClientBuilder.build()
这种方式能让Spring统一管理ObjectMapper,避免手动配置WebClient时的疏漏。
验证
修改配置后,再执行你的请求:
client .get() .uri("你的接口URL") .retrieve() .bodyToMono(FooModel::class.java)
应该就能正确反序列化LocalDate字段了。
内容的提问来源于stack exchange,提问作者Adriano Todaro

