Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Boot issues serializing java.time.LocalDateTime with Jackson to return ISO-8601 JSON timestamps?

I'm working on converting some models in a spring-boot REST API app to use java 8's java.time.LocalDateTime instead of joda's DateTime. I want the timestamps returned from API call to adhere to the ISO_8601 format. Motivation is to be forward compatible with Java 8's time (more here).

The part that's proving difficult is when it comes to serialize an object containing LocalDateTime to JSON.

For example, I have the following entity:

// ... misc imports

import java.time.LocalDateTime;
import java.time.ZoneOffset;

@Data 
@Entity
@Table(name = "users")
public class User {

    @Id @Column
    private String id;

    @Column
    private String name;

    @Column
    private String email;

    @Column
    @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss", timezone = "UTC")
    private java.time.LocalDateTime createdAt;

    public User(String name, String email) {
        this.id = Utils.generateUUID();
        this.createdAt = LocalDateTime.now(ZoneOffset.UTC);
    }
}

I have also set my application.properties to turn off the dates as timestamp jackson feature:

spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS = false

My maven deps:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>1.3.6.RELEASE</version>
</dependency>

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    <version>2.8.1</version>
</dependency>

<dependency>
     <groupId>org.hibernate</groupId>
     <artifactId>hibernate-core</artifactId>
     <version>5.0.1.Final</version>
 </dependency>

Finally, I try to retrieve the JSON representation via controller:

@RequestMapping("/users")
@RestController
public class UserController {

    private UserService userService;

    @Autowired
    public UserController(UserService userService) {
        this.userService = userService;
    }

    @RequestMapping(
        value = "/{id}",
        method = RequestMethod.GET,
        produces = MediaType.APPLICATION_JSON_UTF8_VALUE
    )
    public User getUser(@PathVariable("id") String id) {
        return userService.findById(id);
    }
}

When I actually make a call to this endpoint, I get the following exception:

java.lang.NoSuchMethodError: 
com.fasterxml.jackson.datatype.jsr310.ser.JSR310FormattedSerializerBase.findFormatOverrides(Lcom/fasterxml/jackson/databind/SerializerProvider;Lcom/fasterxml/jackson/databind/BeanProperty;Ljava/lang/Class;)Lcom/fasterxml/jackson/annotation/JsonFormat$Value;

Alternately I also configured the app's ObjectMapper in the configuration class:

@Configuration
public class ServiceConfiguration {

    @Bean
    public ObjectMapper getJacksonObjectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.registerModule(new JavaTimeModule());
        objectMapper.configure(
            com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS,
            false
        );
        return objectMapper;
    }
}

Any leads will be greatly appreciated.


UPDATE:

Turns out it was a version mismatch between Spring Boot's Jackson version and the one I had in my pom.xml. As Miloš and Andy proposed, once I've set the correct version and run the app with spring.jackson.serialization.write_dates_as_timestamps=true, the issue was resolved, without needing to configure the ObjectMapper or adding annotations on my LocalDateTime model fields.

    ...
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>1.3.6.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>1.3.6.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
        </dependency>

        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
        </dependency>

    </dependencies>
like image 374
sa125 Avatar asked Jul 28 '16 06:07

sa125


2 Answers

The NoSuchMethodError is because you are mixing versions of Jackson. Spring Boot 1.3.6 uses Jackson 2.6.7 and you are using 2.8.1 of jackson-datatype-jsr310.

Spring Boot provides dependency management for Jackson, including jackson-datatype-jsr310, so you should remove the version from your pom. If you want to use a different version of Jackson, you should override the jackson.version property:

<properties>
    <jackson.version>2.8.1</jackson.version>
</properties>

This will ensure that all your Jackson dependencies have the same version, thereby avoiding problems with mismatched versions.

You can also, if you wish, remove your Java code that's configuring the ObjectMapper. The Java Time module will be automatically registered when it's in the classpath and writing dates as timestamps can be configured in application.properties:

spring.jackson.serialization.write-dates-as-timestamps=false
like image 94
Andy Wilkinson Avatar answered Nov 14 '22 10:11

Andy Wilkinson


Your ObjectMapper bean must be marked as @Primary in order to be picked up by Spring. Alternatively, you can just create a JavaTimeModule bean and it will get picked up by Spring and added to the default object mapper.

You've probably seen it already but take a look at the official documentation.

like image 5
Miloš Milivojević Avatar answered Nov 14 '22 09:11

Miloš Milivojević