231

For java.util.Date when I do

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd/MM/yyyy")  
  private Date dateOfBirth;

then in JSON request when I send

{ {"dateOfBirth":"01/01/2000"} }  

it works.

How should I do this for Java 8's LocalDate field??

I tried having

@JsonDeserialize(using = LocalDateDeserializer.class)  
@JsonSerialize(using = LocalDateSerializer.class)  
private LocalDate dateOfBirth;  

It didn't work.

Can someone please let me know what's the right way to do this..

Below are dependencies

<dependency>
    <groupId>org.jboss.resteasy</groupId>
    <artifactId>jaxrs-api</artifactId>
     <version>3.0.9.Final</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.jaxrs</groupId>
    <artifactId>jackson-jaxrs-json-provider</artifactId>
    <version>2.4.2</version>
</dependency>
<dependency>
    <groupId>com.wordnik</groupId>
    <artifactId>swagger-annotations</artifactId>
    <version>1.3.10</version>
</dependency>

17 Answers 17

164

I was never able to get this to work simple using annotations. To get it to work, I created a ContextResolver for ObjectMapper, then I added the JSR310Module (update: now it is JavaTimeModule instead), along with one more caveat, which was the need to set write-date-as-timestamp to false. See more at the documentation for the JSR310 module. Here's an example of what I used.

Dependency

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

Note: One problem I faced with this is that the jackson-annotation version pulled in by another dependency, used version 2.3.2, which cancelled out the 2.4 required by the jsr310. What happened was I got a NoClassDefFound for ObjectIdResolver, which is a 2.4 class. So I just needed to line up the included dependency versions

ContextResolver

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JSR310Module;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;

@Provider
public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {  
    private final ObjectMapper MAPPER;

    public ObjectMapperContextResolver() {
        MAPPER = new ObjectMapper();
        // Now you should use JavaTimeModule instead
        MAPPER.registerModule(new JSR310Module());
        MAPPER.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
    }

    @Override
    public ObjectMapper getContext(Class<?> type) {
        return MAPPER;
    }  
}

Resource class

@Path("person")
public class LocalDateResource {

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Response getPerson() {
        Person person = new Person();
        person.birthDate = LocalDate.now();
        return Response.ok(person).build();
    }

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    public Response createPerson(Person person) {
        return Response.ok(
                DateTimeFormatter.ISO_DATE.format(person.birthDate)).build();
    }

    public static class Person {
        public LocalDate birthDate;
    }
}

Test

curl -v http://localhost:8080/api/person
Result: {"birthDate":"2015-03-01"}

curl -v -POST -H "Content-Type:application/json" -d "{\"birthDate\":\"2015-03-01\"}" http://localhost:8080/api/person
Result: 2015-03-01


See also here for JAXB solution.

UPDATE

The JSR310Module is deprecated as of version 2.7 of Jackson. Instead, you should register the module JavaTimeModule. It is still the same dependency.

9
  • 2
    Hi Peeskillet , the field birthDate , is being generated as "birthDate ": { "year": 0, "month": "Month", "dayOfMonth": 0, "dayOfWeek": "DayOfWeek", "era": { "value": 0 }, "dayOfYear": 0, "leapYear": false, "monthValue": 0, "chronology": { "id": "", "calendarType": "" } } how can i make it just as "birthDate"???
    – JAB
    Commented Mar 2, 2015 at 8:07
  • Check the ContextResolver is called. Add a print statement in the getContext method. If this method is called, I don't see a reason for this not to work. If it's not called, then it may be something that's needs to be fixed with the app configuration. For that I would need to see more than what you have provided. Like Resteasy version, dependencies, app config either web.xml or Application subclass. Basically enough to reproduce the problem Commented Mar 2, 2015 at 8:18
  • ContextResolver is not being called Peeskillet . I am resgistering it in web.xml as <context-param> <param-name>resteasy.resources</param-name> <param-value>com.bac.ObjectMapperContextResolver</param-value> </context-param> updated question for dependencies i am using
    – JAB
    Commented Mar 2, 2015 at 9:08
  • Swagger seems to be the issue. I would say to disable it but seeing from this question there is an issue which has been filed, with a conflict between Swagger's ObjectMapper and trying to use your own. You can try and disable theirs, and in the ContextResolver, set all the configurations to the ObjectMapper as swagger does (you can see a link in the question). I don't know as I don't work with swagger much. But I think swagger is the main problem, why the contextresolver is not being called. Commented Mar 2, 2015 at 9:22
  • 1
    The class com.fasterxml.jackson.datatype.jsr310.JSR310Module is deprecated as of version 2.5, recommended is using the newer com.fasterxml.jackson.datatype.jsr310.JavaTimeModule. Commented Nov 29, 2016 at 15:50
129
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
4
  • 2
    new com.fasterxml.jackson.datatype.jsr310.JSR310Module() for version 2.5.4 of Jackson. JavaTimeModule class doesn't exist in this version. Commented Feb 16, 2017 at 13:42
  • 1
    This answer also works for LocalDateTime (jackson 2.9.5). 1 additional dependency required, so my build.sbt looks like: "com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.9.5", "com.fasterxml.jackson.datatype" % "jackson-datatype-jsr310" % "2.9.5"
    – ruhong
    Commented Jun 20, 2018 at 13:34
  • 1
    This pointed me in the right direction, Thank you! I would add that in spring-boot all you need to do is add the following to application.properties: spring.jackson.serialization.write-dates-as-timestamps= false Commented Jun 18, 2019 at 9:28
  • yes, ObjectMapper bean did the trick. Thanks! Commented Aug 1, 2021 at 11:51
119

@JsonSerialize and @JsonDeserialize worked fine for me. They eliminate the need to import the additional jsr310 module:

@JsonDeserialize(using = LocalDateDeserializer.class)  
@JsonSerialize(using = LocalDateSerializer.class)  
private LocalDate dateOfBirth;

Deserializer:

public class LocalDateDeserializer extends StdDeserializer<LocalDate> {

    private static final long serialVersionUID = 1L;

    protected LocalDateDeserializer() {
        super(LocalDate.class);
    }


    @Override
    public LocalDate deserialize(JsonParser jp, DeserializationContext ctxt)
            throws IOException, JsonProcessingException {
        return LocalDate.parse(jp.readValueAs(String.class));
    }

}

Serializer:

public class LocalDateSerializer extends StdSerializer<LocalDate> {

    private static final long serialVersionUID = 1L;

    public LocalDateSerializer(){
        super(LocalDate.class);
    }

    @Override
    public void serialize(LocalDate value, JsonGenerator gen, SerializerProvider sp) throws IOException, JsonProcessingException {
        gen.writeString(value.format(DateTimeFormatter.ISO_LOCAL_DATE));
    }
}
4
  • 16
    Those classes are included in jackson-datatype-jsr310. No need to manually define them in your project.
    – NeuroXc
    Commented Jul 23, 2017 at 17:57
  • 2
    This solution worked for me, using the serializers in jackson-datatype-jsr310.
    – dave
    Commented Mar 17, 2018 at 23:32
  • 5
    If you use serializers and deserializers in jackson-datatype-jsr310, better add @JsonFormat(shape = JsonFormat.Shape.STRING) to your field. Without this format, the value will be serialized as [year, month, day], although deserialization will work.
    – Jian Chen
    Commented Jan 13, 2019 at 2:20
  • this answer doesn't work for me Jackson version 2.9.0
    – alex
    Commented Jun 30, 2021 at 20:38
71

In Spring Boot web app, with Jackson and JSR 310 version "2.8.5"

compile "com.fasterxml.jackson.core:jackson-databind:2.8.5"
runtime "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.8.5"

The @JsonFormat works:

import com.fasterxml.jackson.annotation.JsonFormat;

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
private LocalDate birthDate;
5
  • 2
    Does this work for deserialization? or only serialization? Not having success with deserialization
    – rewolf
    Commented Mar 9, 2017 at 6:25
  • 8
    I had to explicitly declare the deserializer @JsonDeserialize(using= LocalDateDeserializer.class)
    – rewolf
    Commented Mar 12, 2017 at 22:55
  • @JsonFormat just for changing output data format. stackoverflow.com/a/53251526/816759 works perfect with @JsonFormat, @JsonDeserialize, @JsonSerialize
    – Baha
    Commented Dec 4, 2019 at 20:33
  • 1
    In Spring Boot, once you add the JSR310 dependency, all you need to do is add spring.jackson.serialization.write-dates-as-timestamps=false to your application.properties, and it formats it in yyyy-MM-dd automatically. No need for @JsonFormat
    – esfandia
    Commented Mar 29, 2020 at 6:25
  • 1
    Simplest solution. Commented Dec 21, 2020 at 19:25
54

The simplest solution (which supports deserialization and serialization as well) is

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd/MM/yyyy")
@JsonDeserialize(using = LocalDateDeserializer.class)
@JsonSerialize(using = LocalDateSerializer.class)
private LocalDate dateOfBirth;

While using the following dependencies in your project.

Maven

<dependency>
   <groupId>com.fasterxml.jackson.core</groupId>
   <artifactId>jackson-databind</artifactId>
   <version>2.9.7</version>
</dependency>
<dependency>
   <groupId>com.fasterxml.jackson.datatype</groupId>
   <artifactId>jackson-datatype-jsr310</artifactId>
   <version>2.9.7</version>
</dependency>

Gradle

compile "com.fasterxml.jackson.core:jackson-databind:2.9.7"
compile "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.7"

No additional implementation of a ContextResolver, Serializer or Deserializer is required.

5
  • Brilliant, far and away the easiest. FYI for anyone with lots of dependencies, I had to update some other libraries which incorporated jackson annotations.
    – brt
    Commented Jan 28, 2019 at 19:11
  • This answer is the closest i got to fix my problem. Serialization is working, but deserialization is failing because of the pattern I used with @JsonFormat i think (@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy_HH:mm:SS").
    – fsakiyama
    Commented Apr 24, 2019 at 14:50
  • If you have a failed deserialization, most likely is your ObjectMapper doesn't have JavaTimeModule registered. If your ObjectMapper instance is provided from spring/MessageConverter framework. They did some magic to wire them up. In other case, should registerModule to enable LocalDateDeserializerby default for all "LocalDate" in POJO
    – Dennis C
    Commented Jun 11, 2020 at 9:33
  • 1
    After looking at so many solution, this worked for me. For me date was in "yyyyMMdd" format and it worked like charm. Thanks Commented Jul 1, 2021 at 14:34
  • This was the only thing that worked for me, I have ObjectMapper's sprinkled throughout my code base, and the error doesn't tell me which one to edit, and I think this doesn't required registering JavaTimeModule Commented Apr 24, 2023 at 18:01
44
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
private LocalDateTime createdDate;
3
  • 2
    This works. Also requires the dependency for jackson-datatype-jsr310
    – W0lfw00ds
    Commented Sep 23, 2020 at 9:23
  • 2
    You saved my weekend!
    – SGuru
    Commented Nov 20, 2020 at 16:48
  • 1
    I just had the same issue and this solution works perfectly. Thanks @slisnychyi Commented May 23, 2021 at 20:16
23

Since LocalDateSerializer turns it into "[year,month,day]" (a json array) rather than "year-month-day" (a json string) by default, and since I don't want to require any special ObjectMapper setup (you can make LocalDateSerializer generate strings if you disable SerializationFeature.WRITE_DATES_AS_TIMESTAMPS but that requires additional setup to your ObjectMapper), I use the following:

imports:

import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;

code:

// generates "yyyy-MM-dd" output
@JsonSerialize(using = ToStringSerializer.class)
// handles "yyyy-MM-dd" input just fine (note: "yyyy-M-d" format will not work)
@JsonDeserialize(using = LocalDateDeserializer.class)
private LocalDate localDate;

And now I can just use new ObjectMapper() to read and write my objects without any special setup.

2
  • 3
    One thing I'd like to add is to pass date as "2018-12-07" instead of "2018-12-7" else you'll get an error.
    – Kid101
    Commented Dec 6, 2018 at 15:08
  • 1
    Correct, it works with yyyy-MM-dd (2 digit month and day) format, not yyyy-M-d (1 digit month or day) format.
    – Shadow Man
    Commented Dec 10, 2018 at 22:58
20

Simplest and shortest so far:

@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate localDate;

@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime localDateTime;

no dependency required with Spring boot >= 2.2+

0
13

The following annotation worked fine for me.

No extra dependencies needed.

    @JsonProperty("created_at")
    @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX")
    @JsonDeserialize(using = LocalDateTimeDeserializer.class)
    @JsonSerialize(using = LocalDateTimeSerializer.class)
    private LocalDateTime createdAt;
0
11

Just an update of Christopher answer.

Since the version 2.6.0

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

Use the JavaTimeModule instead of JSR310Module (deprecated).

@Provider
public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {  
    private final ObjectMapper MAPPER;

    public ObjectMapperContextResolver() {
        MAPPER = new ObjectMapper();
        MAPPER.registerModule(new JavaTimeModule());
        MAPPER.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
    }

    @Override
    public ObjectMapper getContext(Class<?> type) {
        return MAPPER;
    }  
}

According to the documentation, the new JavaTimeModule uses same standard settings to default to serialization that does NOT use Timezone Ids, and instead only uses ISO-8601 compliant Timezone offsets.

Behavior may be changed using SerializationFeature.WRITE_DATES_WITH_ZONE_ID

1
  • This helped me. In my case, I needed to add the MAPPER.registerModule(new JavaTimeModule()); line. It let me format LocalDate objects as "2020-02-20" format. I didn't need the MAPPER.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); line, for what I was looking for
    – Cuga
    Commented May 21, 2020 at 14:55
11

https://stackoverflow.com/a/53251526/1282532 is the simplest way to serialize/deserialize property. I have two concerns regarding this approach - up to some point violation of DRY principle and high coupling between pojo and mapper.

public class Trade {
    @JsonFormat(pattern = "yyyyMMdd")
    @JsonDeserialize(using = LocalDateDeserializer.class)
    @JsonSerialize(using = LocalDateSerializer.class)
    private LocalDate tradeDate;
    @JsonFormat(pattern = "yyyyMMdd")
    @JsonDeserialize(using = LocalDateDeserializer.class)
    @JsonSerialize(using = LocalDateSerializer.class)
    private LocalDate maturityDate;
    @JsonFormat(pattern = "yyyyMMdd")
    @JsonDeserialize(using = LocalDateDeserializer.class)
    @JsonSerialize(using = LocalDateSerializer.class)
    private LocalDate entryDate;
}

In case you have POJO with multiple LocalDate fields it's better to configure mapper instead of POJO. It can be as simple as https://stackoverflow.com/a/35062824/1282532 if you are using ISO-8601 values ("2019-01-31")

In case you need to handle custom format the code will be like this:

ObjectMapper mapper = new ObjectMapper();
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyyMMdd")));
javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyyMMdd")));
mapper.registerModule(javaTimeModule);

The logic is written just once, it can be reused for multiple POJO

10

As of 2020 and Jackson 2.10.1 there's no need for any special code, it's just a matter of telling Jackson what you want:

ObjectMapper objectMapper = new ObjectMapper();

// Register module that knows how to serialize java.time objects
// Provided by jackson-datatype-jsr310
objectMapper.registerModule(new JavaTimeModule());

// Ask Jackson to serialize dates as String (ISO-8601 by default)
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

This has already been mentioned in this answer, I'm adding a unit test verifying the functionality:

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.Data;
import org.junit.jupiter.api.Test;

import java.time.LocalDate;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class LocalDateSerializationTest {

    @Data
    static class TestBean {
        // Accept default ISO-8601 format
        LocalDate birthDate;
        // Use custom format
        @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd/MM/yyyy")
        LocalDate birthDateWithCustomFormat;
    }

    @Test
    void serializeDeserializeTest() throws JsonProcessingException {
        ObjectMapper objectMapper = new ObjectMapper();

        // Register module that knows how to serialize java.time objects
        objectMapper.registerModule(new JavaTimeModule());

        // Ask Jackson to serialize dates as String (ISO-8601 by default)
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

        // The JSON string after serialization
        String json = "{\"birthDate\":\"2000-01-02\",\"birthDateWithCustomFormat\":\"03/02/2001\"}";

        // The object after deserialization
        TestBean object = new TestBean();
        object.setBirthDate(LocalDate.of(2000, 1, 2));
        object.setBirthDateWithCustomFormat(LocalDate.of(2001, 2, 3));

        // Assert serialization
        assertEquals(json, objectMapper.writeValueAsString(object));

        // Assert deserialization
        assertEquals(object, objectMapper.readValue(json, TestBean.class));
    }
}

TestBean uses Lombok to generate the boilerplate for the bean.

3

A bit easier For Spring :

///...

@Configuration
public class ApplicationCtxBeans {
//....
    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper MAPPER = new ObjectMapper(); 
        MAPPER.registerModule(new JavaTimeModule()); // to handle LocalDateTime etc
        return MAPPER;
    }
//...
}

Usage :

@Service
public class SomeService {
    
//...
    @Autowired
    ObjectMapper jsonMapper;
//...
  JsonNode node = jsonMapper.readTree(
    jsonMapper.writeValueAsString(instance_Of_Class_With_LocalDate_Fields)
  );
//...
}
2

With spring boot 2.3.9.RELEASE,I just registered java time module with no explicit annotation in POJO class having LocalDate field & it worked.

var objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
1

In configuration class define LocalDateSerializer and LocalDateDeserializer class and register them to ObjectMapper via JavaTimeModule like below:

@Configuration
public class AppConfig
{
@Bean
    public ObjectMapper objectMapper()
    {
        ObjectMapper mapper = new ObjectMapper();
        mapper.setSerializationInclusion(Include.NON_EMPTY);
        //other mapper configs
        // Customize de-serialization


        JavaTimeModule javaTimeModule = new JavaTimeModule();
        javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer());
        javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer());
        mapper.registerModule(javaTimeModule);

        return mapper;
    }

    public class LocalDateSerializer extends JsonSerializer<LocalDate> {
        @Override
        public void serialize(LocalDate value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
            gen.writeString(value.format(Constant.DATE_TIME_FORMATTER));
        }
    }

    public class LocalDateDeserializer extends JsonDeserializer<LocalDate> {

        @Override
        public LocalDate deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
            return LocalDate.parse(p.getValueAsString(), Constant.DATE_TIME_FORMATTER);
        }
    }
}
0

If your request contains an object like this:

{
    "year": 1900,
    "month": 1,
    "day": 20
}

Then you can use:

data class DateObject(
    val day: Int,
    val month: Int,
    val year: Int
)
class LocalDateConverter : StdConverter<DateObject, LocalDate>() {
    override fun convert(value: DateObject): LocalDate {
        return value.run { LocalDate.of(year, month, day) }
    }
}

Above the field:

@JsonDeserialize(converter = LocalDateConverter::class)
val dateOfBirth: LocalDate

The code is in Kotlin but this would work for Java too of course.

-1

annotation in Pojo without using additional dependencies

@DateTimeFormat (pattern = "yyyy/MM/dd", iso = DateTimeFormat.ISO.DATE)
private LocalDate enddate;
1
  • 3
    Are you sure about "no additional dependencies"? I have Jackson 2.12.4 and cannot locate this annotation.
    – sxc731
    Commented Jul 23, 2021 at 14:39

Not the answer you're looking for? Browse other questions tagged or ask your own question.