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.
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.
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
update: Spring Boot 2.x doesn't require this configuration anymore. I've written a more up to date answer here.
(This is the way of doing it before Spring Boot 2.x, it might be useful for people working on an older version of Spring Boot)
I finally found here how to do it. To fix it, I needed another dependency:
compile("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.4.0")
By including this dependency, Spring will automatically register a converter for it, as described here. After that, you need to add the following to application.properties:
spring.jackson.serialization.write_dates_as_timestamps=false
This will ensure that a correct converter is used, and dates will be printed in the format of 2016-03-16T13:56:39.492
Annotations are only needed in case you want to change the date format.
I added the com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.6.1 dependency and started to get the date in the following format:
"birthDate": [
2016,
1,
25,
21,
34,
55
]
which is not what I wanted but I was getting closer. I then added the following
spring.jackson.serialization.write_dates_as_timestamps=false
to application.properties file which gave me the correct format that I needed.
"birthDate": "2016-01-25T21:34:55"
The LocalDate is stored in incorrectly in the JSON.
The correct way to fix the problem is to ensure that the ObjectMapper generating the JSON has the JavaTimeModule or the Jdk8Module registered. This ensures that the LocalDate is correctly serialized to a JSON array of [year, month, day].
The brute-force method is to follow the solution that @Roy outlined in another answer, and convert each relevant field manually to a LocalDate. Something along the lines of :
LocalDate date = LocalDate.now()
.with(ChronoField.YEAR, year)
.with(ChronoField.MONTH_OF_YEAR, Month.valueOf(month).getValue())
.with(ChronoField.DAY_OF_MONTH, dayOfMonth);
This is obviously rather error-prone, and not how the classes were intended to be used.
Replace your MyCustomObject.class like this:
==================================
package ;
public class Chronology
{
private String calendarType;
private String id;
public void setCalendarType(String calendarType){
this.calendarType = calendarType;
}
public String getCalendarType(){
return this.calendarType;
}
public void setId(String id){
this.id = id;
}
public String getId(){
return this.id;
}
}
==================================
package ;
public class From
{
private int year;
private String month;
private String era;
private int dayOfMonth;
private String dayOfWeek;
private int dayOfYear;
private boolean leapYear;
private int monthValue;
private Chronology chronology;
public void setYear(int year){
this.year = year;
}
public int getYear(){
return this.year;
}
public void setMonth(String month){
this.month = month;
}
public String getMonth(){
return this.month;
}
public void setEra(String era){
this.era = era;
}
public String getEra(){
return this.era;
}
public void setDayOfMonth(int dayOfMonth){
this.dayOfMonth = dayOfMonth;
}
public int getDayOfMonth(){
return this.dayOfMonth;
}
public void setDayOfWeek(String dayOfWeek){
this.dayOfWeek = dayOfWeek;
}
public String getDayOfWeek(){
return this.dayOfWeek;
}
public void setDayOfYear(int dayOfYear){
this.dayOfYear = dayOfYear;
}
public int getDayOfYear(){
return this.dayOfYear;
}
public void setLeapYear(boolean leapYear){
this.leapYear = leapYear;
}
public boolean getLeapYear(){
return this.leapYear;
}
public void setMonthValue(int monthValue){
this.monthValue = monthValue;
}
public int getMonthValue(){
return this.monthValue;
}
public void setChronology(Chronology chronology){
this.chronology = chronology;
}
public Chronology getChronology(){
return this.chronology;
}
}
==================================
package ;
public class MyCustomObject
{
private From from;
public void setFrom(From from){
this.from = from;
}
public From getFrom(){
return this.from;
}
}