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();
        
        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.

 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.

1
2
3
4
5
6
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) {
        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.

 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
 
{"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