Merge pull request #710 from KyleAure/707-fix-isIntegralNumber

707 fix is integral number
diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml
index 56e8e7c..6374c2a 100644
--- a/.github/workflows/maven.yml
+++ b/.github/workflows/maven.yml
@@ -24,20 +24,26 @@
 
 jobs:
   build:
-    name: Test on JDK ${{ matrix.java_version }}
+    name: Test on JDK ${{ matrix.java_version }} locale ${{ matrix.locale }}
     runs-on: ubuntu-latest
 
     strategy:
       matrix:
         java_version: [ 11, 17, 21 ]
+        include:
+          - locale: en_US
+          # Use a different locale when running against one of the java versions
+          # to ensure we correctly write tests that account for localization differences.
+          - locale: de_AT
+            java_version: 21
 
     steps:
       - name: Checkout for build
-        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
         with:
           fetch-depth: 0
       - name: Set up compile JDK
-        uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
+        uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0
         with: #Compile java needs to be the highest to ensure proper compilation of the multi-release jar
           distribution: 'temurin'
           java-version: 17
@@ -49,6 +55,12 @@
       - name: Yasson install
         run: mvn -U -C clean install -DskipTests
       - name: Yasson tests
-        run: mvn -U -B -C -Dmaven.javadoc.skip=true verify
+        run: |
+          export LANG_TAG="${{ matrix.locale }}"
+          mvn -U -B -C \
+            -Dmaven.javadoc.skip=true \
+            -Duser.language="${LANG_TAG%%_*}" \
+            -Duser.country="${LANG_TAG##*_}" \
+            verify
       - name: JSONB-API TCK
         run: cd yasson-tck && mvn -U -B test -DargLine="-Djava.locale.providers=COMPAT"
diff --git a/.github/workflows/performance.yml b/.github/workflows/performance.yml
new file mode 100644
index 0000000..1e06af1
--- /dev/null
+++ b/.github/workflows/performance.yml
@@ -0,0 +1,62 @@
+#
+# Copyright (c) 2026 Eclipse Foundation and/or its affiliates. All rights reserved.
+#
+# 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
+#
+
+# This workflow performs performance testing using JMH.
+# It is intended to be run on a schedule (e.g. weekly) to track performance over time, but can also be triggered manually when needed.
+# Persist results to github pages for continuous line graphs and regression alerts.
+
+name: Performance
+
+on:
+    schedule:
+        # Runs every Tuesday at 4:13 AM UTC (avoid peak hours for better performance results)
+        - cron: '13 4 * * 2' 
+    workflow_dispatch:
+    
+jobs:
+    performance:
+        name: Run JMH performance tests
+        runs-on: ubuntu-latest
+
+        steps:
+            - name: Checkout for performance
+              uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+              with:
+                fetch-depth: 0
+            - name: Set up compile JDK
+              uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
+              with: #Compile java needs to be the highest to ensure proper compilation of the multi-release jar
+                distribution: 'temurin'
+                java-version: 17
+                cache: 'maven'
+            - name: Compile Yasson
+              run: |
+                mvn -U -C clean install -DskipTests
+            - name: Run JMH performance tests
+              run: |
+                cd yasson-jmh
+                mvn -U -B clean package
+                java -jar target/yasson-jmh.jar -rf json -rff jmh-result.json
+            - name: Persist JMH results
+              uses: benchmark-action/github-action-benchmark@52576c92bccf6ac60c8223ec7eb2565637cae9ba # v1.22.1
+              with:
+                name: Java JMH Benchmark
+                tool: 'jmh'
+                output-file-path: jmh-result.json
+                github-token: ${{ secrets.GITHUB_TOKEN }}
+            
+                # Enable deployment to GitHub Pages for continuous line graphs
+                auto-push: true
+                comment-on-alert: true       # Creates a comment if performance degrades
+                fail-on-alert: true          # Fails the workflow run on regression
+                alert-threshold: '200%'      # Triggers an alert if execution time doubles
+
diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml
new file mode 100644
index 0000000..5329115
--- /dev/null
+++ b/.github/workflows/verify.yml
@@ -0,0 +1,54 @@
+#
+# Copyright (c) 2026 Eclipse Foundation and/or its affiliates. All rights reserved.
+#
+# 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
+#
+
+# This workflow is used to verify this project against staged versions of the API and TCK. 
+# It is not intended to be used for regular CI builds, but rather as a manual workflow that can be triggered when needed.
+
+name: Verify
+
+on:
+    workflow_dispatch:
+        inputs:
+            jsonb_version:
+                description: 'The version of the TCK to verify against (e.g. 3.1.0-M1)'
+                required: true
+
+jobs:
+    verify:
+        name: Verify against staged API or TCK
+        runs-on: ubuntu-latest
+
+        steps:
+            - name: Checkout for verify
+              uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+              with:
+                fetch-depth: 0
+            - name: Set up compile JDK
+              uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
+              with: #Compile java needs to be the highest to ensure proper compilation of the multi-release jar
+                distribution: 'temurin'
+                java-version: 17
+                cache: 'maven'
+            - name: Compile Yasson
+              run: |
+                mvn -U -C clean install \
+                    -Pstaged \
+                    -Djakarta.json.bind.version=${{ github.event.inputs.jsonb_version }} \
+                    -DskipTests
+            - name: Run TCK
+              run: |
+                cd yasson-tck
+                mvn -U -B test \
+                    -Pstaged \
+                    -Djakarta.json.bind.version=${{ github.event.inputs.jsonb_version }} \
+                    -Djsonb.tck.version=${{ github.event.inputs.jsonb_version }} \
+                    -DargLine="-Djava.locale.providers=COMPAT"
diff --git a/pom.xml b/pom.xml
index 0a0c8a3..fcbef73 100644
--- a/pom.xml
+++ b/pom.xml
@@ -20,7 +20,7 @@
     <parent>
         <groupId>org.eclipse.ee4j</groupId>
         <artifactId>project</artifactId>
-        <version>2.0.2</version>
+        <version>2.0.4</version>
     </parent>
 
     <groupId>org.eclipse</groupId>
@@ -34,21 +34,30 @@
 
     <properties>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+
         <maven.compiler.release>11</maven.compiler.release>
+        <maven.compiler.target>11</maven.compiler.target>
         <maven.compiler.testRelease>${maven.compiler.release}</maven.compiler.testRelease>
+        
         <nexus.staging.repository>yasson-maven2-staging</nexus.staging.repository>
 
         <!--Dependencies-->
         <hamcrest.version>2.2</hamcrest.version>
+        
+        <!-- Jakarta API -->
         <jakarta.annotation-api.version>3.0.0</jakarta.annotation-api.version>
         <jakarta.el-api.version>6.0.0</jakarta.el-api.version>
         <jakarta.enterprise.cdi-api.version>4.1.0</jakarta.enterprise.cdi-api.version>
         <jakarta.interceptor-api.version>2.2.0</jakarta.interceptor-api.version>
         <jakarta.json.bind.version>3.0.1</jakarta.json.bind.version>
         <jakarta.json.version>2.1.3</jakarta.json.version>
-        <jakarta.parson.version>1.1.7</jakarta.parson.version>
-        <junit-jupiter.version>5.10.2</junit-jupiter.version>
-        <weld-se-core.version>6.0.0.Beta1</weld-se-core.version>
+
+        <!-- Jakarta Implementation -->
+        <jakarta.parson.version>1.1.9</jakarta.parson.version>
+
+        <!-- Test dependencies-->
+        <junit-jupiter.version>5.14.4</junit-jupiter.version> <!-- TODO update to Junit 6 when running on Java 17+ -->
+        <weld-se-core.version>6.0.4.Final</weld-se-core.version>
 
         <!--Plugins-->
         <build-helper-maven-plugin.version>3.6.0</build-helper-maven-plugin.version>
@@ -365,9 +374,9 @@
                                 <goal>compile</goal>
                             </goals>
                             <configuration>
-                                <release>11</release>
-                                <source>11</source>
-                                <target>11</target>
+                                <release>${maven.compiler.release}</release>
+                                <source>${maven.compiler.release}</source>
+                                <target>${maven.compiler.release}</target>
                             </configuration>
                         </execution>
                         <execution>
@@ -434,7 +443,7 @@
                     <artifactId>maven-javadoc-plugin</artifactId>
                     <version>${maven-javadoc-plugin.version}</version>
                     <configuration>
-                        <bottom><![CDATA[Copyright &#169; 2017, 2024 Oracle Corporation. All rights reserved.<br>]]></bottom>
+                        <bottom><![CDATA[Copyright &#169; 2017, 2026 Oracle Corporation. All rights reserved.<br>]]></bottom>
                     </configuration>
                 </plugin>
                 <plugin>
diff --git a/src/main/java/org/eclipse/yasson/internal/ComponentMatcher.java b/src/main/java/org/eclipse/yasson/internal/ComponentMatcher.java
index b4a2cf8..2763ea9 100644
--- a/src/main/java/org/eclipse/yasson/internal/ComponentMatcher.java
+++ b/src/main/java/org/eclipse/yasson/internal/ComponentMatcher.java
@@ -24,6 +24,7 @@
 
 import jakarta.json.bind.JsonbConfig;
 import jakarta.json.bind.adapter.JsonbAdapter;
+import jakarta.json.bind.annotation.JsonbTypeSerializer;
 import jakarta.json.bind.serializer.JsonbDeserializer;
 import jakarta.json.bind.serializer.JsonbSerializer;
 
@@ -142,9 +143,28 @@
                                                                ComponentBoundCustomization customization) {
 
         if (customization == null || customization.getSerializerBinding() == null) {
-            return searchComponentBinding(propertyRuntimeType, ComponentBindings::getSerializer);
+            return searchComponentBinding(propertyRuntimeType, ComponentBindings::getSerializer, this::getAnnotationBasedSerializer);
         }
-        return Optional.of(customization.getSerializerBinding());
+        final SerializerBinding<?> binding = customization.getSerializerBinding();
+
+        // If the binding type exactly matches the runtime type, use it (optimization)
+        if (binding.getBindingType().equals(propertyRuntimeType)) {
+            return Optional.of(binding);
+        }
+
+        // Special handling for Object type: search for more specific serializers based on runtime type
+        // This allows annotation-based or config-based serializers on concrete types to be found
+        // when the property is declared as Object but has a specific runtime type
+        if (Object.class.equals(binding.getBindingType())) {
+            final Optional<SerializerBinding<?>> moreSpecific = searchComponentBinding(propertyRuntimeType,
+                ComponentBindings::getSerializer, this::getAnnotationBasedSerializer);
+            if (moreSpecific.isPresent()) {
+                return moreSpecific;
+            }
+        }
+
+        // Use the customization binding (user explicitly configured it for this property)
+        return Optional.of(binding);
     }
 
     /**
@@ -175,7 +195,14 @@
         if (customization == null || customization.getSerializeAdapterBinding() == null) {
             return searchComponentBinding(propertyRuntimeType, ComponentBindings::getAdapterInfo);
         }
-        return Optional.of(customization.getSerializeAdapterBinding());
+        // Check if the customization's adapter binding matches the runtime type
+        AdapterBinding binding = customization.getSerializeAdapterBinding();
+        if (matches(propertyRuntimeType, binding.getBindingType())) {
+            return Optional.of(binding);
+        }
+        // The annotation-based adapter doesn't match the runtime type,
+        // fall through to search for a better match based on runtime type
+        return searchComponentBinding(propertyRuntimeType, ComponentBindings::getAdapterInfo);
     }
 
     /**
@@ -194,7 +221,19 @@
         return Optional.of(customization.getDeserializeAdapterBinding());
     }
 
-    private <T extends AbstractComponentBinding> Optional<T> searchComponentBinding(Type runtimeType, Function<ComponentBindings, T> supplier) {
+    /**
+     * Search for a component binding for the given runtime type.
+     *
+     * @param runtimeType         The runtime type to find a component for
+     * @param supplier            Function to extract the desired component from ComponentBindings
+     * @param annotationDiscovery Optional function for runtime annotation discovery (null if not applicable)
+     * @param <T>                 The type of component binding to search for
+     * @return Optional containing the component binding if found
+     */
+    private <T extends AbstractComponentBinding> Optional<T> searchComponentBinding(
+            Type runtimeType,
+            Function<ComponentBindings, T> supplier,
+            Function<Class<?>, Optional<T>> annotationDiscovery) {
         // First check if there is an exact match
         ComponentBindings binding = userComponents.get(runtimeType);
         if (binding != null) {
@@ -206,6 +245,15 @@
         
         Optional<Class<?>> runtimeClass = ReflectionUtils.getOptionalRawType(runtimeType);
         if (runtimeClass.isPresent()) {
+            // Check for annotation-based component on the runtime type itself
+            // Currently only used for @JsonbTypeSerializer during serialization
+            if (annotationDiscovery != null) {
+                Optional<T> annotationBased = annotationDiscovery.apply(runtimeClass.get());
+                if (annotationBased.isPresent()) {
+                    return annotationBased;
+                }
+            }
+
             // Check if any interfaces have a match
             for (Class<?> ifc : runtimeClass.get().getInterfaces()) {
                 ComponentBindings ifcBinding = userComponents.get(ifc);
@@ -220,7 +268,7 @@
             // check if the superclass has a match
             Class<?> superClass = runtimeClass.get().getSuperclass();
             if (superClass != null && superClass != Object.class) {
-                Optional<T> superBinding = searchComponentBinding(superClass, supplier);
+                Optional<T> superBinding = searchComponentBinding(superClass, supplier, annotationDiscovery);
                 if (superBinding.isPresent()) {
                     return superBinding;
                 }
@@ -229,7 +277,64 @@
         
         return Optional.empty();
     }
-    
+
+    // Convenience overload for components without annotation discovery (deserializers, adapters)
+    private <T extends AbstractComponentBinding> Optional<T> searchComponentBinding(
+            final Type runtimeType,
+            final Function<ComponentBindings, T> supplier) {
+        return searchComponentBinding(runtimeType, supplier, null);
+    }
+
+    /**
+     * Discovers and caches a serializer defined by @JsonbTypeSerializer annotation on a runtime type.
+     *
+     * <p>This method performs <strong>runtime</strong> annotation discovery during serialization,
+     * which is distinct from the build-time annotation introspection performed by AnnotationIntrospector.
+     * It is invoked when serializing a property where the runtime type is more specific than the
+     * declared type (e.g., a property declared as {@code Object} containing an instance of a class
+     * annotated with @JsonbTypeSerializer).</p>
+     *
+     * <p>Note: Only @JsonbTypeSerializer is checked, not @JsonbTypeAdapter or @JsonbTypeDeserializer,
+     * because:</p>
+     * <ul>
+     *   <li>Serializers are unidirectional (serialization only), so runtime discovery is complete</li>
+     *   <li>Deserializers don't apply - we lack runtime type information during deserialization</li>
+     *   <li>Adapters are bidirectional - discovering them only at runtime during serialization
+     *       would be incomplete since they couldn't be discovered during deserialization</li>
+     * </ul>
+     *
+     * @param clazz The runtime class to check for @JsonbTypeSerializer annotation
+     * @return SerializerBinding if annotation is present and successfully introspected, empty otherwise
+     */
+    private Optional<SerializerBinding<?>> getAnnotationBasedSerializer(final Class<?> clazz) {
+        // Check if the class has a @JsonbTypeSerializer annotation
+        final JsonbTypeSerializer annotation = clazz.getAnnotation(JsonbTypeSerializer.class);
+        if (annotation == null) {
+            return Optional.empty();
+        }
+
+        // Thread-safe get-or-create using compute
+       final SerializerBinding<?> binding = userComponents.compute(clazz, (type, bindings) -> {
+            // If already cached, return as-is
+            if (bindings != null && bindings.getSerializer() != null) {
+                return bindings;
+            }
+
+            // Create new serializer binding
+            final Class<? extends JsonbSerializer> serializerClass = annotation.value();
+            final JsonbSerializer<?> serializer = jsonbContext.getComponentInstanceCreator().getOrCreateComponent(serializerClass);
+            final SerializerBinding<?> newBinding = new SerializerBinding<>(clazz, serializer);
+
+            // Create or update ComponentBindings
+            if (bindings == null) {
+                return new ComponentBindings(clazz, newBinding, null, null);
+            }
+            return new ComponentBindings(clazz, newBinding, bindings.getDeserializer(), bindings.getAdapterInfo());
+        }).getSerializer();
+
+        return Optional.ofNullable(binding);
+    }
+
     private <T> Optional<T> getMatchingBinding(Type runtimeType, ComponentBindings binding, Function<ComponentBindings, T> supplier) {
         final T component = supplier.apply(binding);
         if (component != null && matches(runtimeType, binding.getBindingType())) {
diff --git a/src/main/java/org/eclipse/yasson/internal/ReflectionUtils.java b/src/main/java/org/eclipse/yasson/internal/ReflectionUtils.java
index 47b5187..d83850b 100644
--- a/src/main/java/org/eclipse/yasson/internal/ReflectionUtils.java
+++ b/src/main/java/org/eclipse/yasson/internal/ReflectionUtils.java
@@ -252,7 +252,7 @@
                     }
                 }
                 
-                if (resolvedArgs[i] == null || resolvedArgs[i].equals(typeToResolve)) {
+                if (resolvedArgs[i] == null) {
                     if (typeToSearch instanceof Class) {
                         return Object.class;
                     }
@@ -262,6 +262,10 @@
                                                                         typeToSearch));
                 }
             }
+            // The expected type and the resolved type are the same, simply return the type
+            if (resolvedArgs[i].equals(typeToResolve)) {
+                return typeToResolve;
+            }
             if (resolvedArgs[i] instanceof ParameterizedType) {
                 resolvedArgs[i] = resolveTypeArguments((ParameterizedType) resolvedArgs[i], typeToSearch);
             } else if (unresolvedArg instanceof GenericArrayType) {
diff --git a/src/test/java/org/eclipse/yasson/defaultmapping/generics/GenericsTest.java b/src/test/java/org/eclipse/yasson/defaultmapping/generics/GenericsTest.java
index 42eb35a..907eb05 100644
--- a/src/test/java/org/eclipse/yasson/defaultmapping/generics/GenericsTest.java
+++ b/src/test/java/org/eclipse/yasson/defaultmapping/generics/GenericsTest.java
@@ -14,6 +14,7 @@
 
 import static org.eclipse.yasson.Jsonbs.defaultJsonb;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertIterableEquals;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
@@ -52,6 +53,7 @@
 import org.eclipse.yasson.defaultmapping.generics.model.GenericArrayClass;
 import org.eclipse.yasson.defaultmapping.generics.model.GenericTestClass;
 import org.eclipse.yasson.defaultmapping.generics.model.GenericWithUnboundedWildcardClass;
+import org.eclipse.yasson.defaultmapping.generics.model.ListContainer;
 import org.eclipse.yasson.defaultmapping.generics.model.LowerBoundTypeVariableWithCollectionAttributeClass;
 import org.eclipse.yasson.defaultmapping.generics.model.MultiLevelExtendedGenericTestClass;
 import org.eclipse.yasson.defaultmapping.generics.model.MultipleBoundsContainer;
@@ -516,14 +518,36 @@
     }
 
     @Test
-    public void genericUpperBoundContainer() {
-        final String expectedJson = "{\"tree\":{\"children\":[{\"children\":[],\"name\":\"child\"}],\"name\":\"parent\"}}";
+    public void genericUpperBoundContainer() throws Exception {
+        final String expectedJson = "{\"tree\":{\"children\":[{\"name\":\"child\"}],\"name\":\"parent\"}}";
         final TreeContainer<TreeElement> container = new TreeContainer<>();
         final TreeElement parent = new TreeElement("parent");
         parent.setChildren(List.of(new TreeElement("child")));
         container.setTree(parent);
 
-        assertEquals(expectedJson, defaultJsonb.toJson(container));
+        // Use a new instance of Jsonb to avoid any caching
+        try (var jsonb = JsonbBuilder.create()) {
+            assertEquals(expectedJson, jsonb.toJson(container));
+            TreeContainer<TreeElement> result = jsonb.fromJson(expectedJson, new TestTypeToken<TreeContainer<TreeElement>>() {}.getType());
+            assertIterableEquals(container.getTree().getChildren(), result.getTree().getChildren());
+        }
+
+    }
+
+    @Test
+    public void genericUpperBoundContainerWithListContainer() throws Exception {
+        final String expectedJson = "{\"list\":[{\"children\":[{\"name\":\"child\"}],\"name\":\"parent\"}]}";
+        final ListContainer<TreeElement> container = new ListContainer<>();
+        final TreeElement parent = new TreeElement("parent");
+        parent.setChildren(List.of(new TreeElement("child")));
+        container.setList(List.of(parent));
+
+        // Use a new instance of Jsonb to avoid any caching
+        try (var jsonb = JsonbBuilder.create()) {
+            assertEquals(expectedJson, jsonb.toJson(container));
+            ListContainer<TreeElement> result = jsonb.fromJson(expectedJson, new TestTypeToken<ListContainer<TreeElement>>() {}.getType());
+            assertIterableEquals(container.getList(), result.getList());
+        }
 
     }
     
diff --git a/src/test/java/org/eclipse/yasson/defaultmapping/generics/model/ListContainer.java b/src/test/java/org/eclipse/yasson/defaultmapping/generics/model/ListContainer.java
new file mode 100644
index 0000000..44b128f
--- /dev/null
+++ b/src/test/java/org/eclipse/yasson/defaultmapping/generics/model/ListContainer.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2025 IBM, Inc. and/or its affiliates.
+ *
+ * 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.defaultmapping.generics.model;
+
+import java.util.List;
+
+/**
+ *
+ * @author <a href="mailto:jperkins@ibm.com">James R. Perkins</a>
+ */
+public class ListContainer<T> {
+
+    private List<T> list;
+
+    public List<T> getList() {
+        return list;
+    }
+
+    public void setList(List<T> list) {
+        this.list = list;
+    }
+
+}
diff --git a/src/test/java/org/eclipse/yasson/defaultmapping/generics/model/TreeElement.java b/src/test/java/org/eclipse/yasson/defaultmapping/generics/model/TreeElement.java
index 8668761..a8dd238 100644
--- a/src/test/java/org/eclipse/yasson/defaultmapping/generics/model/TreeElement.java
+++ b/src/test/java/org/eclipse/yasson/defaultmapping/generics/model/TreeElement.java
@@ -12,16 +12,15 @@
 
 package org.eclipse.yasson.defaultmapping.generics.model;
 
-import java.util.ArrayList;
-import java.util.List;
-
 /**
  * @author <a href="mailto:jperkins@redhat.com">James R. Perkins</a>
  */
-public class TreeElement implements TreeTypeContainer<TreeElement> {
+public class TreeElement extends TreeTypeContainer<TreeElement> {
 
-    private final String name;
-    private List<TreeElement> children = new ArrayList<>();
+    private String name;

+

+    public TreeElement() {

+    }

 
     public TreeElement(final String name) {
         this.name = name;
@@ -30,12 +29,25 @@
     public String getName() {
         return name;
     }
-
-    public List<TreeElement> getChildren() {
-        return children;
-    }
-
-    public void setChildren(final List<TreeElement> children) {
-        this.children = children;
-    }
-}
+

+    public void setName(final String name) {

+        this.name = name;

+    }

+

+    @Override

+    public boolean equals(Object o) {

+        if (this == o) {

+            return true;

+        }

+        if (o == null || getClass() != o.getClass()) {

+            return false;

+        }

+        

+        if (!super.equals(o)) {

+            return false;

+        }

+        

+        TreeElement that = (TreeElement) o;

+        return name != null ? name.equals(that.name) : that.name == null;

+    }

+}

diff --git a/src/test/java/org/eclipse/yasson/defaultmapping/generics/model/TreeTypeContainer.java b/src/test/java/org/eclipse/yasson/defaultmapping/generics/model/TreeTypeContainer.java
index 120ae73..f649f7c 100644
--- a/src/test/java/org/eclipse/yasson/defaultmapping/generics/model/TreeTypeContainer.java
+++ b/src/test/java/org/eclipse/yasson/defaultmapping/generics/model/TreeTypeContainer.java
@@ -17,9 +17,31 @@
 /**
  * @author <a href="mailto:jperkins@redhat.com">James R. Perkins</a>
  */
-public interface TreeTypeContainer<T extends TreeTypeContainer<T>> {
+public class TreeTypeContainer<T extends TreeTypeContainer<T>> {
+    private List<T> children;
 
-    List<T> getChildren();
+    public List<T> getChildren() {
+        return children;
+    }
 
-    void setChildren(List<T> children);
-}
+    public void setChildren(final List<T> children) {
+        this.children = children;
+    }
+

+    @Override

+    public boolean equals(Object o) {

+        if (this == o) {

+            return true;

+        }

+        if (o == null || getClass() != o.getClass()) {

+            return false;

+        }

+        

+        TreeTypeContainer<?> that = (TreeTypeContainer<?>) o;

+        if (children == null) {

+            return that.children == null;

+        }

+        

+        return children.containsAll(that.children) && that.children.containsAll(children); 

+    }

+}

diff --git a/src/test/java/org/eclipse/yasson/documented/DocumentationExampleTest.java b/src/test/java/org/eclipse/yasson/documented/DocumentationExampleTest.java
index d49e4a0..5012e78 100644
--- a/src/test/java/org/eclipse/yasson/documented/DocumentationExampleTest.java
+++ b/src/test/java/org/eclipse/yasson/documented/DocumentationExampleTest.java
@@ -351,7 +351,7 @@
         @JsonbDateFormat("dd.MM.yyyy")
         public LocalDate birthDate;
 
-        @JsonbNumberFormat("#0.00")
+        @JsonbNumberFormat(value = "#0.00", locale="en_US")
         public BigDecimal salary;
     }
     
@@ -375,24 +375,25 @@
 
         public LocalDate birthDate;
 
+        @JsonbNumberFormat(value = "#0.00", locale="en_US") // TODO: remove if withNumberFormat is added to JsonbConfig builder
         public BigDecimal salary;
     }
     
-    @Test
+    @Test //TODO https://github.com/eclipse-ee4j/yasson/issues/722
     public void testDateNumberFormats2() {
         Person10 p = new Person10();
         p.name = "Jason Bourne";
         p.birthDate = LocalDate.of(1999, 8, 7);
         p.salary = new BigDecimal("123.45678");
         Jsonb jsonb = JsonbBuilder.create(new JsonbConfig()//
-                .withDateFormat("dd.MM.yyyy", null)); // TODO: why no withNumberFormat?
+                .withDateFormat("dd.MM.yyyy", null)); // TODO: add withNumberFormat if added to JsonbConfig builder
         String json = jsonb.toJson(p);
-        assertEquals("{\"birthDate\":\"07.08.1999\",\"name\":\"Jason Bourne\",\"salary\":123.45678}", json);
+        assertEquals("{\"birthDate\":\"07.08.1999\",\"name\":\"Jason Bourne\",\"salary\":\"123.46\"}", json);
         
-        Person9 after = jsonb.fromJson("{\"birthDate\":\"07.08.1999\",\"name\":\"Jason Bourne\",\"salary\":123.45678}", Person9.class);
+        Person9 after = jsonb.fromJson("{\"birthDate\":\"07.08.1999\",\"name\":\"Jason Bourne\",\"salary\":\"123.46\"}", Person9.class);
         assertEquals(p.name, after.name);
         assertEquals(p.birthDate, after.birthDate);
-        assertEquals(p.salary, after.salary);
+        assertEquals(new BigDecimal("123.46"), after.salary);
     }
     
     public static class Customer {
diff --git a/src/test/java/org/eclipse/yasson/jsonstructure/Issue673.java b/src/test/java/org/eclipse/yasson/jsonstructure/Issue673.java
index 7aeca3a..be2a555 100644
--- a/src/test/java/org/eclipse/yasson/jsonstructure/Issue673.java
+++ b/src/test/java/org/eclipse/yasson/jsonstructure/Issue673.java
@@ -27,12 +27,35 @@
 import java.util.Collections;
 import java.util.stream.Collectors;
 
+/**
+ * Test case for Issue #673: Custom deserializers with polymorphic types and JSON structure API.
+ *
+ * <p>This test validates the interaction between:
+ * <ul>
+ *   <li>Custom {@link JsonbDeserializer} implementations</li>
+ *   <li>Polymorphic type handling via {@link JsonbTypeInfo} and {@link JsonbSubtype}</li>
+ *   <li>JSON-P structure API ({@link JsonArray}, {@link JsonObject}, {@link JsonValue})</li>
+ * </ul>
+ *
+ * <p>The test ensures that custom deserializers can properly access and process JSON structure
+ * objects when deserializing complex types with polymorphic behavior.
+ *
+ * @see <a href="https://github.com/eclipse-ee4j/yasson/issues/673">Issue #673</a>
+ */
 public class Issue673 {
     
+    /**
+     * Marker interface for objects that can be referenced.
+     * Implemented by both {@link Reference} and {@link IRIReference}.
+     */
     public static interface Referenceable {
 
     }
 
+    /**
+     * A reference object with a description field.
+     * Deserialized from JSON objects containing a "description" property.
+     */
     public static class Reference implements Referenceable {
 
         private String description;
@@ -46,6 +69,10 @@
         }
     }
 
+    /**
+     * An IRI (Internationalized Resource Identifier) reference.
+     * Deserialized from JSON string values representing URIs.
+     */
     public static class IRIReference implements Referenceable {
 
         private String value;
@@ -65,14 +92,27 @@
         }
     }
 
+    /**
+     * Interface for location types with polymorphic deserialization support.
+     * Uses {@link JsonbTypeInfo} to determine concrete type based on "type" field in JSON.
+     */
     @JsonbTypeInfo(key = "type", value = {
-        @JsonbSubtype(alias = Location.TYPE, 
+        @JsonbSubtype(alias = Location.TYPE,
                       type = Location.class)
     })
     public static interface LocationInterface {
 
     }
 
+    /**
+     * Concrete location implementation with custom deserializers for complex fields.
+     *
+     * <p>Demonstrates:
+     * <ul>
+     *   <li>Array-to-string conversion via {@link TagsDeserializer}</li>
+     *   <li>Polymorphic reference deserialization via {@link ReferenceableDeserializer}</li>
+     * </ul>
+     */
     public static class Location implements LocationInterface {
 
         public final static String TYPE = "Location";
@@ -80,6 +120,12 @@
         private String tags;
         private Referenceable referenceable;
 
+        /**
+         * Gets the tags as a comma-separated string.
+         * Uses custom deserializer to convert JSON array to string.
+         *
+         * @return comma-separated tag string
+         */
         @JsonbTypeDeserializer(TagsDeserializer.class)
         public String getTags() {
             return tags;
@@ -89,6 +135,13 @@
             this.tags = tags;
         }
         
+        /**
+         * Gets the reference object.
+         * Uses custom deserializer to handle polymorphic deserialization
+         * from either string (IRI) or object (Reference) JSON values.
+         *
+         * @return the referenceable object
+         */
         @JsonbTypeDeserializer(ReferenceableDeserializer.class)
         public Referenceable getReference() {
             return referenceable;
@@ -99,11 +152,20 @@
         }
     }
 
+    /**
+     * Custom deserializer that converts a JSON array of strings into a comma-separated string.
+     *
+     * <p>Example JSON: {@code ["tag1", "tag2", "tag3"]} → {@code "tag1, tag2, tag3"}
+     *
+     * <p>This tests the ability to use {@link JsonParser#getArray()} to access
+     * JSON structure objects during deserialization.
+     */
     public static class TagsDeserializer implements JsonbDeserializer<String> {
         @Override
         public String deserialize(JsonParser jp, DeserializationContext dc, Type type) {
             final JsonValue v = jp.getArray();
-            if (v instanceof JsonArray arr) {
+            if (v instanceof JsonArray) {
+                JsonArray arr = (JsonArray) v;
                 return arr.stream()
                         .filter(JsonString.class::isInstance)
                         .map(JsonString.class::cast)
@@ -115,17 +177,35 @@
 
     }
     
+    /**
+     * Custom deserializer that handles polymorphic deserialization of {@link Referenceable} objects.
+     *
+     * <p>Supports two JSON representations:
+     * <ul>
+     *   <li>String value → {@link IRIReference} (e.g., {@code "http://example.com"})</li>
+     *   <li>Object value → {@link Reference} (e.g., {@code {"description": "..."}})</li>
+     * </ul>
+     *
+     * <p>This tests the ability to:
+     * <ul>
+     *   <li>Use {@link JsonParser#getValue()} to access JSON structure objects</li>
+     *   <li>Recursively deserialize nested objects using {@link DeserializationContext#deserialize}</li>
+     *   <li>Create new parsers from JSON-P structure objects</li>
+     * </ul>
+     */
     public static class ReferenceableDeserializer implements JsonbDeserializer<Referenceable> {
 
         @Override
         public Referenceable deserialize(JsonParser jp, DeserializationContext dc, Type type) {
             final JsonValue v = jp.getValue();
-            if (v instanceof JsonString str) {
+            if (v instanceof JsonString) {
+                JsonString str = (JsonString) v;
                 return new IRIReference(str.getString());
             }
-            if (v instanceof JsonObject obj) {
+            if (v instanceof JsonObject) {
+                JsonObject obj = (JsonObject) v;
                 return dc.deserialize(Reference.class,
-                        Json.createParserFactory(Collections.EMPTY_MAP)
+                        Json.createParserFactory(Collections.emptyMap())
                                 .createParser(obj));
             }
             return null;
diff --git a/src/test/java/org/eclipse/yasson/jsonstructure/JsonStructureToParserAdapterTest.java b/src/test/java/org/eclipse/yasson/jsonstructure/JsonStructureToParserAdapterTest.java
index 37d4f15..8cf1779 100644
--- a/src/test/java/org/eclipse/yasson/jsonstructure/JsonStructureToParserAdapterTest.java
+++ b/src/test/java/org/eclipse/yasson/jsonstructure/JsonStructureToParserAdapterTest.java
@@ -285,7 +285,7 @@
         
         Jsonb jsonb = JsonbBuilder.create();
         Issue673.LocationInterface result = jsonb.fromJson(json, Issue673.LocationInterface.class);
-	        
+            
         assertNotNull(result);
         assertTrue(result instanceof Issue673.Location);
         Issue673.Location location = (Issue673.Location) result;
diff --git a/src/test/java/org/eclipse/yasson/serializers/SerializersTest.java b/src/test/java/org/eclipse/yasson/serializers/SerializersTest.java
index db0efdf..228ed1f 100644
--- a/src/test/java/org/eclipse/yasson/serializers/SerializersTest.java
+++ b/src/test/java/org/eclipse/yasson/serializers/SerializersTest.java
@@ -35,6 +35,7 @@
 import jakarta.json.bind.JsonbBuilder;
 import jakarta.json.bind.JsonbConfig;
 import jakarta.json.bind.JsonbException;
+import jakarta.json.bind.annotation.JsonbTypeSerializer;
 import jakarta.json.bind.config.PropertyOrderStrategy;
 import jakarta.json.bind.serializer.DeserializationContext;
 import jakarta.json.bind.serializer.JsonbDeserializer;
@@ -78,6 +79,7 @@
 import static org.eclipse.yasson.Jsonbs.defaultJsonb;
 import static org.eclipse.yasson.Jsonbs.nullableJsonb;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
@@ -812,4 +814,92 @@
 
     }
 
+    /**
+     * Test that annotation-based serializers work when property is declared as Object
+     * but the runtime type has @JsonbTypeSerializer annotation.
+     * This is a regression test for issue #689.
+     */
+    @Test
+    public void testAnnotationBasedSerializerWithObjectTypedProperty() throws Exception {
+        try (Jsonb jsonb = JsonbBuilder.create()) {
+
+            final ObjectPropertyContainer container = new ObjectPropertyContainer();
+            final AnnotatedWithSerializerType objectInstance = new AnnotatedWithSerializerType();
+            objectInstance.value = "test";
+            container.annotatedAsObject = objectInstance;
+            container.annotatedConcrete = new AnnotatedWithSerializerType();
+            container.annotatedConcrete.value = "test2";
+
+            final String result = jsonb.toJson(container);
+
+            // Both properties should use the annotation-based serializer
+            final String expected = "{\"annotatedAsObject\":{\"valueField\":\"replaced value\"},\"annotatedConcrete\":{\"valueField\":\"replaced value\"}}";
+            assertEquals(expected, result);
+
+            // Deserialization: annotatedConcrete uses annotation-based deserializer
+            // annotatedAsObject is declared as Object so JSON-B creates a HashMap (expected behavior)
+            final ObjectPropertyContainer deserialized = jsonb.fromJson(expected, ObjectPropertyContainer.class);
+            //  In the JSON, the type looks like an object and therefore is a map
+            assertInstanceOf(Map.class, deserialized.annotatedAsObject, "Object property deserializes to Map");
+            final Map<?, ?> map =  (Map<?, ?>) deserialized.annotatedAsObject;
+            assertTrue(map.containsKey("valueField"));
+            assertEquals("replaced value", map.get("valueField"));
+            assertEquals("replaced value", deserialized.annotatedConcrete.value);
+        }
+    }
+
+    /**
+     * Test that field-level and method-level @JsonbTypeSerializer annotations work on Object-typed properties.
+     * This tests existing AnnotationIntrospector code (not runtime discovery).
+     */
+    @Test
+    public void testFieldAndMethodLevelSerializerOnObjectType() throws Exception {
+        try (Jsonb jsonb = JsonbBuilder.create()) {
+            final ObjectWithAnnotatedFields container = new ObjectWithAnnotatedFields();
+            container.fieldAnnotated = "test field";
+            container.setMethodAnnotated("test method");
+
+            final String result = jsonb.toJson(container);
+
+            // Both should use their respective serializers
+            final String expected = "{\"fieldAnnotated\":\"FIELD:test field\",\"methodAnnotated\":\"METHOD:test method\"}";
+            assertEquals(expected, result);
+        }
+    }
+
+    public static class ObjectWithAnnotatedFields {
+        @JsonbTypeSerializer(ObjectFieldSerializer.class)
+        public Object fieldAnnotated;
+
+        private Object methodAnnotated;
+
+        @JsonbTypeSerializer(ObjectMethodSerializer.class)
+        public Object getMethodAnnotated() {
+            return methodAnnotated;
+        }
+
+        public void setMethodAnnotated(Object methodAnnotated) {
+            this.methodAnnotated = methodAnnotated;
+        }
+    }
+
+    public static class ObjectFieldSerializer implements JsonbSerializer<Object> {
+        @Override
+        public void serialize(Object obj, JsonGenerator generator, SerializationContext ctx) {
+            generator.write("FIELD:" + obj.toString());
+        }
+    }
+
+    public static class ObjectMethodSerializer implements JsonbSerializer<Object> {
+        @Override
+        public void serialize(Object obj, JsonGenerator generator, SerializationContext ctx) {
+            generator.write("METHOD:" + obj.toString());
+        }
+    }
+
+    public static class ObjectPropertyContainer {
+        public Object annotatedAsObject;  // Declared as Object - this was the bug scenario
+        public AnnotatedWithSerializerType annotatedConcrete;  // Declared concretely - should always work
+    }
+
 }
diff --git a/yasson-jmh/pom.xml b/yasson-jmh/pom.xml
index dcdd303..61d3046 100644
--- a/yasson-jmh/pom.xml
+++ b/yasson-jmh/pom.xml
@@ -5,6 +5,12 @@
 
     <modelVersion>4.0.0</modelVersion>
 
+    <parent>
+        <groupId>org.eclipse.ee4j</groupId>
+        <artifactId>project</artifactId>
+        <version>2.0.4</version>
+    </parent>
+
     <groupId>org.eclipse.yasson</groupId>
     <artifactId>yasson-jmh</artifactId>
     <version>1.0-SNAPSHOT</version>
@@ -14,7 +20,7 @@
 
     <properties>
         <jmh.version>1.21</jmh.version>
-        <yasson.version>2.0.2-SNAPSHOT</yasson.version>
+        <yasson.version>3.0.5-SNAPSHOT</yasson.version>
     </properties>
 
 
diff --git a/yasson-tck/pom.xml b/yasson-tck/pom.xml
index c3d36c7..bab505d 100644
--- a/yasson-tck/pom.xml
+++ b/yasson-tck/pom.xml
@@ -5,17 +5,38 @@
 
     <modelVersion>4.0.0</modelVersion>
 
+    <parent>
+        <groupId>org.eclipse.ee4j</groupId>
+        <artifactId>project</artifactId>
+        <version>2.0.4</version>
+        <relativePath/>
+    </parent>
+
     <groupId>org.eclipse</groupId>
     <artifactId>yasson-tck</artifactId>
     <version>1.0.0-SNAPSHOT</version>
 
     <properties>
-        <jsonb.tck.version>3.0.0</jsonb.tck.version>
-        <yasson.version>3.0.5-SNAPSHOT</yasson.version>
-        <jakarta.json.bind.version>3.0.1</jakarta.json.bind.version>
-        <jakarta.json.version>2.1.3</jakarta.json.version>
         <maven.compiler.source>11</maven.compiler.source>
         <maven.compiler.target>11</maven.compiler.target>
+
+        <!-- API Versions -->
+        <jakarta.json.version>2.1.3</jakarta.json.version> <!-- EE10 -->
+        <jakarta.json.bind.version>3.0.1</jakarta.json.bind.version> <!-- EE10 -->
+
+        <!-- IMPL Versions -->
+        <jsonb.tck.version>3.0.0</jsonb.tck.version> <!-- EE10 -->
+        <yasson.version>3.0.5-SNAPSHOT</yasson.version> <!-- EE10 --> 
+
+        <!-- Test Versions -->
+        <junit-jupiter.version>5.14.4</junit-jupiter.version> <!-- TODO update to Junit 6 when running on Java 17+ -->  
+        <weld-se-core.version>6.0.4.Final</weld-se-core.version>
+        <arquillian-junit5-container.version>1.8.0.Final</arquillian-junit5-container.version>
+
+        <!-- Plugin Versions -->
+        <maven-dependency-plugin.version>3.6.1</maven-dependency-plugin.version>
+        <maven-surefire-plugin.version>3.2.5</maven-surefire-plugin.version>
+        <maven-surefire-report-plugin.version>3.2.5</maven-surefire-report-plugin.version>
     </properties>
 
     <dependencies>
@@ -46,13 +67,13 @@
         <dependency>
             <groupId>org.jboss.weld.se</groupId>
             <artifactId>weld-se-core</artifactId>
-            <version>6.0.0.Beta1</version>
+            <version>${weld-se-core.version}</version>
             <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>org.jboss.arquillian.junit5</groupId>
             <artifactId>arquillian-junit5-container</artifactId>
-            <version>1.8.0.Final</version>
+            <version>${arquillian-junit5-container.version}</version>
         </dependency>
     </dependencies>
 
@@ -61,7 +82,7 @@
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-dependency-plugin</artifactId>
-                <version>3.6.1</version>
+                <version>${maven-dependency-plugin.version}</version>
                 <executions>
                     <execution>
                         <id>copy</id>
@@ -96,7 +117,7 @@
             </plugin>
             <plugin>
                 <artifactId>maven-surefire-plugin</artifactId>
-                <version>3.2.5</version>
+                <version>${maven-surefire-plugin.version}</version>
                 <configuration>
                     <trimStackTrace>false</trimStackTrace>
                     <failIfNoTests>true</failIfNoTests>
@@ -111,7 +132,7 @@
             </plugin>
             <plugin>
                 <artifactId>maven-surefire-report-plugin</artifactId>
-                <version>3.2.5</version>
+                <version>${maven-surefire-report-plugin.version}</version>
                 <executions>
                     <execution>
                         <id>post-unit-test</id>