Introduction
By default, Spring Boot's Jackson serializer converts java.util.Date to a Unix timestamp (milliseconds since epoch) and java.time.LocalDateTime to an array like [2025,9,23,14,30,0]. To get human-readable formats like "2025-09-23T14:30:00", configure the date format either globally in application.properties, per-field with @JsonFormat, or via a custom ObjectMapper bean. The recommended approach for new code is to use java.time classes (LocalDate, LocalDateTime, ZonedDateTime) with the jackson-datatype-jsr310 module, which Spring Boot auto-configures.
The Default Problem
1@RestController
2public class EventController {
3 @GetMapping("/event")
4 public Event getEvent() {
5 Event event = new Event();
6 event.setName("Conference");
7 event.setDate(new Date());
8 event.setLocalDate(LocalDate.now());
9 event.setLocalDateTime(LocalDateTime.now());
10 return event;
11 }
12}
13
14// Default JSON output:
15// {
16// "name": "Conference",
17// "date": 1695484200000, ← Unix timestamp (not readable)
18// "localDate": [2025, 9, 23], ← Array (not standard)
19// "localDateTime": [2025, 9, 23, 14, 30, 0] ← Array
20// }
Fix 1: application.properties (Global)
1# application.properties
2spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
3spring.jackson.time-zone=UTC
4spring.jackson.serialization.write-dates-as-timestamps=false
1# application.yml
2spring:
3 jackson:
4 date-format: "yyyy-MM-dd HH:mm:ss"
5 time-zone: UTC
6 serialization:
7 write-dates-as-timestamps: false
write-dates-as-timestamps=false is the key setting. It tells Jackson to serialize dates as ISO-8601 strings instead of numeric timestamps or arrays.
1// After configuration:
2{
3 "name": "Conference",
4 "date": "2025-09-23 14:30:00",
5 "localDate": "2025-09-23",
6 "localDateTime": "2025-09-23T14:30:00"
7}
1import com.fasterxml.jackson.annotation.JsonFormat;
2import java.time.LocalDateTime;
3import java.util.Date;
4
5public class Event {
6 private String name;
7
8 @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "UTC")
9 private Date date;
10
11 @JsonFormat(pattern = "dd/MM/yyyy")
12 private LocalDate localDate;
13
14 @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
15 private LocalDateTime localDateTime;
16
17 // getters and setters
18}
1{
2 "name": "Conference",
3 "date": "2025-09-23 14:30:00",
4 "localDate": "23/09/2025",
5 "localDateTime": "2025-09-23T14:30:00"
6}
@JsonFormat overrides the global configuration for specific fields. Use it when different fields need different formats.
Fix 3: Custom ObjectMapper Bean
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 mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
16 mapper.setTimeZone(TimeZone.getTimeZone("UTC"));
17 return mapper;
18 }
19}
This gives full control over serialization. Spring Boot uses this ObjectMapper for all JSON processing.
Fix 4: Custom Serializer
1import com.fasterxml.jackson.core.JsonGenerator;
2import com.fasterxml.jackson.databind.JsonSerializer;
3import com.fasterxml.jackson.databind.SerializerProvider;
4import java.io.IOException;
5import java.time.LocalDateTime;
6import java.time.format.DateTimeFormatter;
7
8public class CustomDateTimeSerializer extends JsonSerializer<LocalDateTime> {
9 private static final DateTimeFormatter FORMATTER =
10 DateTimeFormatter.ofPattern("dd-MMM-yyyy HH:mm");
11
12 @Override
13 public void serialize(LocalDateTime value, JsonGenerator gen,
14 SerializerProvider provider) throws IOException {
15 gen.writeString(value.format(FORMATTER));
16 }
17}
18
19// Usage
20public class Event {
21 @JsonSerialize(using = CustomDateTimeSerializer.class)
22 private LocalDateTime eventDate;
23}
24// Output: "eventDate": "23-Sep-2025 14:30"
Handling Deserialization (JSON to Java)
1public class CreateEventRequest {
2 private String name;
3
4 @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
5 private LocalDateTime startTime;
6
7 // Jackson automatically parses "2025-09-23 14:30:00" to LocalDateTime
8}
1// POST /events
2// Body: { "name": "Meeting", "startTime": "2025-09-23 14:30:00" }
3
4@PostMapping("/events")
5public Event createEvent(@RequestBody CreateEventRequest request) {
6 // request.getStartTime() is a LocalDateTime object
7 return eventService.create(request);
8}
The @JsonFormat pattern works for both serialization (Java to JSON) and deserialization (JSON to Java).
Time Zone Handling
1public class Event {
2 // ZonedDateTime includes timezone information
3 @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ssXXX")
4 private ZonedDateTime scheduledAt;
5 // Output: "2025-09-23T14:30:00+00:00"
6
7 // OffsetDateTime for UTC offset
8 @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ssZ")
9 private OffsetDateTime createdAt;
10 // Output: "2025-09-23T14:30:00+0000"
11}
# Force all dates to a specific timezone
spring.jackson.time-zone=America/New_York
Common Pitfalls
Missing jackson-datatype-jsr310: Java 8 date/time classes (LocalDate, LocalDateTime) require the jackson-datatype-jsr310 module. Spring Boot auto-includes it, but if you customize the ObjectMapper, you must register new JavaTimeModule() manually or these types serialize as arrays.
@JsonFormat timezone ignored for LocalDateTime: LocalDateTime has no timezone concept. Setting timezone in @JsonFormat has no effect on it. Use ZonedDateTime or OffsetDateTime when timezone matters.
Date format not applied to deserialization: If your @JsonFormat pattern does not match the incoming JSON string exactly, Jackson throws InvalidFormatException. Ensure the request format matches the pattern precisely.
Global format overridden by @JsonFormat: @JsonFormat on a field takes precedence over application.properties settings. If one field shows a different format, check for a field-level annotation.
Using java.util.Date in new code: java.util.Date is mutable, not timezone-aware, and cumbersome to format. Use java.time.LocalDateTime or java.time.ZonedDateTime for new code — they work better with Jackson and are immutable.
Summary
Set spring.jackson.serialization.write-dates-as-timestamps=false to get ISO-8601 strings instead of timestamps
Use @JsonFormat(pattern = "...") for per-field format control
Spring Boot auto-registers JavaTimeModule — use java.time classes (LocalDate, LocalDateTime, ZonedDateTime)
Configure global format in application.properties or via a custom ObjectMapper bean
Use ZonedDateTime or OffsetDateTime when timezone information is needed
@JsonFormat works for both serialization (output) and deserialization (input)