Formatting json Date/LocalDateTime/LocalDate in Spring Boot
Tags: springboot, jackson, localdatetim
Published: 2022-09-21 00:55:26

Spring Boot uses [jackson](https://github.com/FasterXML/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.

```java
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();
        
        Map<String, 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);
    }
}
```

Output.

```json
{
  "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.
>
>```txt
>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.

```yml
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`.

```java
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.

```java
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) {
        Map<String, Object> ret = new HashMap<>();
        ret.put("payload", payload); // request body
        ret.put("now", new Date());
        return ret;
    }
}

```

### Client

Use Postman to launch http requests.

```txt
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
 
{"payload":{"date":"2022-09-20","dateTime":"2022-09-20 21:02:00"},"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](https://springdoc.cn/spring-boot/application-properties#application-properties.json.spring.jackson.constructor-detector)。

Reference [https://www.springcloud.io/post/2022-09/springboot-date-format/](https://www.springcloud.io/post/2022-09/springboot-date-format/)