Dropwizard Jackson deserializes offset‑less LocalDateTime as UTC, causing regional time shifts
0 reputation · 09 May 2023, 07:31 UTC
0 reputation · 09 May 2023, 07:31 UTC
When a JSON payload contains a LocalDateTime or ZonedDateTime without an explicit offset (e.g., "2026-10-10T14:30:00"), Dropwizard’s configured ObjectMapper interprets the value in UTC. This behavior is implicit because the framework registers the JavaTimeModule and disables timestamp serialization, but it never sets a default deserialization timezone on the mapper.
Define a consistent, application‑wide default timezone for deserializing offset‑less date‑time values so that APIs accepting local times behave predictably for users in different regions, while avoiding side‑effects on fields that intentionally rely on UTC.
ObjectMapper timezone globally affects all date‑time fields and may break libraries expecting UTC.@JsonFormat annotations are verbose and error‑prone for large codebases.What is the recommended approach to set a default deserialization timezone for all java.time fields? Can a custom ObjectMapper provider be installed without unintended side effects? Are there plans to add a configuration property for this behavior?
29580 reputation · 09 May 2023, 12:56 UTC
Use a custom Jackson module that supplies deserializers for LocalDateTime, ZonedDateTime and OffsetDateTime. When the incoming string lacks an offset/zone, the deserializer applies a configured default ZoneId; otherwise it delegates to the standard JavaTimeModule deserializer. Register this module with Dropwizard’s ObjectMapper via environment.jackson().registerModule(customModule) (or an @Provider returning an ObjectMapper that already contains the module). This avoids the side‑effects of a global setTimeZone call.
Calling objectMapper.setTimeZone(TimeZone.getTimeZone("America/New_York")) changes the interpretation of every offset‑less date‑time value, including fields that are intentionally meant to stay in UTC (e.g., timestamps annotated with @JsonFormat(shape = STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'")). Libraries or internal code that expect UTC instants will receive shifted values, leading to subtle bugs.
com.fasterxml.jackson.databind.Module (or use SimpleModule).ZoneId defaultZone = ZoneId.of("America/New_York"); // <-- replace with your desired zone
SimpleModule module = new SimpleModule();
module.addDeserializer(LocalDateTime.class, new JsonDeserializer() {
@Override
public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
String text = p.getText().trim();
if (text.contains("Z") || text.contains("+") || text.contains("-")) {
// offset present – delegate to default deserializer
return p.readValueAs(LocalDateTime.class);
}
// no offset – apply default zone and convert to LocalDateTime
return LocalDateTime.ofInstant(Instant.from(DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(text + defaultZone.getId())), defaultZone);
}
});
// Similar overrides for ZonedDateTime and OffsetDateTime, delegating when offset/zone present
public void run(MyConfiguration config, Environment environment) {
environment.jackson().registerModule(module);
// ... other setup
}
{"eventTime":"2026-10-10T14:30:00"}. Confirm that the resulting LocalDateTime (or ZonedDateTime) equals the instant you obtain by applying your chosen default zone to that string.{"eventTime":"2026-10-10T14:30:00+05:30"} or "...Z". Verify that the parsed value retains that offset and is not shifted by the default zone.@JsonFormat(shape = STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'")) to ensure they still deserialize to the same instant as before the module was added.To finalize the recommendation, please specify the ZoneId you wish to use as the default for offset‑less values (e.g., ZoneId.of("America/New_York")).
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.