Open
Description
Describe the bug
Using this record as example:
record Example(@JsonbDateFormat(TIME_IN_MILLIS) Instant longInstant, Instant stringInstant) {}
and this JSON to deserialize:
{ "longInstant": 1728574424000, "stringInstant": "2024-10-11T13:28:22Z" }
The deserialization fails with the following exception:
jakarta.json.bind.JsonbException: Internal error: Pattern includes reserved character: '#'
at org.eclipse.yasson.internal.DeserializationContextImpl.deserializeItem(DeserializationContextImpl.java:142)
at org.eclipse.yasson.internal.DeserializationContextImpl.deserialize(DeserializationContextImpl.java:127)
at org.eclipse.yasson.internal.JsonBinding.deserialize(JsonBinding.java:55)
at org.eclipse.yasson.internal.JsonBinding.fromJson(JsonBinding.java:62)
...
Caused by: java.lang.IllegalArgumentException: Pattern includes reserved character: '#'
at java.base/java.time.format.DateTimeFormatterBuilder.parsePattern(DateTimeFormatterBuilder.java:2053)
at java.base/java.time.format.DateTimeFormatterBuilder.appendPattern(DateTimeFormatterBuilder.java:1907)
at java.base/java.time.format.DateTimeFormatter.ofPattern(DateTimeFormatter.java:593)
at org.eclipse.yasson.internal.AnnotationIntrospector.lambda$getConstructorDateFormatter$9(AnnotationIntrospector.java:595)
at java.base/java.util.Optional.map(Optional.java:260)
at org.eclipse.yasson.internal.AnnotationIntrospector.getConstructorDateFormatter(AnnotationIntrospector.java:595)
at org.eclipse.yasson.internal.model.CreatorModel.<init>(CreatorModel.java:55)
at org.eclipse.yasson.internal.AnnotationIntrospector.createJsonbCreator(AnnotationIntrospector.java:212)
at org.eclipse.yasson.internal.ClassMultiReleaseExtension.findCreator(ClassMultiReleaseExtension.java:55)
at org.eclipse.yasson.internal.AnnotationIntrospector.getCreator(AnnotationIntrospector.java:189)
at org.eclipse.yasson.internal.AnnotationIntrospector.introspectCustomization(AnnotationIntrospector.java:798)
at org.eclipse.yasson.internal.MappingContext.lambda$createParseClassModelFunction$1(MappingContext.java:88)
at java.base/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1713)
at org.eclipse.yasson.internal.MappingContext.getOrCreateClassModel(MappingContext.java:77)
at org.eclipse.yasson.internal.deserializer.DeserializationModelCreator.deserializerChain(DeserializationModelCreator.java:122)
at org.eclipse.yasson.internal.DeserializationContextImpl.deserializeItem(DeserializationContextImpl.java:137)
... 9 more
To Reproduce
Run the following Junit5 test:
import jakarta.json.bind.Jsonb;
import jakarta.json.bind.JsonbBuilder;
import jakarta.json.bind.annotation.JsonbDateFormat;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.time.Instant;
import static jakarta.json.bind.annotation.JsonbDateFormat.TIME_IN_MILLIS;
import static java.time.temporal.ChronoUnit.MILLIS;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
class TimeInMillisRecordDeserializationTest {
public record Example(@JsonbDateFormat(TIME_IN_MILLIS) Instant longInstant, Instant stringInstant) {}
Jsonb jsonb;
@BeforeEach
void setUp() {
jsonb = JsonbBuilder.create();
}
@AfterEach
void tearDown() throws Exception {
jsonb.close();
}
@Test
void shouldDeserialize() {
var expected = new Example(Instant.now().truncatedTo(MILLIS), Instant.now());
var json = "{ \"longInstant\": %d, \"stringInstant\": \"%s\" }"
.formatted(expected.longInstant().toEpochMilli(), expected.stringInstant());
var actual = assertDoesNotThrow(() -> jsonb.fromJson(json, Example.class));
assertEquals(expected, actual);
}
}
This test will fail with the exception mentioned above.
Expected behavior
The test passes successfully.
System information:
- OS: macOS 15.0.1
- Java Version: 23
- Yasson Version: 3.0.4
Additional context
Using the annotation on the record level works fine, but forces all datetime components in the record to be deserialized from epoch millis:
@JsonbDateFormat(TIME_IN_MILLIS)
record Example(Instant longInstant, Instant stringInstant) {}
Also, the annotation works fine on POJOs:
class Example {
@JsonbDateFormat(TIME_IN_MILLIS)
final Instant longInstant;
final Instant stringInstant;
@JsonbCreator
Example(Instant longInstant, Instant stringInstant) { /* ... */ }
// getters,equals,hashCode...
}