Jackson serializes a ZonedDateTime wrongly in Spring Boot
Solution 1
There is a library jackson-datatype-jsr310. Try it.
This library covers new datetime API and includes serializers for ZonedDateTime
too.
All you need is just to add JavaTimeModule
:
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
UPDATE
To convert datetime to ISO-8601
string you should disable WRITE_DATES_AS_TIMESTAMPS
feature. You can easily do by either overriding ObjectMapper
bean or by using application properties:
spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS = false
Solution 2
If you either don't rely on SpringBoot's auto-configuration feature - you don't provide spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS = false
property into your configuration file - or for whatever reason you create ObjectMapper
instance manually. You can disable this feature programatically as follows:
ObjectMapper m = new ObjectMapper();
m.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
this is for jackson 2.8.7
Solution 3
The answer was already mentioned above but I think it's missing some info. For those looking to parse Java 8 timestamps in many forms (not just ZonedDateTime). You need a recent version of jackson-datatype-jsr310
in your POM and have the following module registered:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
To test this code
@Test
void testSeliarization() throws IOException {
String expectedJson = "{\"parseDate\":\"2018-12-04T18:47:38.927Z\"}";
MyPojo pojo = new MyPojo(ZonedDateTime.parse("2018-12-04T18:47:38.927Z"));
// serialization
assertThat(objectMapper.writeValueAsString(pojo)).isEqualTo(expectedJson);
// deserialization
assertThat(objectMapper.readValue(expectedJson, MyPojo.class)).isEqualTo(pojo);
}
Note that you can configure your object mapper globally in Spring or dropwizard to achieve this. I have not yet found a clean way to do this as an annotation on a field without registering a custom (de)serializer.
Solution 4
For Jackson 2.10
and above,
parent pom.xml
<!-- https://github.com/FasterXML/jackson-bom -->
<dependencyManagement>
<dependency>
<groupId>com.fasterxml.jackson</groupId>
<artifactId>jackson-bom</artifactId>
<version>2.10.3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencyManagement>
module pom.xml
<!-- https://github.com/FasterXML/jackson-modules-java8 -->
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
JsonMapper creation, possibly in your @Configuration
class
@Bean
public JsonMapper jsonMapper() {
return JsonMapper.builder()
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
.addModule(new JavaTimeModule())
.build();
}
Further reading:
jbx
Updated on July 09, 2022Comments
-
jbx almost 2 years
I have a simple application with Spring Boot and Jetty. I have a simple controller returning an object which has a Java 8
ZonedDateTime
:public class Device { // ... private ZonedDateTime lastUpdated; public Device(String id, ZonedDateTime lastUpdated, int course, double latitude, double longitude) { // ... this.lastUpdated = lastUpdated; // ... } public ZonedDateTime getLastUpdated() { return lastUpdated; } }
In my
RestController
I simply have:@RequestMapping("/devices/") public @ResponseBody List<Device> index() { List<Device> devices = new ArrayList<>(); devices.add(new Device("321421521", ZonedDateTime.now(), 0, 39.89011333, 24.438176666)); return devices; }
I was expecting the
ZonedDateTime
to be formatted according to the ISO format, but instead I am getting a whole JSON dump of the class like this:"lastUpdated":{"offset":{"totalSeconds":7200,"id":"+02:00","rules":{"fixedOffset":true,"transitionRules":[],"transitions":[]}},"zone":{"id":"Europe/Berlin","rules":{"fixedOffset":false,"transitionRules":[{"month":"MARCH","timeDefinition":"UTC","standardOffset":{"totalSeconds":3600,"id":"+01:00","rules":{"fixedOffset":true,"transitionRules":[],"transitions":[]}},"offsetBefore":{"totalSeconds":3600,"id":"+01:00","rules":{"fixedOffset":true,"transitionRules":[],"transitions":[]}},"offsetAfter":{"totalSeconds":7200,"id":"+02:00", ...
I just have a
spring-boot-starter-web
application, usingspring-boot-starter-jetty
and excludingspring-boot-starter-tomcat
.Why is Jackson behaving like this in Spring Boot?
** UPDATE **
For those looking for a full step by step guide how to solve this I found this after asking the question: http://lewandowski.io/2016/02/formatting-java-time-with-spring-boot-using-json/
-
jbx over 7 yearsThanks. So I added the
jackson-datatype-jsr310
version 2.8.1 dependency, and the effect was that the timestamp changed to a double"lastUpdated":1471893818.177000000
. In Spring Boot I don't have direct access to theObjectMapper
it is using, any idea where do I tell it to use theJavaTimeModule
? (I am a bit new to Spring Boot) -
vsminkov over 7 years@jbx check this answer - stackoverflow.com/questions/7854030/… also you need to try turning off
WRITE_DATES_AS_TIMESTAMPS
feature to convert datetime to ISO-8601 -
jbx over 7 yearsWas just going to tell you I found the answer. Yes it works, just by
spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS = false
, no need to programmatically customise theObjectMapper
. Since my question was specifically about Spring boot, can you edit your answer to add this so that I choose it as the right answer? -
vsminkov over 7 years@jbx great! glad you solved it completely. Thanks, I'll edit my answer
-
dvelopp almost 7 yearsThanks! It helped.
-
UltimaWeapon over 5 yearsMine just includes a test that shows how to use the mapper. I know it's not much but I wrote the code myselt to test the solution so I figured I might as well add it in case anyone else was wondering.
-
jbx about 4 yearsHow is this different from the other answers?
-
Somu about 4 yearsFrom Jackson
2.10
and aboveObjectMapper
is not recommended any more. This answer is up-to-date with new classes (e.g.JsonMapper
) and methods (e.g.addModule
). -
jbx about 4 yearsOK thanks for the update. In reality changing the property
spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS = false
fromapplication.yml
is enough. -
jojo about 4 yearsFor my solution to my problem, this answer may still be missing the convenience method
.findAndRegisterModules()
, which is functionally equivalent tomapper.registerModules(findModules())
. So when outputting the serialization, it may look like this:new ObjectMapper.findAndRegisterModules().writeValueAsString(requestObject)
, whererequestObject
could be someentity
or parameter. -
MA1 over 2 yearsHow can I disable it for one field only?
-
Janet over 2 yearsthis solution worked for my failed mockMVC test. Thanks!