- Java 8 date time JSON formatting with Jackson
- Creating a POJO
- Controller Layer
- JUNIT 5 test at Controller layer for JSR-310 types
- Spring DateTimeFormat vs Jackson JsonFormat
- Github Repository
- Formatting json Date/LocalDateTime/LocalDate in Spring Boot
- Date
- LocalDateTime & LocalDate
- Testing
- Controller
- Client
- Jackson JSON — Using @JsonFormat to format Date and Enum
- Formatting Date
- Formatting enum
- Example
- Java Object
- Serializing to JSON
- Without @JsonFormat
- Example Project
Java 8 date time JSON formatting with Jackson
First of all we need to create a new Spring Boot 2.2.0 based project. The below gradle file will manage the dependencies for setting up Spring MVC.
plugins < id 'org.springframework.boot' version '2.2.0.RELEASE' id 'io.spring.dependency-management' version '1.0.8.RELEASE' id 'java' >group = 'com.shunya' version = '0.0.1-SNAPSHOT' sourceCompatibility = '1.8' repositories < mavenCentral() >dependencies
Creating a POJO
We will create a simple POJO that holds different types of types i.e. java.util.Date, LocalDate, LocalDateTime, Instant.
import com.fasterxml.jackson.annotation.JsonFormat; import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.Date; public class SampleDto < @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX", timezone = "Asia/Kolkata") private Instant instant; @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ") private Date date; @JsonFormat(pattern = "yyyy-MM-dd") private LocalDate localDate; @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") private LocalDateTime localDateTime; //Getters and Setter omitted for brevity >
We will use this POJO for Jackson based JSON serialization/deserialization.
It is important to note here that we need to specify the timezone while dealing with java.time.Instant otherwise we will encounter the below exception:
nested exception is com.fasterxml.jackson.databind.JsonMappingException: Unsupported field: YearOfEra
Controller Layer
We will create a controller that exposes two endpoints — one with HTTP POST for SampleDto another for HTTP GET for SampleDto.
@RestController public class DateController < @PostMapping("/date") public SampleDto create(@RequestBody SampleDto payload) < return payload; >@GetMapping("/date") public SampleDto get() < final SampleDto dto = new SampleDto(); final Instant time = Instant.ofEpochMilli(1571884105000L); dto.setInstant(time); dto.setDate(new Date(time.toEpochMilli())); dto.setLocalDate(time.atZone(ZoneId.of("UTC")).toLocalDate()); dto.setLocalDateTime(time.atZone(ZoneId.of("UTC")).toLocalDateTime()); return dto; >>
JUNIT 5 test at Controller layer for JSR-310 types
Spring Boot 2.2.0 supports JUNIT5 out of the box without any custom configuration. All you need to do is to include the below dependency in build.gradle
Here we will just test the controller layer using JUNIT 5 based tests in Spring Boot.
@ExtendWith(SpringExtension.class) @WebMvcTest(DateController.class) class DateControllerTest < @Autowired private MockMvc mockMvc; @Test void postDate() throws Exception < //language=JSON String jsonPayload = ""; this.mockMvc.perform(MockMvcRequestBuilders .post("/date") .content(jsonPayload) .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)) .andDo(print()).andExpect(status().isOk()) .andExpect(content().string(CoreMatchers.notNullValue())); > >
WebMvcTest annotation just bootstraps the provided controller instead of loading the entire application context.
Spring DateTimeFormat vs Jackson JsonFormat
@JsonFormat is a Jackson annotation ( com.fasterxml.jackson.annotation ), while @DateTimeFormat is a Spring annotation.
@JsonFormat controls the formatting during JSON serialization/deserialization of java Date time classes (JSR-310 java.time types — LocalDate/LocalDateTime/Instant/Date, etc)
@DateTimeFormat on the other hand controls formatting of a bean in Spring when it is rendered ina view.
Github Repository
You can grab the github repository for this project from this link: https://github.com/cancerian0684/tutorial-jsonformat-spring-boot
Top articles in this category:
Formatting json Date/LocalDateTime/LocalDate in Spring Boot
Spring Boot uses jackson by default to serialize, deserialize json data.
By default, Jackson serializes Date objects as timestamps. For LocalDateTime , LocalDate objects, jackson doesn’t do anything special, it just treats them as basic Java objects.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
import java.time.LocalDate; import java.time.LocalDateTime; import java.util.Date; import java.util.HashMap; import java.util.Map; import com.fasterxml.jackson.databind.ObjectMapper; public class MainTest public static void main(String. args) throws Exception ObjectMapper objectMapper = new ObjectMapper(); MapString, Object> map = new HashMap<>(); map.put("date", new Date()); map.put("localDateTime", LocalDateTime.now()); map.put("localDate", LocalDate.now()); String json = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(map); System.out.println(json); > >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
"date" : 1663680273923, "localDateTime" : "dayOfMonth" : 20, "dayOfWeek" : "TUESDAY", "dayOfYear" : 263, "month" : "SEPTEMBER", "monthValue" : 9, "year" : 2022, "hour" : 21, "minute" : 24, "nano" : 992000000, "second" : 33, "chronology" : "id" : "ISO", "calendarType" : "iso8601" > >, "localDate" : "year" : 2022, "month" : "SEPTEMBER", "era" : "CE", "dayOfMonth" : 20, "dayOfWeek" : "TUESDAY", "dayOfYear" : 263, "leapYear" : false, "monthValue" : 9, "chronology" : "id" : "ISO", "calendarType" : "iso8601" > > >
The above code runs normally in Java8. If you are in a higher version of java, such as java17, running the above code may throw an exception.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Java 8 date/time type `java.time.>LocalDateTime` not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling (through >reference chain: java.util.HashMap["localDateTime"]) at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:77) at com.fasterxml.jackson.databind.SerializerProvider.reportBadDefinition(SerializerProvider.java:1300) at com.fasterxml.jackson.databind.ser.impl.UnsupportedTypeSerializer.serialize(UnsupportedTypeSerializer.java:35) at com.fasterxml.jackson.databind.ser.std.MapSerializer.serializeFields(MapSerializer.java:808) at com.fasterxml.jackson.databind.ser.std.MapSerializer.serializeWithoutTypeInfo(MapSerializer.java:764) at com.fasterxml.jackson.databind.ser.std.MapSerializer.serialize(MapSerializer.java:720) at com.fasterxml.jackson.databind.ser.std.MapSerializer.serialize(MapSerializer.java:35) at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider._serialize(DefaultSerializerProvider.java:480) at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:319) at com.fasterxml.jackson.databind.ObjectWriter$Prefetch.serialize(ObjectWriter.java:1518) at com.fasterxml.jackson.databind.ObjectWriter._writeValueAndClose(ObjectWriter.java:1219) at com.fasterxml.jackson.databind.ObjectWriter.writeValueAsString(ObjectWriter.java:1086) at io.springboot.test.MainTest.main(MainTest.java:22)
Date
The formatting of the Date object can be easily customized through the configuration properties provided by spring boot.
spring: jackson: # Date format string or a fully-qualified date format class name. For instance, 'yyyy-MM-dd HH:mm:ss' date-format: "yyyy-MM-dd HH:mm:ss.SSS" # Locale used for formatting time-zone: "GMT+8"
LocalDateTime & LocalDate
Spring Boot does not provide configuration properties for formatting LocalDateTime and LocalDate , but it does provide a Jackson2ObjectMapperBuilderCustomizer interface to easily customize the formatting of LocalDateTime and LocalDate .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
import java.time.format.DateTimeFormatter; import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; @Configuration public class JacksonConfiguration @Bean public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() return builder -> // formatter DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); // deserializers builder.deserializers(new LocalDateDeserializer(dateFormatter)); builder.deserializers(new LocalDateTimeDeserializer(dateTimeFormatter)); // serializers builder.serializers(new LocalDateSerializer(dateFormatter)); builder.serializers(new LocalDateTimeSerializer(dateTimeFormatter)); >; > >
Testing
A simple test to verify that the above code and configuration takes effect.
Controller
A simple Controller that reads and parses the client’s request body into a payload object, which defines the LocalDate , LocalDateTime fields. And it responds to the client with the payload object to verify that the custom formatting is working.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
import java.time.LocalDate; import java.time.LocalDateTime; import java.util.Date; import java.util.HashMap; import java.util.Map; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import lombok.Data; @Data class Payload private LocalDate date; private LocalDateTime dateTime; > @RestController @RequestMapping("/test") public class TestController @PostMapping public Object test (@RequestBody Payload payload) MapString, Object> ret = new HashMap<>(); ret.put("payload", payload); // request body ret.put("now", new Date()); return ret; > >
Client
Use Postman to launch http requests.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
POST /test HTTP/1.1 Content-Type: application/json User-Agent: PostmanRuntime/7.29.2 Accept: */* Postman-Token: 1b1cbcad-475e-49a9-ad5d-5a8163bd7b05 Host: localhost Accept-Encoding: gzip, deflate, br Connection: keep-alive Content-Length: 70 "date": "2022-09-20", "dateTime": "2022-09-20 21:02:00" > HTTP/1.1 200 OK Content-Encoding: gzip Connection: keep-alive Server: PHP/7.3.1 X-Request-Id: 6038513c-fa65-49bf-8e6c-44f5db749832 Transfer-Encoding: chunked Content-Type: application/json Date: Tue, 20 Sep 2022 14:18:09 GMT ,"now":"2022-09-20 22:18:09.318">
With the request and response logs, you can see that everything is OK.
For more information about the available configurations of jackson in spring boot, you can refer to the official documentation。
Jackson JSON — Using @JsonFormat to format Date and Enum
@JsonFormat is a general purpose annotation which can be used to serialize a particular type into a specific format.
package com.fasterxml.jackson.annotation; . public @interface JsonFormat< . // Datatype-specific configuration to further define formatting aspects. public String pattern() default ""; //Structure to use for serialization public Shape shape() default Shape.ANY; //java.util.Locale to use for serialization (if needed) public String locale() default DEFAULT_LOCALE; //java.util.TimeZone to use for serialization (if needed) public String timezone() default DEFAULT_TIMEZONE; //whether "lenient" handling should be enabled. //This is relevant mostly for deserialization of some textual // datatypes, especially date/time types public OptBoolean lenient() default OptBoolean.DEFAULT; //JsonFormat.Feature to explicitly enable for the annotated property public JsonFormat.Feature[] with() default < >; //JsonFormat.Feature to explicitly disable for the annotated property public JsonFormat.Feature[] without() default < >; . >
This tutorial will show how to serialize Date/Calendar and Java Enum in a desired format.
Formatting Date
By default Date/Calendar are serialized as number (i.e. milliseconds since epoch). To serialize them as string, the attribute @JsonFormat#shape should be assigned to JsonFormat.Shape.STRING along with the attribute @JsonFormat#pattern assigned to a desired SimpleDateFormat-compatible pattern. We can also optionally specify @JsonFormat#timezone or @JsonFormat#locale values. For example
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss Z", timezone="America/New_York") private Date startDate;
Formatting enum
By default enums are serialized by their names. To serialize them by ordinals, we can assign the shape as JsonFormat.Shape.NUMBER . For example:
@JsonFormat(shape = JsonFormat.Shape.NUMBER) private Dept dept;
Example
Java Object
With ‘dateOfBirth’ we just specify the string shape without any pattern; In that case a default String format will be serialized.
Serializing to JSON
-- before serialization --
Employee
-- after serialization --
-- after deserialization --
Employee
Without @JsonFormat
If we remove all @JsonFormat:
Employee — after serialization — — after deserialization — Employee
Example Project
Dependencies and Technologies Used:
- jackson-databind 2.9.6: General data-binding functionality for Jackson: works on core streaming API.
- JDK 10
- Maven 3.5.4