Spring Boot
Jackson
ZonedDateTime
Serialization
Java

Jackson serializes a ZonedDateTime wrongly in Spring Boot

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

When ZonedDateTime looks wrong in a Spring Boot JSON response, the problem is usually not that Jackson is broken. More often, Jackson is following a default that does not match what you expected about offsets, zone IDs, or timestamp format.

What Jackson Actually Serializes

A ZonedDateTime contains three pieces of information: the local date and time, the offset from UTC, and the named zone such as Europe/Berlin. Those pieces are related, but not identical.

If you serialize a ZonedDateTime without extra configuration, Jackson often emits an ISO-8601 string with an offset. Depending on configuration, it may omit the bracketed zone ID and keep only the offset portion. That can make developers think the zone was lost, even when the instant itself is still correct.

For example, these two strings may represent the same moment:

  • '2026-03-11T10:00:00+01:00'
  • '2026-03-11T09:00:00Z'

They are different textual representations of the same instant. Whether that is “wrong” depends on whether your API contract cares about the original named zone or only the instant in time.

Spring Boot and the Java Time Module

Modern Spring Boot setups usually work best when Jackson has Java time support and date timestamps are written as strings, not numeric arrays or epoch numbers.

java
1import com.fasterxml.jackson.databind.SerializationFeature;
2import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
3import com.fasterxml.jackson.databind.ObjectMapper;
4
5public class Demo {
6    public static void main(String[] args) throws Exception {
7        ObjectMapper mapper = new ObjectMapper();
8        mapper.registerModule(new JavaTimeModule());
9        mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
10
11        System.out.println(mapper.writeValueAsString(
12            java.time.ZonedDateTime.parse("2026-03-11T10:00:00+01:00[Europe/Berlin]")
13        ));
14    }
15}

If your output is an array or a numeric timestamp, this configuration is the first thing to inspect.

In Spring Boot, a clean way to enforce the same behavior application-wide is a customizer bean.

java
1import com.fasterxml.jackson.databind.SerializationFeature;
2import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
3import org.springframework.context.annotation.Bean;
4import org.springframework.context.annotation.Configuration;
5
6@Configuration
7public class JacksonConfig {
8    @Bean
9    Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() {
10        return builder -> builder.featuresToDisable(
11            SerializationFeature.WRITE_DATES_AS_TIMESTAMPS
12        );
13    }
14}

Preserving the Zone ID

Another common surprise is that the output contains the correct offset but not the named zone. If your consumers must see Europe/Berlin instead of just +01:00, enable the feature that writes the zone ID explicitly.

java
1import com.fasterxml.jackson.databind.ObjectMapper;
2import com.fasterxml.jackson.databind.SerializationFeature;
3import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
4
5ObjectMapper mapper = new ObjectMapper()
6    .registerModule(new JavaTimeModule())
7    .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
8    .enable(SerializationFeature.WRITE_DATES_WITH_ZONE_ID);

That distinction matters because an offset tells you the position from UTC at one moment, while a zone ID also carries daylight-saving rules for future or past calculations.

When the Time Zone Seems Shifted

Sometimes the serialized value is not just formatted differently. It is actually shifted. That usually happens because the value was converted earlier in the pipeline, not during serialization itself.

Typical causes include:

  • creating the ZonedDateTime in the wrong zone
  • converting to Instant or OffsetDateTime and later expecting the original named zone back
  • setting a global mapper time zone and assuming it rewrites ZonedDateTime values in a predictable way

If you want a specific zone in the JSON, convert the value deliberately before serialization.

java
1import java.time.ZoneId;
2import java.time.ZonedDateTime;
3
4ZonedDateTime original = ZonedDateTime.parse("2026-03-11T09:00:00Z");
5ZonedDateTime berlin = original.withZoneSameInstant(ZoneId.of("Europe/Berlin"));
6
7System.out.println(berlin);

That makes the intent explicit and avoids relying on serializer side effects.

Choosing the Right Type

If your API only cares about an instant with an offset, OffsetDateTime is often simpler. If the API must preserve a named zone because downstream logic depends on local calendar rules, ZonedDateTime is the better type.

A lot of so-called serialization bugs are actually type-selection bugs. Developers choose ZonedDateTime, but the system only needs UTC instants. Or they choose OffsetDateTime, then later realize the original zone name matters.

Common Pitfalls

The most common mistake is assuming that “same instant” and “same text” are identical requirements. Jackson may serialize the correct instant in a format that looks unfamiliar.

Another frequent problem is forgetting to register Java time support or to disable timestamp serialization. That often produces numeric output or inconsistent formatting.

Developers also expect spring.jackson.time-zone or ObjectMapper#setTimeZone to fully control ZonedDateTime. In practice, date-time types that already embed zone information can behave differently from old java.util.Date workflows.

Finally, if clients truly need the named zone, do not assume the offset alone is enough. Enable zone ID output or serialize a dedicated field that carries the zone name explicitly.

Summary

  • 'ZonedDateTime issues are often caused by mismatched expectations about offsets, instants, and zone IDs.'
  • Register JavaTimeModule and disable WRITE_DATES_AS_TIMESTAMPS for readable JSON.
  • Enable WRITE_DATES_WITH_ZONE_ID if the API must preserve the named zone.
  • Convert to the intended zone before serialization instead of relying on global mapper settings.
  • Use OffsetDateTime when only the instant and offset matter, and ZonedDateTime when the zone rules matter too.

Related reading
Course
Intermediate
27 lessons
14 hours
OOD Fundamentals

Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.