Java
Spring Boot
OffsetDateTime
Jackson
Date Formatting

Jackson date-format for OffsetDateTime in Spring Boot

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

OffsetDateTime works well in Spring Boot, but date formatting confusion appears when Jackson defaults do not match your API contract. The most common issues are unexpected timezone offsets, numeric timestamps, or inconsistent patterns between fields. A reliable setup combines module configuration, explicit serialization rules, and clear timezone policy.

How Jackson Handles OffsetDateTime by Default

Spring Boot registers JavaTimeModule automatically in most modern setups. With that module enabled, OffsetDateTime is usually serialized as an ISO-8601 string such as 2026-03-04T10:15:30+00:00.

If WRITE_DATES_AS_TIMESTAMPS is enabled, output may become numeric and hard to read. You can enforce string output globally with configuration.

yaml
1# application.yml
2spring:
3  jackson:
4    serialization:
5      write-dates-as-timestamps: false

This keeps API payloads human-readable and stable for clients.

Global Formatter Configuration

When you need one consistent pattern for all OffsetDateTime values, customize Jackson through a bean.

java
1import com.fasterxml.jackson.databind.ObjectMapper;
2import com.fasterxml.jackson.databind.SerializationFeature;
3import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
4import org.springframework.context.annotation.Bean;
5import org.springframework.context.annotation.Configuration;
6
7@Configuration
8public class JacksonConfig {
9
10    @Bean
11    public ObjectMapper objectMapper() {
12        ObjectMapper mapper = new ObjectMapper();
13        mapper.registerModule(new JavaTimeModule());
14        mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
15        return mapper;
16    }
17}

Then define DTO fields clearly:

java
1import java.time.OffsetDateTime;
2
3public record AuditEventDto(
4    String action,
5    OffsetDateTime createdAt
6) {}

For many APIs, ISO-8601 with offset is the safest baseline because it carries timezone context directly.

Field-Level Pattern Control with @JsonFormat

If one field needs a special pattern, annotate only that field. This avoids global side effects.

java
1import com.fasterxml.jackson.annotation.JsonFormat;
2import java.time.OffsetDateTime;
3
4public class InvoiceDto {
5
6    private String id;
7
8    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ssXXX")
9    private OffsetDateTime issuedAt;
10
11    public InvoiceDto() {}
12
13    public InvoiceDto(String id, OffsetDateTime issuedAt) {
14        this.id = id;
15        this.issuedAt = issuedAt;
16    }
17
18    public String getId() { return id; }
19    public OffsetDateTime getIssuedAt() { return issuedAt; }
20}

XXX preserves the numeric offset in the final string, which is critical for cross-region services.

Testing Serialization and Deserialization

Add focused tests so formatting stays stable during upgrades.

java
1import static org.assertj.core.api.Assertions.assertThat;
2
3import com.fasterxml.jackson.databind.ObjectMapper;
4import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
5import org.junit.jupiter.api.Test;
6
7import java.time.OffsetDateTime;
8
9class JacksonOffsetDateTimeTest {
10
11    @Test
12    void serializesWithOffset() throws Exception {
13        ObjectMapper mapper = new ObjectMapper();
14        mapper.registerModule(new JavaTimeModule());
15
16        InvoiceDto dto = new InvoiceDto("inv-1", OffsetDateTime.parse("2026-03-04T11:12:13+02:00"));
17        String json = mapper.writeValueAsString(dto);
18
19        assertThat(json).contains("+02:00");
20    }
21}

A small test like this catches accidental config changes before they hit production clients.

Deserialization Rules and Client Input

Formatting is only half the contract. Your API must also parse inbound timestamps consistently. If clients send offsets, deserialize directly into OffsetDateTime so the original offset is preserved. If clients send local timestamps without offset, reject or normalize them by policy.

java
1import com.fasterxml.jackson.databind.ObjectMapper;
2import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
3import java.time.OffsetDateTime;
4
5ObjectMapper mapper = new ObjectMapper();
6mapper.registerModule(new JavaTimeModule());
7
8String json = ""2026-03-04T11:12:13+02:00"";
9OffsetDateTime value = mapper.readValue(json, OffsetDateTime.class);
10System.out.println(value);

If your domain logic runs in UTC, convert once at service boundaries and keep internal calculations consistent.

Document accepted timestamp formats in your OpenAPI schema so client teams do not guess and accidentally send incompatible values.

Common Pitfalls

A common pitfall is storing LocalDateTime and assuming it has offset information. It does not. If offset matters in APIs, use OffsetDateTime or ZonedDateTime.

Another issue is applying both global and field-level patterns that conflict. Prefer one global standard and annotate only true exceptions.

Timezone drift also appears when server default timezone changes between environments. Keep server timezone policy explicit and avoid relying on machine defaults.

Finally, ensure clients parse the same format you emit. Contract tests between producer and consumer reduce surprises.

Summary

  • Disable timestamp serialization when you want readable date strings.
  • Use JavaTimeModule and a clear global formatting policy.
  • Apply @JsonFormat only for field-specific exceptions.
  • Preserve offsets in output so clients can reconstruct absolute time.
  • Add serialization tests to lock format behavior across framework upgrades.

Course illustration
Course illustration

All Rights Reserved.