200

I have a Date format coming from API like this:

"start_time": "2015-10-1 3:00 PM GMT+1:00"

Which is YYYY-DD-MM HH:MM am/pm GMT timestamp. I am mapping this value to a Date variable in POJO. Obviously, its showing conversion error.

I would like to know 2 things:

  1. What is the formatting I need to use to carry out conversion with Jackson? Is Date a good field type for this?
  2. In general, is there a way to process the variables before they get mapped to Object members by Jackson? Something like, changing the format, calculations, etc.
buræquete
  • 14,226
  • 4
  • 44
  • 89
Kevin Rave
  • 13,876
  • 35
  • 109
  • 173
  • 1
    This is really good example, place annotation on field of the class: http://java.dzone.com/articles/how-serialize-javautildate – digz6666 May 22 '14 at 07:28
  • Now they have a wiki page for the date handling: http://wiki.fasterxml.com/JacksonFAQDateHandling – Sutra Oct 26 '14 at 07:49
  • These days you should no longer use the `Date` class. [java.time, the modern Java date and time API,](https://docs.oracle.com/javase/tutorial/datetime/) has replaced it nearly 5 years ago. Use it and [FasterXML/jackson-modules-java8](https://github.com/FasterXML/jackson-modules-java8). – Ole V.V. Nov 17 '18 at 00:59
  • At first, I though this is a bug in my code that objectmapper is converting DateFormat to long until I came here. – KnockingHeads Jun 02 '21 at 10:06

10 Answers10

417

Since Jackson v2.0, you can use @JsonFormat annotation directly on Object members;

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm a z")
private Date date;
buræquete
  • 14,226
  • 4
  • 44
  • 89
Olivier Lecrivain
  • 4,955
  • 2
  • 16
  • 10
  • 76
    If you want to include timezone: `@JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone="GMT")` – realPK Mar 13 '15 at 18:27
  • Hi, Which jar have that annotation. I am using jackson-mapper-asl 1.9.10 version. I am not getting it – krishna Ram Nov 25 '15 at 08:57
  • 2
    @Ramki : jackson-annotations >= 2.0 – Olivier Lecrivain Nov 26 '15 at 15:30
  • 3
    This annotation only works perfect in serialization phase but during deserialization the timezone and locale information not used at all. I have tried timezone="CET" and timezone "Europe/Budapest" with locale="hu" but neither works and cause strange time conversation on Calendar. Only the custom serialization with deserialization worked for me handling timezone as required. Here is the perfect tutorial how you need to use http://www.baeldung.com/jackson-serialize-dates – Miklos Krivan Dec 12 '15 at 07:13
  • 1
    This is a separate jar project called [**Jackson Annotations**](https://github.com/FasterXML/jackson-annotations/wiki/Jackson-Annotations). Sample [pom entry](https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-annotations/2.8.1) – bub Aug 16 '16 at 07:50
  • @OlivierLecrivain this annotation is dangerous because your timezone is not the same of JVM, at least in my experience – deFreitas Feb 01 '17 at 19:35
  • 1
    This wasn't working for me until I added `@JsonSerialize(as = Date.class)` before `@JsonFormat` annotation. – Iqbal May 24 '18 at 10:01
  • If you do not mean UTC time, you must specify the timezone. Jackson's default behavior is to assume UTC, it doesn't derive it from JVM's timezone. Ref: [Java Docs](https://fasterxml.github.io/jackson-databind/javadoc/2.9/com/fasterxml/jackson/databind/util/StdDateFormat.html#DEFAULT_TIMEZONE) – Mohnish Jan 10 '19 at 02:51
  • don't forget to include jackson-datatype-jsr310 dependency in your pom, and register java time module as mapper.registerModule(new JavaTimeModule()); see https://stackoverflow.com/questions/21384820/is-there-a-jackson-datatype-module-for-jdk8-java-time – Alex Dec 11 '19 at 12:56
161

What is the formatting I need to use to carry out conversion with Jackson? Is Date a good field type for this?

Date is a fine field type for this. You can make the JSON parse-able pretty easily by using ObjectMapper.setDateFormat:

DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm a z");
myObjectMapper.setDateFormat(df);

In general, is there a way to process the variables before they get mapped to Object members by Jackson? Something like, changing the format, calculations, etc.

Yes. You have a few options, including implementing a custom JsonDeserializer, e.g. extending JsonDeserializer<Date>. This is a good start.

ccpizza
  • 28,968
  • 18
  • 162
  • 169
pb2q
  • 58,613
  • 19
  • 146
  • 147
  • 2
    12 hour format is better if the format also includes the AM/PM designation: DateFormat df = new SimpleDateFormat("yyyy-MM-dd hh:mm a z"); – John Scattergood Feb 08 '16 at 21:45
  • 1
    Tried all these solutions but was not able to store a Date variable of my POJO into a Map key value, also as Date. I want this to then instantiate a BasicDbObject (MongoDB API) from the Map, and consequently store the variable in a MongoDB DB collection as Date (not as Long or String). Is this even possible? Thank you – RedEagle Aug 04 '16 at 22:36
  • 1
    Is it just as easy to use Java 8 `LocalDateTime` or `ZonedDateTime` instead of `Date`? Since `Date` is basically deprecated (or at least many of its methods), I would like to use those alternatives. – houcros Nov 24 '16 at 16:29
  • The javadocs for setSateFormat() say that this call makes the ObjectMapper no longer Thread safe. I created JsonSerializer and JsonDeserializer classes. – MiguelMunoz Feb 14 '18 at 07:15
  • As the question does not mention `java.util.Date` explicitly I want to point out that this does not work for `java.sql.Date.` See also my answer below. – brass monkey Nov 16 '18 at 16:00
  • It is old but SimpleDateFormat is not thread safe while ObjectMapper is and in most of the scenarios, ObjectMapper is re-used. This can create problems in date parsing, hence, serializer approach seems correct while this one incorrect. – Hammad Dar Jun 23 '21 at 15:41
62

Of course there is an automated way called serialization and deserialization and you can define it with specific annotations (@JsonSerialize,@JsonDeserialize) as mentioned by pb2q as well.

You can use both java.util.Date and java.util.Calendar ... and probably JodaTime as well.

The @JsonFormat annotations not worked for me as I wanted (it has adjusted the timezone to different value) during deserialization (the serialization worked perfect):

@JsonFormat(locale = "hu", shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm", timezone = "CET")

@JsonFormat(locale = "hu", shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm", timezone = "Europe/Budapest")

You need to use custom serializer and custom deserializer instead of the @JsonFormat annotation if you want predicted result. I have found real good tutorial and solution here http://www.baeldung.com/jackson-serialize-dates

There are examples for Date fields but I needed for Calendar fields so here is my implementation:

The serializer class:

public class CustomCalendarSerializer extends JsonSerializer<Calendar> {

    public static final SimpleDateFormat FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm");
    public static final Locale LOCALE_HUNGARIAN = new Locale("hu", "HU");
    public static final TimeZone LOCAL_TIME_ZONE = TimeZone.getTimeZone("Europe/Budapest");

    @Override
    public void serialize(Calendar value, JsonGenerator gen, SerializerProvider arg2)
            throws IOException, JsonProcessingException {
        if (value == null) {
            gen.writeNull();
        } else {
            gen.writeString(FORMATTER.format(value.getTime()));
        }
    }
}

The deserializer class:

public class CustomCalendarDeserializer extends JsonDeserializer<Calendar> {

    @Override
    public Calendar deserialize(JsonParser jsonparser, DeserializationContext context)
            throws IOException, JsonProcessingException {
        String dateAsString = jsonparser.getText();
        try {
            Date date = CustomCalendarSerializer.FORMATTER.parse(dateAsString);
            Calendar calendar = Calendar.getInstance(
                CustomCalendarSerializer.LOCAL_TIME_ZONE, 
                CustomCalendarSerializer.LOCALE_HUNGARIAN
            );
            calendar.setTime(date);
            return calendar;
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
    }
}

and the usage of the above classes:

public class CalendarEntry {

    @JsonSerialize(using = CustomCalendarSerializer.class)
    @JsonDeserialize(using = CustomCalendarDeserializer.class)
    private Calendar calendar;

    // ... additional things ...
}

Using this implementation the execution of the serialization and deserialization process consecutively results the origin value.

Only using the @JsonFormat annotation the deserialization gives different result I think because of the library internal timezone default setup what you can not change with annotation parameters (that was my experience with Jackson library 2.5.3 and 2.6.3 version as well).

Miklos Krivan
  • 1,732
  • 20
  • 14
  • 4
    I have got downvote for my answer yesterday. I worked a lot on this topic so I do not understand. May I have got some feedback to learn from it? I would appreciate some notes in case of downvote. This way we can learn more from each other. – Miklos Krivan Jan 14 '16 at 00:27
  • Great answer, thanks it really helped me! Minor suggestion - consider putting CustomCalendarSerializer and CustomCalendarDeserializer as static classes within an enclosing parent class. I think this would make the code a little nicer :) – Stuart Jan 28 '16 at 19:25
  • @Stuart - you should simply offer this reorganisation of the code as another answer, or propose an edit. Miklos may not have the time free to do it. – ocodo Apr 01 '16 at 02:19
  • @MiklosKrivan I have downvoted you for several reasons. You should know that SimpleDateFormat is not threadsafe and frankily you should use alternative libraries for date formatting (Joda, Commons-lang FastDateFormat etc). The other reason is the setting of the timezone and even locale. It is far more preferable to use GMT in serialization environment and let your client say which timezone it is in or even attach the preferred tz as a separate string. Set your server to be on GMT aka UTC. Jackson has builtin ISO format. – Adam Gent May 06 '16 at 14:29
  • 2
    Thx @AdamGent for your feedback. I understand and accept your suggestions. But in this particular case I just wanted to focus that the JsonFormat annotation with locale information is not working the way we would expect. And how can be solved. – Miklos Krivan May 07 '16 at 09:54
22

To add characters such as T and Z in your date

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'")
private Date currentTime;

output

{
    "currentTime": "2019-12-11T11:40:49Z"
}
Odwori
  • 1,460
  • 13
  • 14
6

Just a complete example for spring boot application with RFC3339 datetime format

package bj.demo;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;

import java.text.SimpleDateFormat;

/**
 * Created by BaiJiFeiLong@gmail.com at 2018/5/4 10:22
 */
@SpringBootApplication
public class BarApp implements ApplicationListener<ApplicationReadyEvent> {

    public static void main(String[] args) {
        SpringApplication.run(BarApp.class, args);
    }

    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
        objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX"));
    }
}
BaiJiFeiLong
  • 3,716
  • 1
  • 30
  • 28
5

Building on @miklov-kriven's very helpful answer, I hope these two additional points of consideration prove helpful to someone:

(1) I find it a nice idea to include serializer and de-serializer as static inner classes in the same class. NB, using ThreadLocal for thread safety of SimpleDateFormat.

public class DateConverter {

    private static final ThreadLocal<SimpleDateFormat> sdf = 
        ThreadLocal.<SimpleDateFormat>withInitial(
                () -> {return new SimpleDateFormat("yyyy-MM-dd HH:mm a z");});

    public static class Serialize extends JsonSerializer<Date> {
        @Override
        public void serialize(Date value, JsonGenerator jgen SerializerProvider provider) throws Exception {
            if (value == null) {
                jgen.writeNull();
            }
            else {
                jgen.writeString(sdf.get().format(value));
            }
        }
    }

    public static class Deserialize extends JsonDeserializer<Date> {
        @Overrride
        public Date deserialize(JsonParser jp, DeserializationContext ctxt) throws Exception {
            String dateAsString = jp.getText();
            try {
                if (Strings.isNullOrEmpty(dateAsString)) {
                    return null;
                }
                else {
                    return new Date(sdf.get().parse(dateAsString).getTime());
                }
            }
            catch (ParseException pe) {
                throw new RuntimeException(pe);
            }
        }
    }
}

(2) As an alternative to using @JsonSerialize and @JsonDeserialize annotations on each individual class member you could also consider overriding Jackson's default serialization by applying the custom serialization at an application level, that is all class members of type Date will be serialized by Jackson using this custom serialization without explicit annotation on each field. If you are using Spring Boot for example one way to do this would as follows:

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    public Module customModule() {
        SimpleModule module = new SimpleModule();
        module.addSerializer(Date.class, new DateConverter.Serialize());
        module.addDeserializer(Date.class, new Dateconverter.Deserialize());
        return module;
    }
}
Stuart
  • 3,226
  • 5
  • 24
  • 28
  • I downvoted you (I hate downvoting but I just don't want people to use your answer). SimpleDateFormat is not threadsafe. It is 2016 (you answered in 2016). You should not be using SimpleDateFormat when there are numerous faster and threadsafe options. There is even an exact misuse of what you are proposing Q/A here: http://stackoverflow.com/questions/25680728/json-serializer-and-thread-safety – Adam Gent May 06 '16 at 14:25
  • 3
    @AdamGent thanks for the pointing this out. In this context, using Jackson, the ObjectMapper class is thread safe so it doesn't matter. However, I do take your point that the code may be copied and used in a non-thread safe context. So I have edited my answer to make access to SimpleDateFormat thread safe. I also acknowledge there are alternatives, primarily the java.time package. – Stuart May 09 '16 at 11:19
2

If anyone has problems with using a custom dateformat for java.sql.Date, this is the simplest solution:

ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(java.sql.Date.class, new DateSerializer());
mapper.registerModule(module);

(This SO-answer saved me a lot of trouble: https://stackoverflow.com/a/35212795/3149048 )

Jackson uses the SqlDateSerializer by default for java.sql.Date, but currently, this serializer doesn't take the dateformat into account, see this issue: https://github.com/FasterXML/jackson-databind/issues/1407 . The workaround is to register a different serializer for java.sql.Date as shown in the code example.

Stephanie
  • 508
  • 5
  • 14
2

I want to point out that setting a SimpleDateFormat like described in the other answer only works for a java.util.Date which I assume is meant in the question. But for java.sql.Date the formatter does not work. In my case it was not very obvious why the formatter did not work because in the model which should be serialized the field was in fact a java.utl.Date but the actual object ended up beeing a java.sql.Date. This is possible because

public class java.sql extends java.util.Date

So this is actually valid

java.util.Date date = new java.sql.Date(1542381115815L);

So if you are wondering why your Date field is not correctly formatted make sure that the object is really a java.util.Date.

Here is also mentioned why handling java.sql.Date will not be added.

This would then be breaking change, and I don't think that is warranted. If we were starting from scratch I would agree with the change, but as things are not so much.

brass monkey
  • 5,841
  • 10
  • 36
  • 61
  • 1
    Thanks for pointing out one consequence of this bad design of the two different `Date` classes. These days one shouldn’t any of them, though, nor `SimpleDateFormat`. [java.time, the modern Java date and time API,](https://docs.oracle.com/javase/tutorial/datetime/) has replaced them nearly 5 years ago. Use it and [FasterXML/jackson-modules-java8](https://github.com/FasterXML/jackson-modules-java8). – Ole V.V. Nov 17 '18 at 01:02
1

Working for me. SpringBoot.

 import com.alibaba.fastjson.annotation.JSONField;

 @JSONField(format = "yyyy-MM-dd HH:mm:ss")  
 private Date createTime;

output:

{ 
   "createTime": "2019-06-14 13:07:21"
}
anson
  • 1,436
  • 14
  • 16
0

If we are having the spring boot application, then one more option thats simple to implement for app wide configuration is to use below in application properties file. You can customize the format as needed.

spring.jackson.date-format=yyyy-MM-dd'T'HH:mm:ss.SSS

NOTE: If using this solution use the Spring Dependency Injection to get the reference of the ObjectMapper class. Cons of not using explicit format is sometimes while upgrading the libraries for jackson code breaks because of change in the format for some versions.

Pankaj
  • 615
  • 5
  • 9