Reputation: 12212
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>
Upvotes: 23
Views: 38006
Reputation: 5139
If you have issue with Spring Batch Jackson2ExecutionContextStringSerializer, the solution is:
@Configuration
public class JacksonConfig {
@Bean
public BatchConfigurer batchConfigurer(DataSource dataSource, PlatformTransactionManager transactionManager) {
return new DefaultBatchConfigurer(dataSource) {
@Override
protected JobRepository createJobRepository() throws Exception {
Jackson2ExecutionContextStringSerializer serializer = new Jackson2ExecutionContextStringSerializer();
ObjectMapper objectMapper = new ObjectMapper().registerModule(new JavaTimeModule()).findAndRegisterModules();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true);
serializer.setObjectMapper(objectMapper);
JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
factory.setDataSource(dataSource);
factory.setTransactionManager(transactionManager);
factory.setSerializer(serializer);
return factory.getObject();
}
};
}
}
Source: https://docs.spring.io/spring-batch/docs/current/reference/html/job.html
Upvotes: 1
Reputation: 2413
You can also try to use
ObjectMapper objectMapper = JsonMapper.builder()
.addModule(new JavaTimeModule())
.build();
JavaTimeModule is from package com.fasterxml.jackson.datatype.jsr310 I have used library jackson-datatype-jsr310-2.13.3.jar
Upvotes: 9
Reputation: 2095
Spring Data Couchbase introduced a @Bean
factory method in class AbstractCouchbaseConfiguration
, which produces an ObjectMapper
bean so that Spring does not create its own and correct one (automatically including the java-time module stuff).
Here is the offending source file: https://github.com/spring-projects/spring-data-couchbase/blame/4.2.x/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java#L309
The bug report is here: https://github.com/spring-projects/spring-data-couchbase/issues/1209
This has been fixed in Spring-Data-Couchbase 4.3
Upvotes: 0
Reputation: 111
In case you are currently using String Boot 2.5.4 + Apache CXF (Java config) for JAX-RS, the below config solved problem for me
dependencies{
constraints{
implementation("org.apache.cxf:cxf-spring-boot-starter-jaxrs:3.4.4")
implementation("com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider:2.12.5")
implementation("com.fasterxml.jackson.module:jackson-modules-java8:2.12.5")
}
}
dependencies{
implementation("org.apache.cxf:cxf-spring-boot-starter-jaxrs")
implementation("com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider")
implementation("com.fasterxml.jackson.module:jackson-modules-java8")
}
Spring Boot (org.springframework.http.converter.json.Jackson2ObjectMapperBuilder), automatically registers the following well-known modules if they are detected on the classpath:
But to use CXF it seems we need to initialize it with correct serialization provider. And in case we not pass Spring Boot configured ObjectMapper to the JacksonJsonProvider we will catch the error like this:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Java 8 date/time type 'java.time.Instant' not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling
to fix it we have to init JacksonJsonProvider with Spring configured ObjectMapper, like the code below
@Autowired
DataService dataService;
@Autowired
EventService eventService;
@Autowired
SessionService sessionService;
@Autowired
StatusService statusService;
@Autowired
private Bus bus;
@Autowired
private ObjectMapper objectMapper;
@Bean
public JacksonJsonProvider jsonProvider() {
JacksonJsonProvider provider = new JacksonJsonProvider();
provider.setMapper(objectMapper);
return provider;
}
@Bean
public Server rsServer() {
JAXRSServerFactoryBean server = new JAXRSServerFactoryBean();
server.setProviders(Stream.of(jsonProvider()).collect(Collectors.toList()));
server.setBus(bus);
server.setServiceBeans(Stream
.of(dataService, eventService, sessionService, statusService)
.collect(Collectors.toList()));
return server.create();
}
Upvotes: 1
Reputation: 4101
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.
Upvotes: 42
Reputation: 12212
The solution was to add the dependency to the classpath. For some reason, it was not in the IDE.
Even though the dependency is deprecated, it's still used by spring-boot-autoconfigure
module. See Spring Boot Code
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
when it's in the classpath, Java 8 date and time objects are serialized as a timestamp.
Upvotes: 4
Reputation: 2086
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.
Upvotes: 7