68

I'm trying to deserialize an ISO8601 formatted date into Java8 java.time.Instant using Jackson. I registered JavaTimeModule with the ObjectMapper, and turned off the WRITE_DATES_AS_TIMESTAMPS setting.

However, if one tries to deserialize 2016-03-28T19:00:00.000+01:00 it will not work, because it seems that JavaTimeModule will only deserialize date-times formatted with UTC timezone offset (e.g. 2016-03-28T18:00:00.000Z). I then tried using @JsonFormat annotation like this:

@JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd'T'HH:mm:ss.SSSZ", timezone = "UTC")

And like this:

@JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd'T'HH:mm:ss.SSSZ", timezone = JsonFormat.DEFAULT_TIMEZONE)

However, neither of these work and I get an exception:

com.fasterxml.jackson.databind.JsonMappingException: Unsupported field: YearOfEra (through reference chain: org.example.Article["date"])

Which implies that timezone parameter is ignored and date time formatter doesn't know how to format an Instant without a timezone.

Is there a way to deserialize a ISO8601 string that's not in UTC time zone offset to Java 8 java.time.Instant using Jackson and JavaTimeModule without writing a custom deserializer?

kellyfj
  • 6,586
  • 12
  • 45
  • 66
Krešimir Nesek
  • 5,302
  • 4
  • 29
  • 56
  • strange, I expected this error `Failed to parse Date value '2016-03-28T19:00:00.000+01:00' (format: "yyyy-MM-dd'T'HH:mm:ss.SSSZ"): Unparseable date: "2016-03-28T19:00:00.000+01:00"` and after trying your code, I got this kind of error in both cases. The format is not matching... did you tried it without any annotations? – Meiko Rachimow Mar 27 '16 at 21:36
  • 2
    or with this pattern: yyyy-MM-dd'T'HH:mm:ss.SSSXXX (see: https://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html) – Meiko Rachimow Mar 27 '16 at 21:38
  • Without any annotations it works but only if the offset is specified as Z. It won't work for +0100 or +01:00. I tried your format and I still get Unsupported field: YearOfEra exception, indicating that deserializer's formatter is not configured with a time zone (for some reason java 8 fromatter requires TZ even though the offset is specified in the string and the Instant is fully defined). – Krešimir Nesek Mar 27 '16 at 21:46
  • which version of jackson / jackson-datatype-jsr310 you are using? – Meiko Rachimow Mar 27 '16 at 21:48
  • Jackson 2.6.5 and jackson-datatype-jsr310 2.6.5 – Krešimir Nesek Mar 27 '16 at 22:08
  • Ok - I noticed that there's a separate problem with how I annotated the class (I'm actually using Kotlin which has annotation use-sites which I didn't use correctly). Anyhow, when trying your format string, it worked. Please copy your comment with your pattern to the answer so I can accept it. – Krešimir Nesek Mar 27 '16 at 22:19

5 Answers5

78

You need to set the explicit time zone via XXX in your modell class:

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX")

(see: https://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html)

Meiko Rachimow
  • 4,664
  • 2
  • 25
  • 43
  • 1
    Using the suggested format along with placing annotation at correct place resolved the issue. – Krešimir Nesek Mar 27 '16 at 23:53
  • 3
    `Date` is part of the terrible legacy date-time classes that were years ago supplanted by the modern *java.time* classes. Specifically, `java.util.Date` was replaced by `java.time.Instant`, the class requested in this Question. – Basil Bourque Nov 29 '19 at 19:16
  • 2
    Somewhy it fails for me with `Cannot construct instance of java.text.SimpleDateFormat, problem: Illegal pattern character 'T'` Changing type to `Date` and keeping `@JsonFormat(shape = JsonFormat.Shape.STRING)` helped me, thanks to Guss answer – Buben Ivanov Jan 13 '21 at 10:29
  • 4
    Thanks, this helped me a lot. I suggest making the millisecond part optional though: yyyy-MM-dd'T'HH:mm:ss[.SSS]XXX – Joost Lambregts Feb 03 '22 at 14:38
  • Excellent, @JoostLambregts! Been searching for this! Thank you! – Yves Calaci Aug 03 '22 at 17:21
18

If you want to serialize Date objects into ISO-8601, you don't need to specify a pattern at all - ISO-8601 is the default pattern. It is kind of mentioned in the JsonFormat Java doc:

Common uses include choosing between alternate representations -- for example, whether Date is to be serialized as number (Java timestamp) or String (such as ISO-8601 compatible time value) -- as well as configuring exact details with pattern() property.

[emphasasis mine] you should understand from the above text that specifying shape = STRING would mean an ISO-8601 format but you can choose something else using the pattern property.

In my experience, this always turns out a UTC date format (with the time zone rendered as +0000), which could be the default time zone in my VM (even though my operating system clock is not set to UTC).

Guss
  • 30,470
  • 17
  • 104
  • 128
18

In Jackson 2.9.8 (current one as I'm writing this) it's better to use Instant instead of Date.

You have to add a dependency:

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    <version>2.9.8</version>
</dependency> 

Also, register the module and configure SerializationFeature.WRITE_DATES_AS_TIMESTAMPS to false.

new ObjectMapper()
                .findAndRegisterModules()
                .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);

More information about Jackson for Java8 here: https://github.com/FasterXML/jackson-modules-java8

zoomout
  • 399
  • 3
  • 6
5

Jackson can be configured globally (without annotations) to accept timestamps with or without colon:

ObjectMapper mapper = new ObjectMapper();
mapper.setDateFormat(new StdDateFormat().withColonInTimeZone(true));

The default Jackson timezone format was changed since version 2.11 from '+0000' to '+00:00'. Both formats are valid according to ISO-8601.

Dimitar II
  • 2,299
  • 32
  • 33
  • 1
    They're not both valid. One is basic format, the other extended ISO-8601 format. You cannot mix and match them. – john16384 Oct 08 '20 at 06:50
4

The format "Z" does not work with "+01:00" as this is a different pattern. JsonFormat is using SimpleDateFormat patterns. "Z" in upper case only represents strict RFC 822. You have to use syntax like: "+0100", without colon.

See: ISO 8601:2004, SimpleDateFormat patterns