6

I have Spring boot application and LocalDateTime properties serialized as JSON array, instead of String.

As I found while search on web, all I need is to set up in application.properties

spring.jackson.serialization.write-dates-as-timestamps=false

I also tried to put jackson-datatype-jsr310 to dependencies, but no luck either

Also I tried to add annotation:

@DateTimeFormat(iso = ISO.DATE_TIME)

it not helps too.

I saw a lot of people get in something similar, but their solutions seems related to Spring Boot 1.x, while I use 2.04

Also I use Lombok, but not sure is it affects serialization format.

How can I track down and fix the date serialization format to be ISO date String?

Here response example (start is LocalDateTime and I want to have it as ISO String):

{
  "id": 3,
  "enabled": true,
  "outletId": 5,
  "reason": "hello",
  "start": [
    2019,
    9,
    10,
    10,
    42,
    11
  ],
  "status": "AVAILABLE"
}

Here the REST controller method response object:

@Data
@Entity
@Table(indexes = { @Index(columnList = ("outletId"),name="outlet_id_index"), 
        @Index(columnList = ("start"),name="start_index"),
        @Index(columnList = ("outletId, start"),name="outlet_id_start_index")})
public class OutletChron extends BaseEntity {
    private Long outletId;
    private String reason;

    @DateTimeFormat(iso = ISO.DATE_TIME)
    private LocalDateTime start;
    @Enumerated(EnumType.STRING)
    @Column(length = 30)
    private OutletStatus status;
}

Here my POM:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.banquets</groupId>
    <artifactId>Banquet</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>Banquet</name>
    <description></description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.4.RELEASE</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <spring-cloud.version>Finchley.SR1</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <!-- <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web-services</artifactId> 
            </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> 
            </dependency> -->

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>


</project>

UPDATE

I build a new project just to test, and I found there String format was default for LocalDateTime mapping. I was able to track down that format changes once I configure swagger. So without this swagger config I have String format:

@Configuration
@EnableSwagger2
public class SwaggerConfig extends WebMvcConfigurationSupport {

    @Autowired
    ServletContext servletContext;

    @Bean
    public Docket productApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.demo"))
                .build();
    }

    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("swagger-ui.html")
                .addResourceLocations("classpath:/META-INF/resources/");

        registry.addResourceHandler("/webjars/**")
                .addResourceLocations("classpath:/META-INF/resources/webjars/");
    }
}

UPDATE this Swagger config seems works (Date format is String and I can access Swagger UI at http://localhost:8000/api/swagger-ui.html

@Configuration
@EnableSwagger2
public class SwaggerConfig {

    @Bean
    public Docket apiDocket() {
        return new Docket(DocumentationType.SWAGGER_2)
                .select()
                .apis(RequestHandlerSelectors.any())
                .paths(PathSelectors.any())
                .build();
    }
}
P_M
  • 2,723
  • 4
  • 29
  • 62
  • `extends WebMvcConfigurationSupport` is actually the web configuration. With this you register a bean of that type and as such Spring Boot will not auto configure web related beans. This is the class that gets imported by `@EnableWebMvc` (well actually that imports a subclass of that class). So extending that class is the same as adding `@EnableWebMvc`. – M. Deinum Sep 12 '18 at 11:05

3 Answers3

7

Spring boot 2.x does not required to import JSR310 specifications as now its part of spring framework now so no need to import them and string formatting will automatically work.

If you need to override some default configuration of spring boot then you need to implement WebMvcConfigurer instead of extending WebMvcConfigurationSupport.

In your case, if you want to put static files somewhere else not in default resources folder then you might need to override addResourceHandlers and register paths.

If resources in default path then not required and just removing extending WebMvcConfigurationSupport will work for default string formatting.

UPDATED ANSWER:

If you use WebMvcConfigurationSupport that means uts an indication that it shouldn't auto-configure Spring MVC, means default settings will not work and you have to define all things here by overriding its support methods. So instead of WebMvcConfigurationSupport implement WebMvcConfigurer instead.

Here is the updated config.

@Configuration
@EnableSwagger2
public class SwaggerConfig implements WebMvcConfigurer {

    @Bean
    public Docket productApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.demo"))
                .build();
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("swagger-ui.html")
                .addResourceLocations("classpath:/META-INF/resources/");

        registry.addResourceHandler("/webjars/**")
                .addResourceLocations("classpath:/META-INF/resources/webjars/");
    }
}
imTachu
  • 3,759
  • 4
  • 29
  • 56
kj007
  • 6,073
  • 4
  • 29
  • 47
  • Yes, I just did this. Lombok is seems fine, but Swagger config breaks something. Please see update. – P_M Sep 11 '18 at 15:21
  • @Pavlo check my updated answer, this will solve your problem. – kj007 Sep 12 '18 at 03:03
  • 1
    I was able to make it works, by removing "extends WebMvcConfigurationSupport" at all. I do not know what is it for. So why should I still need "implements WebMvcConfigurer"? – P_M Sep 12 '18 at 08:10
  • If you need to add resource handlers then you need to implement it, not sure but seems you want to access swagger UI. I corrected your configuration what you have updated in your description. – kj007 Sep 12 '18 at 08:26
  • Yes, I got Swagger with UI on http://localhost:8000/api/swagger-ui.html without "extends WebMvcConfigurationSupport" and without "implements WebMvcConfigurer". May be Spring Boot takes care about it, I not sure. I will update init question with my current swagger config – P_M Sep 12 '18 at 10:31
  • Feel free to update your answer and I will accept it. My research was quite long but lead to almost what you suggested initially. – P_M Sep 12 '18 at 10:38
  • I have updated what I was trying to say, if you need to update default resources then you need to override otherwise of-course removing WebMvcConfigurationSupport will work as this was not allowing you to use default settings and in case of yours it was date. – kj007 Sep 12 '18 at 10:50
  • `WebMvcConfigurationSupport` is not the same as implementing `WebMvcConfigurer`.The first is the actual configuration class loaded by `@EnableWebMvc`, extending that class is the same as adding `@EnableWebMvc`. On older spring versions you should have extended `WebMvcConfigurerAdapter` NOT `WebMvcConfigurationSupport`. – M. Deinum Sep 12 '18 at 11:07
  • @M.Deinum true but WebMvcConfigurationSupport extending means default settings will not work so instead of extending WebMvcConfigurationSupport just implement WebMvcConfigurer so default will be not changes until unless we dont override any method of it, and here case was also this. – kj007 Sep 12 '18 at 11:10
  • As stated you should have extended `WebMvcConfigurerAdapter` NOT `WebMvcConfigurationSupport`. In a regular Spring application it doesn't make a difference in a Spring Boot application it does as Spring Boot doesn't apply its auto configuration. – M. Deinum Sep 12 '18 at 11:11
  • true but Since Spring 5 you just need to implement the interface WebMvcConfigurer as WebMvcConfigurerAdapter is deprecated.please correct me if I am wrong. – kj007 Sep 12 '18 at 11:13
3

For me the following works:

@JsonFormat(pattern = "dd.MM.yyyy HH:mm")
private LocalDateTime startTime;

This will print the date in a string format e.g. like 11.09.2018 15:44

mrkernelpanic
  • 4,268
  • 4
  • 28
  • 52
  • This could be solution as it works. Though I not like it, as I have to write the pattern myself. If I will not find anything better, will have to use this. – P_M Sep 11 '18 at 14:14
1

Create object mapper manually

@Bean
@Primary
public ObjectMapper objectMapper() {
    ObjectMapper mapper = new ObjectMapper();

    JavaTimeModule timeModule = new JavaTimeModule();
    mapper.registerModule(timeModule);

    mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
    mapper.configure(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS, false);

    return mapper;
}
Nick
  • 3,691
  • 18
  • 36
  • Nothing changes. :( I added this bean to root application class. I see it called when I start application, but date response format left the same - it is an array – P_M Sep 11 '18 at 13:41
  • Remove `@DateTimeFormat` annotation – Nick Sep 11 '18 at 15:14
  • I tried without it and it not works too. I was able to track down my Swagger config seems cause it. Please check question update. – P_M Sep 11 '18 at 15:19