Spring Boot 4与 Jackson 3在 ADJUST_DATES_TO_CONTEXT_TIME_ZONE上的问题
在Spring Boot 4中似乎进行了较大重构,我想使用OffsetDateTime,但它总是被转换为UTC,这是默认值,但我想禁用默认设置。此外,这似乎只在网络客户端上出现这个问题。下面给出分解,我的Spring HTTP接口正在接收这个JSON Web服务
{
"data": {
"id": 1,
"content": "Hello, test!",
"createdAt": "2026-04-19T08:30:00-04:00",
"createdAt2": "2026-04-19T08:30:00-04:00"
}
}
我的控制器方法
@GetMapping("/data")
public DataRecord getData() {
return dataClient.getData();
}
Http接口
@HttpExchange(url = "http://localhost:3000")
public interface DataClient {
@GetExchange("/data")
DataRecord getData();
}
Record
public record DataRecord(
@JsonProperty("id") Long id,
@JsonProperty("content") String content,
@JsonFormat(without = JsonFormat.Feature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE, with = {})
@JsonProperty("createdAt") OffsetDateTime createdAt,
@JsonProperty("createdAt2") OffsetDateTime createdAt2
) {}
以及我在application.properties中的设置
# Old way
#spring.jackson.deserialization.adjust-dates-to-context-time-zone=false
# New way
spring.jackson.datatype.datetime.adjust-dates-to-context-time-zone=false
spring.jackson.datatype.datetime.write-dates-with-context-time-zone=false
这些设置应该保留原始偏移量,但它们不起作用。相反,在调用控制器时我得到如下结果
{
"id": 1,
"content": "Hello, test!",
"createdAt": "2026-04-19T08:30:00-04:00",
"createdAt2": "2026-04-19T12:30:00Z"
}
The createdAt2日期被转换为UTC即使它被设定为不这样做?我发现唯一有效的是在createdAt字段上的这个注解
@JsonFormat(without = JsonFormat.Feature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE, with = {})
@JsonProperty("createdAt") OffsetDateTime createdAt
至少这解释了为何createdAt至少是正确的。不过我在找一个全局设置,这样就不需要在每个OffsetDateTime字段上都标注注解。可行吗?
编辑: 我创建了一个简单的GitHub仓库来测试这个问题 https://github.com/peterfk/spring-boot-4-rest-client-offsetdatetime-bug
解决方案
The issue with Spring Boot 4 + Jackson 3 and ADJUST_DATES_TO_CONTEXT_TIME_ZONE
在Spring Boot 4中,用于JavaTime模块配置的 application.properties 方式发生了显著变化。属性:
spring.jackson.datatype.datetime.adjust-dates-to-context-time-zone=false
属性可能无法可靠地应用到HTTP接口客户端,因为它们使用一个单独配置的 HttpServiceProxyFactory,它有自己的 WebClient,根据你的设置,可能无法拾取自动配置的 ObjectMapper。
最可靠的解决方案是显式地配置一个全局的 ObjectMapper bean,使用你需要的JavaTimeModule设置,或者一个更简单、干净的等效实现:
@Configuration
public class JacksonConfig {
@Bean
@Primary
public ObjectMapper objectMapper() {
return JsonMapper.builder()
.addModule(new JavaTimeModule())
.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)
.disable(SerializationFeature.WRITE_DATES_WITH_CONTEXT_TIME_ZONE)
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.build();
}
}Then make sure your HttpServiceProxyFactory uses this ObjectMapper explicitly:
java@Configuration
public class WebClientConfig {
@Bean
public DataClient dataClient(ObjectMapper objectMapper) {
// Ensure WebClient uses your configured ObjectMapper
WebClient webClient = WebClient.builder()
.codecs(configurer -> configurer
.defaultCodecs()
.jackson2JsonDecoder(
new Jackson2JsonDecoder(objectMapper)
)
)
.build();
HttpServiceProxyFactory factory = HttpServiceProxyFactory
.builderFor(WebClientAdapter.create(webClient))
.build();
return factory.createClient(DataClient.class);
}
}
为什么会这样工作:HTTP接口客户端(@HttpExchange)使用它们自己的编解码流水线。即使你的全局 ObjectMapper 配置正确,下面的 WebClient 仍可能使用默认编解码器,除非你显式地将你的 ObjectMapper 注入其中。这很可能解释了字段上的 @JsonFormat 至少在字段级别是有效的,但全局属性却不起作用。