{"id":12863,"date":"2016-02-12T10:33:01","date_gmt":"2016-02-12T09:33:01","guid":{"rendered":"https:\/\/touk.pl\/blog\/?p=12863"},"modified":"2023-03-20T16:06:38","modified_gmt":"2023-03-20T15:06:38","slug":"formatting-java-time-with-spring-boot-using-json","status":"publish","type":"post","link":"https:\/\/touk.pl\/blog\/2016\/02\/12\/formatting-java-time-with-spring-boot-using-json\/","title":{"rendered":"Formatting Java Time with Spring Boot using JSON"},"content":{"rendered":"<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/touk.pl\/blog\/wp-content\/uploads\/2016\/02\/stf0-banner.jpg\" alt=\"stf0-banner\" width=\"2272\" height=\"513\" class=\"alignnone size-full wp-image-12864\" \/> <strong>The aim of this post is to summarize and review ways of formatting Java Time objects using Spring Boot and Jackson library.<\/strong><\/p>\n<p>This post is organized into five steps. Each step represents one aspect of the issue and it is also related to one commit in the example project repository.<\/p>\n<h2 id=\"step-0-prerequirements\">Step 0 &#8211; Prerequirements<\/h2>\n<h3 id=\"versions-and-dependencies\">Versions and dependencies<\/h3>\n<p>This tutorial is based on <code>Spring Boot<\/code> version <code>1.3.1.RELEASE<\/code> with <code>spring-boot-starter-web<\/code>. It uses <code>jackson-datatype-jsr310<\/code> from <code>com.fasterxml.jackson.datatype<\/code> in version <code>2.6.4<\/code>, which is a default version of <code>Spring Boot<\/code>. All of these is based on Java 8.<\/p>\n<h3 id=\"the-code\">The Code<\/h3>\n<p>In the example <a href=\"https:\/\/github.com\/mlevvy\/spring-boot-jackson-formatting-tutorial\">code repository<\/a>, you can find one HTTP service made with <code>Spring Boot<\/code>. This service is a <code>GET<\/code> operation, which returns a class with Java Time objects. You can also find the integration test that deserializes the response.<\/p>\n<h2 id=\"step-1-the-goal\">Step 1 &#8211; The goal<\/h2>\n<p>I would like to return class <code>Clock<\/code>, containing <code>LocalDate<\/code>,<code>LocalTime<\/code> and <code>LocalDateTime<\/code>, preinitialized in constructor.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\">public final class Clock {\r\n    private final LocalDate localDate;\r\n    private final LocalTime localTime;\r\n    private final LocalDateTime localDateTime;\r\n    ...\r\n}\r\n<\/pre>\n<p>Response class is serialized to JSON Map, which is a default behaviour. To some extent it is correct, but ISO-formatted Strings in response are preferable.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"json\">{  \r\n    \"localDate\":{  \r\n        \"year\":2016,\r\n        \"month\":\"JANUARY\",\r\n        \"era\":\"CE\",\r\n        \"dayOfYear\":1,\r\n        \"dayOfWeek\":\"FRIDAY\",\r\n        \"leapYear\":true,\r\n        \"dayOfMonth\":1,\r\n        \"monthValue\":1,\r\n        \"chronology\":{  \r\n            \"id\":\"ISO\",\r\n            \"calendarType\":\"iso8601\"\r\n        }\r\n    }\r\n}\r\n<\/pre>\n<p>Integration testing is an appropriate way to test our functionality.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\">ResponseEntity resp = sut.getForEntity(\"http:\/\/localhost:8080\/clock\", Clock.class);\r\n\r\nassertEquals(OK, resp.getStatusCode());\r\nassertEquals(c.getLocalDate(), resp.getBody().getLocalDate());\r\nassertEquals(c.getLocalTime(), resp.getBody().getLocalTime());\r\nassertEquals(c.getLocalDateTime(), resp.getBody().getLocalDateTime());\r\n<\/pre>\n<p>Unfortunately, tests are not passing, because of deserialization problems. The exception with message is thrown <code>can not instantiate from JSON object<\/code>.<\/p>\n<h2 id=\"step-2-adds-serialization\">Step 2 &#8211; Adds serialization<\/h2>\n<p>First things first. We have to add JSR-310 module. It is a datatype module to make <a href=\"http:\/\/jackson.codehaus.org\">Jackson<\/a> recognize Java 8 Date &amp; Time API data types.<\/p>\n<p>Note that in this example <code>jackson-datatype-jsr310<\/code> version is inherited from <code>spring-boot-dependencies<\/code> dependency management.<\/p>\n<pre class=\"EnlighterJSRAW\">com.fasterxml.jackson.datatype\r\n      jackson-datatype-jsr310\r\n<\/pre>\n<p>Response is now consistent but still, not perfect. Dates are serialized as numbers:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\">{  \r\n    \"version\":2,\r\n    \"localDate\":[  \r\n        2016,\r\n        1,\r\n        1\r\n    ],\r\n    \"localTime\":[  \r\n        10,\r\n        24\r\n    ],\r\n    \"localDateTime\":[  \r\n        2016,\r\n        1,\r\n        1,\r\n        10,\r\n        24\r\n    ],\r\n    \"zonedDateTime\":1451640240.000000000\r\n}\r\n<\/pre>\n<p>We are one step closer to our goal. Tests are passing now because this format can be deserialized without any additional deserializers. How do I know? Start an application server on commit <code>Step 2 - Adds Object Mapper<\/code>, then checkout to <code>Step 1 - Introduce types and problems<\/code>, and run integration tests without <code>@WebIntegrationTest<\/code> annotation.<\/p>\n<h2 id=\"step-3-enables-iso-formatting\">Step 3 &#8211; Enables ISO formatting<\/h2>\n<p><a href=\"https:\/\/en.wikipedia.org\/wiki\/ISO_8601\">ISO 8601<\/a> formatting is a standard. I&#8217;ve found it in many projects. We are going to enable and use it. Edit spring boot properties file <code>application.properties<\/code> and add the following line:<\/p>\n<pre class=\"EnlighterJSRAW\">spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS = false\r\n<\/pre>\n<p>Now, the response is something that I&#8217;ve expected:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"json\">{  \r\n    \"version\":2,\r\n    \"localDate\":\"2016-01-01\",\r\n    \"localTime\":\"10:24\",\r\n    \"localDateTime\":\"2016-01-01T10:24\",\r\n    \"zonedDateTime\":\"2016-01-01T10:24:00+01:00\"\r\n}\r\n<\/pre>\n<h2 id=\"step-4-adds-on-demand-formatting-pattern\">Step 4 &#8211; Adds on-demand formatting pattern<\/h2>\n<p>Imagine one of your client systems does not have the capability of formatting time. It may be a primitive device or microservice that treats this date as a collection of characters. That is why special formatting is required.<\/p>\n<p>We can change formatting in response class by adding <code>JsonFormat<\/code> annotation with pattern parameter. Standard <a href=\"https:\/\/docs.oracle.com\/javase\/6\/docs\/api\/java\/text\/SimpleDateFormat.html\">SimpleDateFormat<\/a> rules apply.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\">@JsonFormat(pattern = \"dd::MM::yyyy\")\r\nprivate final LocalDate localDate;\r\n    \r\n@JsonFormat(pattern = \"KK:mm a\")\r\nprivate final LocalTime localTime;\r\n<\/pre>\n<p>Below there is a service response using custom <code>@JsonFormat<\/code> pattern:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"json\">{  \r\n    \"version\":2,\r\n    \"localDate\":\"01::01::2016\",\r\n    \"localTime\":\"10:24 AM\",\r\n    \"localDateTime\":\"2016-01-01T10:24\",\r\n    \"zonedDateTime\":\"2016-01-01T10:24:00+01:00\"\r\n}\r\n<\/pre>\n<p>Our tests are still passing. It means that this pattern is used for serialization in service and deserialization in tests.<\/p>\n<h2 id=\"step-5-globally-changes-formatting\">Step 5 &#8211; Globally changes formatting<\/h2>\n<p>There are situations where you have to resign from <code>ISO 8601<\/code> formatting in your whole application, and apply custom-made standards.<\/p>\n<p>In this part, we will redefine the format pattern for LocalDate. This will change formatting of LocalDate in <strong>every endpoint<\/strong> of your API.<\/p>\n<p>We have to define: &#8211; <code>DateTimeFormatter<\/code> with our pattern. &#8211; <code>Serializer<\/code> using defined pattern. &#8211; <code>Deserializer<\/code> using defined pattern. &#8211; <code>ObjectMapper<\/code> bean with custom serializer and deserializer. &#8211; <code>RestTemplate<\/code> that uses our <code>ObjectMapper<\/code>.<\/p>\n<p>Bean ObjectMapper is defined with annotation <code>@Primary<\/code>, to override default configuration. My custom pattern for <code>LocalDate<\/code> is <code>dd::MM::yyyy<\/code><\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\">public static final DateTimeFormatter FORMATTER = ofPattern(\"dd::MM::yyyy\");\r\n    \r\n@Bean\r\n@Primary\r\npublic ObjectMapper serializingObjectMapper() {\r\n    ObjectMapper objectMapper = new ObjectMapper();\r\n    JavaTimeModule javaTimeModule = new JavaTimeModule();\r\n    javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer());\r\n    javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer());\r\n    objectMapper.registerModule(javaTimeModule);\r\n    return objectMapper;\r\n}\r\n<\/pre>\n<p>Definitions of serializer and deserializer for all LocalDate classes:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\">public class LocalDateSerializer extends JsonSerializer {\r\n    \r\n    @Override\r\n    public void serialize(LocalDate value, JsonGenerator gen, SerializerProvider serializers) throws IOException {\r\n        gen.writeString(value.format(FORMATTER));\r\n    }\r\n}\r\n    \r\npublic class LocalDateDeserializer extends JsonDeserializer {\r\n    \r\n    @Override\r\n    public LocalDate deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {\r\n        return LocalDate.parse(p.getValueAsString(), FORMATTER);\r\n    }\r\n}\r\n<\/pre>\n<p>Now, the response is formatted with our custom pattern:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"json\">\r\n{  \r\n    \"localDate\":\"01::01::2016\"\r\n}\r\n<\/pre>\n<h3 id=\"tests\">Tests<\/h3>\n<p>When we define a custom serializer, our tests start to fail. It is because RestTemplate knows nothing about our deserializer. We have to create a custom RestTemplateFactory that creates RestTemplate with object mapper containing our deserializer.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"java\">@Configuration\r\npublic class RestTemplateFactory {\r\n    \r\n    @Autowired\r\n    private ObjectMapper objectMapper;\r\n    \r\n    @Bean\r\n    public RestTemplate createRestTemplate() {\r\n        RestTemplate restTemplate = new RestTemplate();\r\n        List converters = new ArrayList();\r\n        MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();\r\n        jsonConverter.setObjectMapper(objectMapper);\r\n        converters.add(jsonConverter);\r\n        restTemplate.setMessageConverters(converters);\r\n        return restTemplate;\r\n    }\r\n}\r\n<\/pre>\n<h2 id=\"conclusion\">Conclusion<\/h2>\n<p>Custom formatting Dates is relatively simple, but you have to know how to set up it. Luckily, <code>Jackson<\/code> works smoothly with <code>Spring<\/code>. If you know other ways of solving this problem or you have other observations, please comment or let me know.<\/p>\n<p>Blog from <a href=\"http:\/\/lewandowski.io\/2016\/02\/formatting-java-time-with-spring-boot-using-json\/\">Micha\u0142\u00a0Lewandowski<\/a> personal blog. <a href=\"http:\/\/www.flickr.com\/photos\/19511776@N00\/286814746\">Photo Credit<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"The aim of this post is to summarize and review ways of formatting Java Time objects using Spring&hellip;\n","protected":false},"author":62,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[11],"tags":[68,283,42],"_links":{"self":[{"href":"https:\/\/touk.pl\/blog\/wp-json\/wp\/v2\/posts\/12863"}],"collection":[{"href":"https:\/\/touk.pl\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/touk.pl\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/touk.pl\/blog\/wp-json\/wp\/v2\/users\/62"}],"replies":[{"embeddable":true,"href":"https:\/\/touk.pl\/blog\/wp-json\/wp\/v2\/comments?post=12863"}],"version-history":[{"count":17,"href":"https:\/\/touk.pl\/blog\/wp-json\/wp\/v2\/posts\/12863\/revisions"}],"predecessor-version":[{"id":15445,"href":"https:\/\/touk.pl\/blog\/wp-json\/wp\/v2\/posts\/12863\/revisions\/15445"}],"wp:attachment":[{"href":"https:\/\/touk.pl\/blog\/wp-json\/wp\/v2\/media?parent=12863"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/touk.pl\/blog\/wp-json\/wp\/v2\/categories?post=12863"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/touk.pl\/blog\/wp-json\/wp\/v2\/tags?post=12863"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}