skip to content

How does Spring Boot serialize java.time types like LocalDateTime, and how do you control the exact format?

level: middleimportance: must knowfreq 68%

answer

  1. jsr310 = JavaTimeModule, auto-registered by Boot
  2. Boot disables WRITE_DATES_AS_TIMESTAMPS -> ISO-8601
  3. @JsonFormat(shape=STRING, pattern, timezone)
  4. @JsonFormat is symmetric (also parses @RequestBody)
  5. Instant needs timezone to format with a pattern

basics

~10 s

The JavaTimeModule (jackson-datatype-jsr310) handles java.time types, and Spring Boot disables WRITE_DATES_AS_TIMESTAMPS so they serialize as ISO-8601 strings. For a specific field, override with @JsonFormat(pattern = ...).

solid answer

~40 s

Java 8 date/time types (LocalDate, LocalDateTime, Instant, ZonedDateTime) aren't handled by core Jackson; they need the JavaTimeModule from jackson-datatype-jsr310, which is on the classpath via spring-boot-starter-json and auto-registered by Jackson2ObjectMapperBuilder. By default Jackson would write them as numeric arrays/epochs, but Spring Boot disables SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, so you get ISO-8601 strings like 2026-07-22T10:15:30. To globally force a pattern or timezone use spring.jackson.date-format and spring.jackson.time-zone (note date-format mainly affects java.util.Date). For per-field control, annotate with @JsonFormat(shape = STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "UTC"), which works for both serialization and @RequestBody parsing. Without the jsr310 module you'd get a serialization failure or ugly numeric output.

code

java · 12 lines
java
public class OrderDto {
    // Boot default: serialized as ISO-8601 string "2026-07-22T10:15:30"
    private LocalDateTime createdAt;

    // Explicit custom format, used for BOTH output and @RequestBody parsing
    @JsonFormat(shape = JsonFormat.Shape.STRING,
                pattern = "yyyy-MM-dd HH:mm:ss",
                timezone = "UTC")
    private Instant paidAt;

    // getters/setters omitted
}

go deeper

for a junior

Know java.time needs JavaTimeModule and Boot gives ISO strings; use @JsonFormat for a custom pattern.

for a middle

Explain jsr310 auto-registration, the timestamps feature toggle, and symmetric @JsonFormat semantics.

for a senior

Discuss global vs per-field format control, Instant+timezone edge cases, and interoperability trade-offs of ISO vs custom patterns.

for a principal

Set org-wide date conventions (ISO-8601, UTC), avoid brittle custom patterns, and reason about contract stability across clients.

## The problem Core Jackson (`jackson-databind`) has no built-in knowledge of the Java 8 `java.time` package (JSR-310): `LocalDate`, `LocalDateTime`, `LocalTime`, `Instant`, `OffsetDateTime`, `ZonedDateTime`, `Duration`, `Period`. Trying to serialize them without support throws `InvalidDefinitionException` (often 'Java 8 date/time type not supported by default') or, if it falls back to bean introspection, emits confusing nested numbers. ## The fix: JavaTimeModule `com.fasterxml.jackson.datatype.jsr310.JavaTimeModule` (artifact `jackson-datatype-jsr310`) teaches Jackson to handle these types. In Spring Boot it's on the classpath transitively via `spring-boot-starter-json`, and `Jackson2ObjectMapperBuilder` auto-registers well-known modules it finds (equivalent to `findAndRegisterModules`). So you normally don't register it yourself. ## Timestamps vs ISO-8601 strings Raw Jackson defaults `SerializationFeature.WRITE_DATES_AS_TIMESTAMPS` to **true**, which renders a `LocalDateTime` as a numeric array `[2026,7,22,10,15,30]` and `Instant` as an epoch decimal. Spring Boot **disables** this feature, so java.time serializes as human-readable ISO-8601 strings (`2026-07-22T10:15:30`, `2026-07-22T10:15:30Z`). If you re-enable it via `spring.jackson.serialization.write-dates-as-timestamps=true` you get numbers again. ## Global format controls - `spring.jackson.date-format=yyyy-MM-dd HH:mm:ss` — sets a `SimpleDateFormat`/pattern; note this primarily governs legacy `java.util.Date`/`Calendar`. It does **not** cleanly override ISO output for all java.time types. - `spring.jackson.time-zone=UTC` — the timezone used when a zone must be applied. - `spring.jackson.locale` — locale for formatting. ## Per-field control: @JsonFormat `@JsonFormat` on a field/getter is the precise tool: ```java @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "UTC") private LocalDateTime createdAt; ``` - `shape = STRING` forces textual output even if timestamps are enabled. - `pattern` sets an exact format (parsed by `DateTimeFormatter`). - `timezone` applies to zone-aware types like `Instant`/`ZonedDateTime` (has no effect on zone-less `LocalDateTime` for offset, but governs conversion where relevant). - It's **symmetric**: the same annotation drives `@RequestBody` deserialization, so inbound JSON must match the pattern. ## Edge cases & gotchas - Forgetting the jsr310 dependency in a non-Boot or trimmed setup -> serialization failure. Boot users rarely hit this. - A custom `pattern` without a `timezone` on `Instant` can throw because an `Instant` needs a zone to be formatted — supply `timezone`. - Nanosecond precision: `SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS` affects numeric output; irrelevant once you use STRING/ISO. - Mixing a global `date-format` with `@JsonFormat` — the annotation wins for that field. - Deserialization strictness: an inbound value not matching the pattern throws during `@RequestBody` binding (400 via Spring's converter). ## When to use what - Rely on Boot defaults (ISO-8601) for standard APIs — it's interoperable and unambiguous. - Use `@JsonFormat` only when a client contract demands a specific non-ISO format or timezone. - Avoid global `date-format` for java.time; prefer per-field annotations or leaving ISO defaults.

  • Why might LocalDateTime serialize as [2026,7,22,10,15,30] instead of a string?
    Because SerializationFeature.WRITE_DATES_AS_TIMESTAMPS is enabled. Spring Boot disables it by default; if you re-enabled it (or built a raw ObjectMapper without disabling it), you get the numeric array.
  • Does @JsonFormat affect deserialization of @RequestBody too?
    Yes. @JsonFormat is symmetric: the same pattern/timezone is applied when parsing inbound JSON, so the request payload must match the declared format or binding fails.

saying these in an interview costs you the question

  • Claiming core Jackson supports java.time out of the box
  • Thinking spring.jackson.date-format reliably formats all java.time types
  • Believing @JsonFormat only affects output, not input parsing
  • Not knowing Boot disables WRITE_DATES_AS_TIMESTAMPS

context