When using Spring with Kotlin, there is one interesting thing with duration fields serialisation. For instance, when you write a REST endpoint to receive the following body:

{
  "name": "Jim",
  "duration": "PT1S"
}

...and write the following DTO for it:

class KotlinDurationDto(
    val name: String,
    val duration: Duration,
)

..., then the first Duration class that the IDE suggests to import is kotlin.time.Duration. That's reasonable since you are writing in Kotlin.

And then, sending the request to that endpoint results in an HTTP 500 response and the following exception in the service logs:

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: 
Cannot construct instance of `kotlin.time.Duration` (no Creators, like default constructor, exist): 
no String-argument constructor/factory method to deserialize from String value ('PT1S')

This is what you get with Spring Boot 3.3.x and 3.4.x. If you are lucky enough to use an older version, the behaviour may differ. For instance, in Spring Boot 3.2.12, the result is an HTTP 400 with the following warning in the logs:

org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error:
Cannot deserialize value of type `long` from String "PT1S": not a valid `long` value

But that's not all! If the duration is of a nullable type, i.e.:

class KotlinNullableDurationDto(
    val name: String,
    val duration: Duration? = null,
)

...then you'll get another result: an HTTP 500 with the same logs as before:

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: 
Cannot construct instance of `kotlin.time.Duration` (no Creators, like default constructor, exist): 
no String-argument constructor/factory method to deserialize from String value ('PT1S')

Spring Boot 3.3.x+ and the Jackson version that it brings are at least consistent when handling nullable and non-nullable types.

If you are even luckier (like me) to have a service with a lot of dependencies that cause conflicts with Jackson dependency versions (that you haven't found in time), then you may get a weird NullPointerException during serialisation, pointing to a field that is neither of type Duration nor nullable.

The solution for this could be to just use java.time.Duration instead of kotlin.time.Duration.

Here are a couple of FasterXML-related issues on GitHub:

And here is a small project on GitHub to play around with: pfilaretov42/spring-kotlin-duration.


Dream your code, code your dream.