For java.util.Date when I do
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = \"dd/MM/yyyy\")
private Date dateOfBirth;
<
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.
The JSR310Module is deprecated as of version 2.7 of Jackson. Instead, you should register the module JavaTimeModule. It is still the same dependency.
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.
@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+
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.
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
works fine for me.
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