Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java 8 date time types serialized as object with Spring Boot

I have an entity with fields of Java 8 date time types. The issue is that these fields are serialized as object. I added the jackson-datatype-jsr310 dependency, so Spring Boot 1.5.7 would auto configure the JavaTimeModule that handles Java 8 date time types. It seems that the module is not registered (I put a breakpoint in JavaTimeModule constructor). I know I don't need a custom ObjectMapper. I spent hours reading about that issue and the solution is always to add the jackson-datatype-jsr310 dependency but it does not work in my case.

The entity:

@Entity
public class DateTimeEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private LocalDate localDate;

    private LocalDateTime localDateTime;

    private Instant instant;

    private OffsetDateTime offsetDateTime;

    private ZonedDateTime zonedDateTime;

}

The RestController method:

@GetMapping("/datetimes/{id}")
public ResponseEntity<DateTimeEntity> getById(@PathVariable Long id) {
    DateTimeEntity dateTimeEntity = dateTimeRepository.findOne(id);
    return new ResponseEntity<DateTimeEntity>(dateTimeEntity, HttpStatus.OK);

}

The JSON object returned:

    {
    "id": 1,
    "localDate": null,
    "localDateTime": null,
    "instant": {
        "epochSecond": 1508772600,
        "nano": 0
    },
    "offsetDateTime": {
        "offset": {
            "totalSeconds": 0,
            "id": "Z",
            "rules": {
                "fixedOffset": true,
                "transitionRules": [],
                "transitions": []
            }
        },
        "dayOfMonth": 23,
        "dayOfWeek": "MONDAY",
        "dayOfYear": 296,
        "month": "OCTOBER",
        "monthValue": 10,
        "year": 2017,
        "hour": 15,
        "minute": 30,
        "nano": 0,
        "second": 0
    },
    "zonedDateTime": {
        "offset": {
            "totalSeconds": 0,
            "id": "Z",
            "rules": {
                "fixedOffset": true,
                "transitionRules": [],
                "transitions": []
            }
        },
        "zone": {
            "totalSeconds": 0,
            "id": "Z",
            "rules": {
                "fixedOffset": true,
                "transitionRules": [],
                "transitions": []
            }
        },
        "dayOfMonth": 23,
        "dayOfWeek": "MONDAY",
        "dayOfYear": 296,
        "month": "OCTOBER",
        "monthValue": 10,
        "year": 2017,
        "hour": 15,
        "minute": 30,
        "nano": 0,
        "second": 0,
        "chronology": {
            "id": "ISO",
            "calendarType": "iso8601"
        }
    }
}

The POM file:

<?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.example</groupId>
    <artifactId>framework-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

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

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
        <mockito.version>2.11.0</mockito.version>
        <org.mapstruct.version>1.2.0.Final</org.mapstruct.version>
    </properties>

    <dependencies>
        <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>org.mapstruct</groupId>
            <artifactId>mapstruct-jdk8</artifactId>
            <version>${org.mapstruct.version}</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
        </dependency>

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
            <version>${jackson.version}</version>
            <scope>test</scope>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.5.1</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.mapstruct</groupId>
                            <artifactId>mapstruct-processor</artifactId>
                            <version>${org.mapstruct.version}</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.asciidoctor</groupId>
                <artifactId>asciidoctor-maven-plugin</artifactId>
                <version>1.5.5</version>
                <executions>
                    <execution>
                        <id>output-html</id>
                        <phase>generate-resources</phase>
                        <goals>
                            <goal>process-asciidoc</goal>
                        </goals>
                        <configuration>
                            <backend>html</backend>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>
like image 561
Sydney Avatar asked Nov 05 '17 10:11

Sydney


2 Answers

I started having this issue in the upgrade of spring boot from 2.3.7 to 2.5.1.

If you have an ObjectMapper @Bean defined, then you'll want to register the time module with it.

@Bean
public ObjectMapper defaultMapper() {
    ObjectMapper objectMapper = new ObjectMapper(); 
    objectMapper.registerModule(new JavaTimeModule()); 
    return objectMapper;
}

A lot of the time, coders will just create a "new ObjectMapper()" when using jackson serialization so keep an eye out for using the vanilla mapper, rather than autowiring a pre-configured default that has the time module registered.

As mentioned, you'll need the jackson-datatype-jsr310, but this is included in spring boot as a managed version.

If you don't define an object mapper bean manually, then spring boot should automatically provide one with the time module registered.

like image 89
TheJeff Avatar answered Oct 05 '22 02:10

TheJeff


According to How to customize ObjectMapper :

Any beans of type com.fasterxml.jackson.databind.Module will be automatically registered with the auto-configured Jackson2ObjectMapperBuilder and applied to any ObjectMapper instances that it creates. This provides a global mechanism for contributing custom modules when you add new features to your application.

Just adding the dependancy is not enough, you have to declare a @Bean of you module like follow:

@Bean
public Module dateTimeModule(){
    return new JavaTimeModule();
}

Plus jackson-datatype-jsr310 module is deprecated you should use JavaTimeModule instead.

like image 24
Baptiste Beauvais Avatar answered Oct 05 '22 03:10

Baptiste Beauvais