Repository: kestra-io/kestra (java)

Issue:
Duration is not correctly deserialized with Property v2 default value
### Describe the issue

When testing the plugin Azure Datafactory locally I am able to run it when the duration property are set in the flow. 
But I am getting a de-serialization issue when using the default properties. 

Here's the PR with the code causing issue : [PR](https://github.com/kestra-io/plugin-azure/pull/148)

Here's some videos example of the issue : 
- Working : [duration_working_when_set.webm](https://github.com/user-attachments/assets/4b8337a7-d511-4b6c-9013-06a3e204180f)
- Not working when using default : [duration_not_working_when_using_default.webm](https://github.com/user-attachments/assets/9246ac44-a71a-4e13-9fa5-96408f9538b5)

Here's the stack trace : 
```
java.lang.IllegalArgumentException: Cannot deserialize value of type `java.time.Duration` from String "5.000000000": Failed to deserialize java.time.Duration: (java.time.format.DateTimeParseException) Text cannot be parsed to a Duration
2024-10-23T08:19:54.961268349Z  at [Source: UNKNOWN; byte offset: #UNKNOWN]
2024-10-23T08:19:54.961274856Z 	at com.fasterxml.jackson.databind.ObjectMapper._convert(ObjectMapper.java:4636)
2024-10-23T08:19:54.961281495Z 	at com.fasterxml.jackson.databind.ObjectMapper.convertValue(ObjectMapper.java:4567)
2024-10-23T08:19:54.961288035Z 	at io.kestra.core.models.property.Property.as(Property.java:82)
2024-10-23T08:19:54.961294368Z 	at io.kestra.core.runners.DefaultRunContext.render(DefaultRunContext.java:210)
2024-10-23T08:19:54.961301152Z 	at io.kestra.plugin.azure.datafactory.CreateRun.run(CreateRun.java:167)
2024-10-23T08:19:54.961307687Z 	at io.kestra.plugin.azure.datafactory.CreateRun.run(CreateRun.java:52)
2024-10-23T08:19:54.961314143Z 	at io.kestra.core.runners.WorkerTaskCallable.doCall(WorkerTaskCallable.java:78)
2024-10-23T08:19:54.961320565Z 	at io.kestra.core.runners.AbstractWorkerCallable.call(AbstractWorkerCallable.java:56)
2024-10-23T08:19:54.961327187Z 	at io.kestra.core.runners.WorkerSecurityService.callInSecurityContext(WorkerSecurityService.java:10)
2024-10-23T08:19:54.961333770Z 	at io.kestra.core.runners.Worker.callJob(Worker.java:773)
2024-10-23T08:19:54.961357858Z 	at io.kestra.core.runners.Worker.runAttempt(Worker.java:728)
2024-10-23T08:19:54.961365543Z 	at io.kestra.core.runners.Worker.run(Worker.java:580)
2024-10-23T08:19:54.961396656Z 	at io.kestra.core.runners.Worker.handleTask(Worker.java:284)
2024-10-23T08:19:54.961403247Z 	at io.kestra.core.runners.Worker.lambda$run$7(Worker.java:240)
2024-10-23T08:19:54.961408074Z 	at io.micrometer.core.instrument.internal.TimedRunnable.run(TimedRunnable.java:49)
2024-10-23T08:19:54.961412587Z 	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
2024-10-23T08:19:54.961417118Z 	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
2024-10-23T08:19:54.961421681Z 	at java.base/java.lang.Thread.run(Unknown Source)
2024-10-23T08:19:54.961428718Z Caused by: com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `java.time.Duration` from String "5.000000000": Failed to deserialize java.time.Duration: (java.time.format.DateTimeParseException) Text cannot be parsed to a Duration
2024-10-23T08:19:54.961434833Z  at [Source: UNKNOWN; byte offset: #UNKNOWN]
2024-10-23T08:19:54.961439264Z 	at com.fasterxml.jackson.databind.exc.InvalidFormatException.from(InvalidFormatException.java:67)
2024-10-23T08:19:54.961443735Z 	at com.fasterxml.jackson.databind.DeserializationContext.weirdStringException(DeserializationContext.java:1959)
2024-10-23T08:19:54.961448484Z 	at com.fasterxml.jackson.databind.DeserializationContext.handleWeirdStringValue(DeserializationContext.java:1245)
2024-10-23T08:19:54.961453060Z 	at com.fasterxml.jackson.datatype.jsr310.deser.JSR310DeserializerBase._handleDateTimeException(JSR310DeserializerBase.java:176)
2024-10-23T08:19:54.961457858Z 	at com.fasterxml.jackson.datatype.jsr310.deser.DurationDeserializer._fromString(DurationDeserializer.java:200)
2024-10-23T08:19:54.961462378Z 	at com.fasterxml.jackson.datatype.jsr310.deser.DurationDeserializer.deserialize(DurationDeserializer.java:163)
2024-10-23T08:19:54.961467011Z 	at com.fasterxml.jackson.datatype.jsr310.deser.DurationDeserializer.deserialize(DurationDeserializer.java:44)
2024-10-23T08:19:54.961471506Z 	at com.fasterxml.jackson.databind.ObjectMapper._convert(ObjectMapper.java:4631)
2024-10-23T08:19:54.961476109Z 	... 17 common frames omitted
2024-10-23T08:19:54.961480392Z Caused by: java.time.format.DateTimeParseException: Text cannot be parsed to a Duration
2024-10-23T08:19:54.961484900Z 	at java.base/java.time.Duration.parse(Unknown Source)
2024-10-23T08:19:54.961489191Z 	at com.fasterxml.jackson.datatype.jsr310.deser.DurationDeserializer._fromString(DurationDeserializer.java:198)
2024-10-23T08:19:54.961493734Z 	... 20 common frames omitted
```

### Environment

- Kestra Version: last version of kestra:develop
- Operating System (OS/Docker/Kubernetes):
- Java Version (if you don't run kestra in Docker):


Pull request:
fix(core): serialize duration as strings
Fixes #5615

Since Jackson 2.10, to serialize duration as string we need to switch WRITE_DURATIONS_AS_TIMESTAMPS off as it no longuer use WRITE_DATES_AS_TIMESTAMPS. I tested and an old flow with a duration as timestamp is still readable so this is a backward compatible change.


Patch:
diff --git a/core/src/main/java/io/kestra/core/models/flows/State.java b/core/src/main/java/io/kestra/core/models/flows/State.java
index 76cd800f1..5bef62cbc 100644
--- a/core/src/main/java/io/kestra/core/models/flows/State.java
+++ b/core/src/main/java/io/kestra/core/models/flows/State.java
@@ -1,8 +1,12 @@
 package io.kestra.core.models.flows;
 
+import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.datatype.jsr310.ser.DurationSerializer;
+import com.fasterxml.jackson.datatype.jsr310.util.DurationUnitConverter;
 import io.micronaut.core.annotation.Introspected;
 import lombok.Value;
 import lombok.extern.slf4j.Slf4j;
@@ -84,10 +88,11 @@ public class State {
     }
 
     @JsonProperty(access = JsonProperty.Access.READ_ONLY)
+    @JsonFormat(shape = JsonFormat.Shape.NUMBER_INT) // force serialization as timestamp as the database column is using a number
     public Duration getDuration() {
         return Duration.between(
             this.histories.getFirst().getDate(),
-            this.histories.size() > 1 ? this.histories.get(this.histories.size() - 1).getDate() : Instant.now()
+            this.histories.size() > 1 ? this.histories.getLast().getDate() : Instant.now()
         );
     }
 
diff --git a/core/src/main/java/io/kestra/core/serializers/JacksonMapper.java b/core/src/main/java/io/kestra/core/serializers/JacksonMapper.java
index abcb9528f..cc9db385b 100644
--- a/core/src/main/java/io/kestra/core/serializers/JacksonMapper.java
+++ b/core/src/main/java/io/kestra/core/serializers/JacksonMapper.java
@@ -121,6 +121,7 @@ public final class JacksonMapper {
     private static ObjectMapper configure(ObjectMapper mapper) {
         return mapper
             .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
+            .configure(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS, false)
             .setSerializationInclusion(JsonInclude.Include.NON_NULL)
             .registerModule(new JavaTimeModule())
             .registerModule(new Jdk8Module())


Test patch:
diff --git a/core/src/test/java/io/kestra/plugin/core/kv/SetTest.java b/core/src/test/java/io/kestra/plugin/core/kv/SetTest.java
index 36e798466..58abf0030 100644
--- a/core/src/test/java/io/kestra/plugin/core/kv/SetTest.java
+++ b/core/src/test/java/io/kestra/plugin/core/kv/SetTest.java
@@ -209,8 +209,7 @@ public class SetTest {
         assertThat(kv.getValue(TEST_KEY).get().value(), is(Instant.parse("2023-05-02T01:02:03Z")));
 
         set.toBuilder().value("P1DT5S").kvType(KVType.DURATION).build().run(runContext);
-        // TODO Hack meanwhile we handle duration serialization as currently they are stored as bigint...
-        assertThat((long) Double.parseDouble(kv.getValue(TEST_KEY).get().value().toString()), is(Duration.ofDays(1).plus(Duration.ofSeconds(5)).toSeconds()));
+        assertThat(kv.getValue(TEST_KEY).get().value(), is(Duration.ofDays(1).plus(Duration.ofSeconds(5))));
 
         set.toBuilder().value("[{\"some\":\"value\"},{\"another\":\"value\"}]").kvType(KVType.JSON).build().run(runContext);
         assertThat(kv.getValue(TEST_KEY).get().value(), is(List.of(Map.of("some", "value"), Map.of("another", "value"))));