Spring Boot 2.5.0 and InvalidDefinitionException: Java 8 date/time type `java.time.Instant` not supported by default
I saw an issue in one of my test classes. The problem there was it was creating a new ObjectMapper instance that was not adding the JavaTimeModule.
Here is a sample test that works in Spring 2.4.5 but fails in 2.5.0/2.5.1 with com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Java 8 date/time type java.time.ZonedDateTime
not supported by default
It might be due to the upgrade in the jackson-datatype-jsr310 version
package net.jpmchase.gti.gtfabric;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import java.time.ZonedDateTime;
public class ObjectMapperTest {
@Test
public void objectMapperTest() throws Exception{
ZonedDateTime time = ZonedDateTime.now();
ObjectMapper o = new ObjectMapper();
o.writeValueAsString(time);
}
}
To fix this particular test case had to add an explicit
ObjectMapper o = new ObjectMapper();
o.registerModule(new JavaTimeModule());
Spring webtestclient serializes dates to timestamps instead of dates
You need to define an ObjectMapper
bean so that the auto-configured one is not used:
@Configuration(proxyBeanMethods = false)
class JacksonConfiguration {
companion object {
val serializationDateFormat: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssXX")
val deserializationDateFormat: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm[:ss][XXX][X]")
}
@Bean
fun objectMapper() = jacksonObjectMapper().applyDefaultSettings ()
private fun ObjectMapper.applyDefaultSettings() =
apply {
disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)
disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE)
setSerializationInclusion(JsonInclude.Include.NON_NULL)
registerModule(Jdk8Module())
registerModule(ParameterNamesModule())
registerModule(JsonComponentModule())
registerModule(
JavaTimeModule().apply {
addSerializer(ZonedDateTime::class.java, ZonedDateTimeSerializer(serializationDateFormat))
addDeserializer(ZonedDateTime::class.java, ZonedDateTimeDeserializer())
}
)
}
class ZonedDateTimeDeserializer : JsonDeserializer<ZonedDateTime>() {
override fun deserialize(jsonParser: JsonParser, deserializationContext: DeserializationContext): ZonedDateTime {
val epochTime = jsonParser.text.toLongOrNull()
return if (epochTime != null) {
ZonedDateTime.ofInstant(
Instant.ofEpochSecond(epochTime),
currentZone
)
} else {
ZonedDateTime.parse(jsonParser.text, deserializationDateFormat)
}
}
}
}
Timezone of ZonedDateTime changed to UTC during auto conversion of RequestBody with Spring Boot
Add the following line to the application.properties
file:
spring.jackson.deserialization.ADJUST_DATES_TO_CONTEXT_TIME_ZONE = false
References:
- Baeldung: Jackson Date: Deserialize Joda ZonedDateTime with Time Zone Preserved
- Javadoc: jackson-databind 2.6.0 API: ADJUST_DATES_TO_CONTEXT_TIME_ZONE
- Spring Boot: “How-to” Guides: Customize the Jackson ObjectMapper
ZonedDateTime round trip via Jackson produces unequal ZonedDateTime
Quoting Wikipedia for ISO 8601:
If the time is in UTC, add a
Z
directly after the time without a space.Z
is the zone designator for the zero UTC offset."09:30 UTC"
is therefore represented as"09:30Z"
or"0930Z"
."14:45:15 UTC"
would be"14:45:15Z"
or"144515Z"
.UTC time is also known as Zulu time, since Zulu is the NATO phonetic alphabet word for Z.
Z
is not a Zone. UTC
is the Zone, which is then represented using Z
in a formatted string.
Don't ever use ZoneId.of("Z")
. It's wrong.
The request sent by the client was syntactically incorrect Java ZonedDateTime backend
Solution:
Assume you are using the most "default" configuration, which is based on FasterXML Jackson.
If so, then you just need to configure properly serializer and desirializer for ZonedDateTime
in your application; and it might be either custom ones or the ones from jackson-datatype-jsr310 (recommended).
I've created a small/minimal example, which is based on the Spring 5.0.9 and Jackson 2.9.6 (latest versions currently).
Please find it here: spring5-rest-zoneddatetime >>, main parts are:
Event
DTO:public class Event {
private long id;
private String name;
private ZonedDateTime time;
// Constructors, public getters and setters
}Field
time
might be apublic
one same to your sample, it is also fine, but if field isprivate
- then you will needpublic
getter and setter.NOTE: I'm ignoring here
@DynamoDBTypeConverted
and@DynamoDBAttribute
annotations since they are related to persistence logic, not the REST layer.EventController
contains only one method same to yours:@RestController
public class EventController {
@RequestMapping(value = "/event", method = RequestMethod.POST)
public ResponseEntity post(@RequestBody Event event) {
System.out.println("Event posted: " + event.toString());
return ResponseEntity.ok(event);
}
}Dependencies in the
pom.xml
looks so:<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.0.9.RELEASE</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.6</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.9.6</version>
</dependency>The important one here is JSR-310 datatype implementation, which also introduces
com.fasterxml.jackson.datatype.jsr310.ser.ZonedDateTimeSerializer
andcom.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer
.
Additional information:
In case of custom serializer/desirializer will be needed, please check this question >>
Next date formats will be supported for the
time
field:"2018-01-01T22:25:15+01:00[Europe/Paris]"
- not fully an ISO 8601 btw"2018-01-01T22:25:15+01:00"
"2018-01-01T22:25:15.000000001Z"
1514768461.000000001
- float-pointing number, amount of seconds from the1970-01-01, 00:00:00 [UTC]
By default REST APi response will use float-pointing numbers for dates, e.g. in our case response will look so:
{
"id": 3,
"name": "Test event",
"time": 1514768460
}To return string values instead, please check e.g. this question >>
Also need to mention that if you will use Spring Boot instead (good starter) - all things discussed above will work out of the box.
Related Topics
Spring Security Configuration Filter Any Requests Except a Specific Endpoint
Time Complexity of Hashmap Methods
Javac: File Not Found: First.Java Usage: Javac <Options> <Source Files>
How to Loop Through List of Webelements and Select One Webelement With a Condition
Android Webview Displaying Blank Page
How to Read Empty Cells of an Excel File Using Poi
Determine the Number of Pages in a Pdf File
Way to Check If Two Collections Contain the Same Elements, Independent of Order
Custom Pagination by Array List
Jackson Deserialization Issue for Zoneddatetime
How to Sort Json Object in Java
How to Specify the Required Java Version in a Gradle Build
Java Simpledateformatter With 10 Decimals After the Seconds, Cannot Convert to Date
Error: Could Not Find or Load Main Class in Intellij Ide
Typecasting an Object from Parent Class to Child
How to Call a Function Only After the Previous Function Is Fully Executed in Java
How to Efficiently Read Multiple Json Files into a Dataframe or Javardd