Merge pull request #788 from sharanggupta/fix-609-array-into-string

Fix #609: Reject a JSON array or object where a value is expected
diff --git a/pom.xml b/pom.xml
index dde3171..5f0f011 100644
--- a/pom.xml
+++ b/pom.xml
@@ -61,7 +61,7 @@
         <!--Plugins-->
         <build-helper-maven-plugin.version>3.6.0</build-helper-maven-plugin.version>
         <buildnumber-maven-plugin.version>3.3.1</buildnumber-maven-plugin.version>
-        <spotbugs-maven-plugin.version>4.10.4.1</spotbugs-maven-plugin.version>
+        <spotbugs-maven-plugin.version>4.10.5.0</spotbugs-maven-plugin.version>
         <glassfish-copyright-maven-plugin.version>2.4</glassfish-copyright-maven-plugin.version>
         <maven-bundle-plugin.version>5.1.9</maven-bundle-plugin.version>
         <maven-checkstyle-plugin.version>3.3.1</maven-checkstyle-plugin.version>
diff --git a/src/main/java/org/eclipse/yasson/internal/AnnotationIntrospector.java b/src/main/java/org/eclipse/yasson/internal/AnnotationIntrospector.java
index 2228c51..8edc2af 100644
--- a/src/main/java/org/eclipse/yasson/internal/AnnotationIntrospector.java
+++ b/src/main/java/org/eclipse/yasson/internal/AnnotationIntrospector.java
@@ -1,5 +1,6 @@
 /*
  * Copyright (c) 2016, 2026 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2026 Contributors to the Eclipse Foundation
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0 which is available at
@@ -594,9 +595,16 @@
      */
     public JsonbDateFormatter getConstructorDateFormatter(JsonbAnnotatedElement<Parameter> param) {
         return param.getAnnotation(JsonbDateFormat.class)
-                .map(annotation -> new JsonbDateFormatter(DateTimeFormatter.ofPattern(annotation.value(),
-                                                                                      Locale.forLanguageTag(annotation.locale())),
-                                                          annotation.value(), annotation.locale()))
+                .map(annotation -> {
+                    String format = annotation.value();
+                    String locale = annotation.locale();
+                    if (JsonbDateFormat.TIME_IN_MILLIS.equals(format) || JsonbDateFormat.DEFAULT_FORMAT.equals(format)) {
+                        //not patterns, handled by the date converters as for properties
+                        return new JsonbDateFormatter(format, locale);
+                    }
+                    return new JsonbDateFormatter(DateTimeFormatter.ofPattern(format, Locale.forLanguageTag(locale)),
+                                                  format, locale);
+                })
                 .orElse(null);
     }
 
diff --git a/src/main/java/org/eclipse/yasson/internal/ComponentMatcher.java b/src/main/java/org/eclipse/yasson/internal/ComponentMatcher.java
index 2763ea9..b27b63f 100644
--- a/src/main/java/org/eclipse/yasson/internal/ComponentMatcher.java
+++ b/src/main/java/org/eclipse/yasson/internal/ComponentMatcher.java
@@ -1,5 +1,6 @@
 /*
  * Copyright (c) 2016, 2022 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2026 Contributors to the Eclipse Foundation
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0 which is available at
@@ -418,7 +419,7 @@
                 .findParameterizedType(deserializerClass, JsonbDeserializer.class);
         Type deserializerBindingType = resolveTypeArg(deserializerRuntimeType.getActualTypeArguments()[0], deserializerClass);
         final ComponentBindings componentBindings = getBindingInfo(deserializerBindingType);
-        if (componentBindings.getDeserializer() != null && componentBindings.getDeserializer().getClass()
+        if (componentBindings.getDeserializer() != null && componentBindings.getDeserializer().getJsonbDeserializer().getClass()
                 .equals(deserializerClass)) {
             return componentBindings.getDeserializer();
         } else {
@@ -442,7 +443,8 @@
                 .findParameterizedType(serializerClass, JsonbSerializer.class);
         Type serBindingType = resolveTypeArg(serializerRuntimeType.getActualTypeArguments()[0], serializerClass);
         final ComponentBindings componentBindings = getBindingInfo(serBindingType);
-        if (componentBindings.getSerializer() != null && componentBindings.getSerializer().getClass().equals(serializerClass)) {
+        if (componentBindings.getSerializer() != null
+                && componentBindings.getSerializer().getJsonbSerializer().getClass().equals(serializerClass)) {
             return componentBindings.getSerializer();
         } else {
             JsonbSerializer serializer = instance != null ? instance : jsonbContext.getComponentInstanceCreator()
diff --git a/src/test/java/org/eclipse/yasson/records/RecordTest.java b/src/test/java/org/eclipse/yasson/records/RecordTest.java
index edaee35..bf3ad00 100644
--- a/src/test/java/org/eclipse/yasson/records/RecordTest.java
+++ b/src/test/java/org/eclipse/yasson/records/RecordTest.java
@@ -24,6 +24,8 @@
 import org.eclipse.yasson.internal.properties.Messages;
 import org.junit.jupiter.api.Test;
 
+import java.time.Instant;
+import java.time.LocalDate;
 import java.util.List;
 import java.util.Set;
 
@@ -50,6 +52,18 @@
     }
 
     @Test
+    public void testRecordWithDateFormats() {
+        String json = "{\"iso\":\"2024-10-11T13:28:22Z\",\"localeOnly\":\"2024-10-11\",\"millis\":1728574424000}";
+
+        RecordWithDateFormats deserialized = Jsonbs.defaultJsonb.fromJson(json, RecordWithDateFormats.class);
+
+        assertEquals(Instant.ofEpochMilli(1728574424000L), deserialized.millis());
+        assertEquals(LocalDate.of(2024, 10, 11), deserialized.localeOnly());
+        assertEquals(Instant.parse("2024-10-11T13:28:22Z"), deserialized.iso());
+        assertEquals(json, Jsonbs.defaultJsonb.toJson(deserialized));
+    }
+
+    @Test
     public void testRecordProcessingWithoutJsonbProperties() {
         CarWithoutAnnotations car = new CarWithoutAnnotations("skoda", "green");
         String expected = "{\"color\":\"green\",\"type\":\"skoda\"}";
diff --git a/src/test/java/org/eclipse/yasson/records/RecordWithDateFormats.java b/src/test/java/org/eclipse/yasson/records/RecordWithDateFormats.java
new file mode 100644
index 0000000..a247f13
--- /dev/null
+++ b/src/test/java/org/eclipse/yasson/records/RecordWithDateFormats.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2026 Contributors to the Eclipse Foundation
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0,
+ * or the Eclipse Distribution License v. 1.0 which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
+ */
+
+package org.eclipse.yasson.records;
+
+import java.time.Instant;
+import java.time.LocalDate;
+
+import jakarta.json.bind.annotation.JsonbDateFormat;
+
+public record RecordWithDateFormats(@JsonbDateFormat(JsonbDateFormat.TIME_IN_MILLIS) Instant millis,
+                                    @JsonbDateFormat(locale = "de") LocalDate localeOnly,
+                                    Instant iso) {
+}
diff --git a/src/test/java/org/eclipse/yasson/serializers/RegisteredComponentReuseTest.java b/src/test/java/org/eclipse/yasson/serializers/RegisteredComponentReuseTest.java
new file mode 100644
index 0000000..ec5b661
--- /dev/null
+++ b/src/test/java/org/eclipse/yasson/serializers/RegisteredComponentReuseTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2026 Contributors to the Eclipse Foundation
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0,
+ * or the Eclipse Distribution License v. 1.0 which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
+ */
+
+package org.eclipse.yasson.serializers;
+
+import java.lang.reflect.Type;
+
+import jakarta.json.bind.Jsonb;
+import jakarta.json.bind.JsonbBuilder;
+import jakarta.json.bind.JsonbConfig;
+import jakarta.json.bind.annotation.JsonbTypeDeserializer;
+import jakarta.json.bind.annotation.JsonbTypeSerializer;
+import jakarta.json.bind.serializer.DeserializationContext;
+import jakarta.json.bind.serializer.JsonbDeserializer;
+import jakarta.json.bind.serializer.JsonbSerializer;
+import jakarta.json.bind.serializer.SerializationContext;
+import jakarta.json.stream.JsonGenerator;
+import jakarta.json.stream.JsonParser;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * A serializer or deserializer instance registered in the {@link JsonbConfig} is used for the
+ * properties annotated with its class, instead of a new instance
+ * (<a href="https://github.com/eclipse-ee4j/yasson/issues/627">#627</a>).
+ */
+public class RegisteredComponentReuseTest {
+
+    public static class PrefixSerializer implements JsonbSerializer<String> {
+
+        private final String prefix;
+
+        public PrefixSerializer() {
+            this("new:");
+        }
+
+        public PrefixSerializer(String prefix) {
+            this.prefix = prefix;
+        }
+
+        @Override
+        public void serialize(String obj, JsonGenerator generator, SerializationContext ctx) {
+            generator.write(prefix + obj);
+        }
+    }
+
+    public static class PrefixDeserializer implements JsonbDeserializer<String> {
+
+        private final String prefix;
+
+        public PrefixDeserializer() {
+            this("new:");
+        }
+
+        public PrefixDeserializer(String prefix) {
+            this.prefix = prefix;
+        }
+
+        @Override
+        public String deserialize(JsonParser parser, DeserializationContext ctx, Type rtType) {
+            return prefix + parser.getString();
+        }
+    }
+
+    public static class Annotated {
+
+        @JsonbTypeSerializer(PrefixSerializer.class)
+        @JsonbTypeDeserializer(PrefixDeserializer.class)
+        public String value;
+    }
+
+    @Test
+    public void registeredSerializerIsUsedForAnnotatedProperty() throws Exception {
+        Annotated annotated = new Annotated();
+        annotated.value = "x";
+        try (Jsonb jsonb = JsonbBuilder.create(new JsonbConfig().withSerializers(new PrefixSerializer("registered:")))) {
+            assertEquals("{\"value\":\"registered:x\"}", jsonb.toJson(annotated));
+        }
+    }
+
+    @Test
+    public void registeredDeserializerIsUsedForAnnotatedProperty() throws Exception {
+        try (Jsonb jsonb = JsonbBuilder.create(new JsonbConfig().withDeserializers(new PrefixDeserializer("registered:")))) {
+            assertEquals("registered:x", jsonb.fromJson("{\"value\":\"x\"}", Annotated.class).value);
+        }
+    }
+
+    @Test
+    public void annotatedPropertyWithoutRegisteredInstanceUsesNewInstance() throws Exception {
+        Annotated annotated = new Annotated();
+        annotated.value = "x";
+        try (Jsonb jsonb = JsonbBuilder.create()) {
+            assertEquals("{\"value\":\"new:x\"}", jsonb.toJson(annotated));
+            assertEquals("new:x", jsonb.fromJson("{\"value\":\"x\"}", Annotated.class).value);
+        }
+    }
+}