Merge pull request #694 from redmitry/master

issue-673 fix
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..7038eba
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,8 @@
+version: 2
+updates:
+  - package-ecosystem: github-actions
+    directory: /
+    schedule:
+      interval: daily
+
+# TODO - add maven dependabot if community agrees it's useful
\ No newline at end of file
diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml
index 3b21094..56e8e7c 100644
--- a/.github/workflows/maven.yml
+++ b/.github/workflows/maven.yml
@@ -1,5 +1,5 @@
 #
-# Copyright (c) 2021, 2024 Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2021, 2026 Oracle 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
@@ -12,7 +12,15 @@
 
 name: Yasson
 
-on: [push, pull_request]
+on:
+  push:
+    branches:
+      - 'main'
+      - '*-RELEASE'
+  pull_request:
+    branches:
+      - 'main'
+      - '*-RELEASE'
 
 jobs:
   build:
@@ -25,11 +33,11 @@
 
     steps:
       - name: Checkout for build
-        uses: actions/checkout@v4
+        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
         with:
           fetch-depth: 0
       - name: Set up compile JDK
-        uses: actions/setup-java@v4
+        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
@@ -37,10 +45,10 @@
       - name: Copyright
         run: bash etc/copyright.sh
       - name: Checkstyle
-        run: mvn -B checkstyle:checkstyle -Pstaging
+        run: mvn -B checkstyle:checkstyle
       - name: Yasson install
-        run: mvn -U -C clean install -Pstaging -DskipTests
+        run: mvn -U -C clean install -DskipTests
       - name: Yasson tests
-        run: mvn -U -B -C -Dmaven.javadoc.skip=true -Pstaging verify
+        run: mvn -U -B -C -Dmaven.javadoc.skip=true verify
       - name: JSONB-API TCK
-        run: cd yasson-tck && mvn -U -B test -DargLine="-Djava.locale.providers=COMPAT" -Pstaging
+        run: cd yasson-tck && mvn -U -B test -DargLine="-Djava.locale.providers=COMPAT"
diff --git a/.gitignore b/.gitignore
index 3160630..231039b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,6 @@
 .idea/
 .settings/
 /.DS_Store
+bin/
+.envrc
+.vscode/
diff --git a/README.md b/README.md
index 374a9db..f04550b 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,12 @@
 # Eclipse Yasson
 
 [![Maven Central](https://img.shields.io/maven-central/v/org.eclipse/yasson.svg?label=Maven%20Central)](https://mvnrepository.com/artifact/org.eclipse/yasson)
+<!-- TODO reenable once snapshots can be browsed via https://central.sonatype.com/service/rest/repository/browse/maven-snapshots
 [![Jakarta Staging (Snapshots)](https://img.shields.io/nexus/s/https/jakarta.oss.sonatype.org/org.eclipse/yasson.svg)](https://jakarta.oss.sonatype.org/content/repositories/staging/org/eclipse/yasson/)
+-->
 [![Gitter](https://badges.gitter.im/eclipse/yasson.svg)](https://gitter.im/eclipse/yasson)
 [![Javadocs](https://www.javadoc.io/badge/org.eclipse/yasson.svg)](https://www.javadoc.io/doc/org.eclipse/yasson)
-[![Build Status](https://github.com/eclipse-ee4j/yasson/actions/workflows/maven.yml/badge.svg?branch=master)](https://github.com/eclipse-ee4j/yasson/actions/workflows/maven.yml?branch=master)
+[![Build Status](https://github.com/eclipse-ee4j/yasson/actions/workflows/maven.yml/badge.svg?branch=main)](https://github.com/eclipse-ee4j/yasson/actions/workflows/maven.yml?branch=main)
 [![License](https://img.shields.io/badge/License-EPL%202.0-green.svg)](https://opensource.org/licenses/EPL-2.0)
 
 Yasson is a Java framework which provides a standard binding layer between Java classes and JSON documents. This is similar to what JAXB is doing in the XML world. Yasson is an official reference implementation of JSON Binding ([JSR-367](https://jcp.org/en/jsr/detail?id=367)).
diff --git a/pom.xml b/pom.xml
index f3f048a..0a0c8a3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!--
 
-    Copyright (c) 2016, 2024 Oracle and/or its affiliates. All rights reserved.
+    Copyright (c) 2016, 2026 Oracle 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
@@ -15,17 +15,17 @@
 
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
 
     <parent>
         <groupId>org.eclipse.ee4j</groupId>
         <artifactId>project</artifactId>
-        <version>1.0.9</version>
+        <version>2.0.2</version>
     </parent>
 
-    <modelVersion>4.0.0</modelVersion>
     <groupId>org.eclipse</groupId>
     <artifactId>yasson</artifactId>
-    <version>3.0.4-SNAPSHOT</version>
+    <version>3.0.5-SNAPSHOT</version>
     <packaging>jar</packaging>
     <name>Yasson</name>
 
@@ -36,6 +36,7 @@
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         <maven.compiler.release>11</maven.compiler.release>
         <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>
diff --git a/src/main/java/org/eclipse/yasson/internal/ReflectionUtils.java b/src/main/java/org/eclipse/yasson/internal/ReflectionUtils.java
index 1c7cca1..47b5187 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) {
+                if (resolvedArgs[i] == null || resolvedArgs[i].equals(typeToResolve)) {
                     if (typeToSearch instanceof Class) {
                         return Object.class;
                     }
diff --git a/src/main/java/org/eclipse/yasson/internal/deserializer/DeserializationModelCreator.java b/src/main/java/org/eclipse/yasson/internal/deserializer/DeserializationModelCreator.java
index 1f1bdab..6a6daca 100644
--- a/src/main/java/org/eclipse/yasson/internal/deserializer/DeserializationModelCreator.java
+++ b/src/main/java/org/eclipse/yasson/internal/deserializer/DeserializationModelCreator.java
@@ -180,15 +180,15 @@
             return typeDeserializer;
         }
         if (Collection.class.isAssignableFrom(rawType)) {
-            return createCollectionDeserializer(cachedItem, rawType, chain, propertyCustomization);
+            return createCollectionDeserializer(cachedItem, rawType, chain);
         } else if (Map.class.isAssignableFrom(rawType)) {
-            return createMapDeserializer(cachedItem, rawType, chain, propertyCustomization);
+            return createMapDeserializer(cachedItem, rawType, chain);
         } else if (rawType.isArray()) {
-            return createArrayDeserializer(cachedItem, rawType, chain, propertyCustomization);
+            return createArrayDeserializer(cachedItem, rawType, chain);
         } else if (type instanceof GenericArrayType) {
-            return createGenericArray(cachedItem, rawType, chain, propertyCustomization);
+            return createGenericArray(cachedItem, rawType, chain);
         } else if (Optional.class.isAssignableFrom(rawType)) {
-            return createOptionalDeserializer(chain, type, propertyCustomization, cachedItem);
+            return createOptionalDeserializer(chain, type, cachedItem);
         } else {
             return createObjectDeserializer(chain, type, propertyCustomization, classModel, rawType, cachedItem);
         }
@@ -262,8 +262,7 @@
 
     private ModelDeserializer<JsonParser> createCollectionDeserializer(CachedItem cachedItem,
                                                                        Class<?> rawType,
-                                                                       LinkedList<Type> chain,
-                                                                       Customization propertyCustomization) {
+                                                                       LinkedList<Type> chain) {
         Type type = cachedItem.type;
         Type colType = type instanceof ParameterizedType
                 ? ((ParameterizedType) type).getActualTypeArguments()[0]
@@ -284,8 +283,7 @@
 
     private ModelDeserializer<JsonParser> createMapDeserializer(CachedItem cachedItem,
                                                                 Class<?> rawType,
-                                                                LinkedList<Type> chain,
-                                                                Customization propertyCustomization) {
+                                                                LinkedList<Type> chain) {
         Type type = cachedItem.type;
         Type keyType = type instanceof ParameterizedType
                 ? ((ParameterizedType) type).getActualTypeArguments()[0]
@@ -298,9 +296,10 @@
                                                                    ClassCustomization.empty(),
                                                                    JustReturn.instance(),
                                                                    MAP_KEY_EVENTS);
+        ClassModel valueClassModel = jsonbContext.getMappingContext().getOrCreateClassModel(ReflectionUtils.resolveRawType(chain, valueType));
         ModelDeserializer<JsonParser> valueProcessor = typeProcessor(chain,
                                                                      valueType,
-                                                                     propertyCustomization,
+                                                                     valueClassModel.getClassCustomization(),
                                                                      JustReturn.instance());
 
         MapDeserializer mapDeserializer = new MapDeserializer(keyProcessor, valueProcessor);
@@ -315,14 +314,15 @@
 
     private ModelDeserializer<JsonParser> createArrayDeserializer(CachedItem cachedItem,
                                                                   Class<?> rawType,
-                                                                  LinkedList<Type> chain,
-                                                                  Customization propertyCustomization) {
+                                                                  LinkedList<Type> chain) {
         JsonbConfigProperties configProperties = jsonbContext.getConfigProperties();
         if (rawType.equals(byte[].class) && !configProperties.getBinaryDataStrategy().equals(BinaryDataStrategy.BYTE)) {
             String strategy = configProperties.getBinaryDataStrategy();
+            // Special case for byte[] with base64 encoding - use String's class customization
+            ClassModel stringModel = jsonbContext.getMappingContext().getOrCreateClassModel(String.class);
             ModelDeserializer<JsonParser> typeProcessor = typeProcessor(chain,
                                                                         String.class,
-                                                                        propertyCustomization,
+                                                                        stringModel.getClassCustomization(),
                                                                         JustReturn.instance());
             ModelDeserializer<JsonParser> base64Deserializer = ArrayInstanceCreator.createBase64Deserializer(strategy,
                                                                                                              typeProcessor);
@@ -331,22 +331,23 @@
             return nullChecker;
         }
         Class<?> arrayType = rawType.getComponentType();
+        ClassModel classModel = jsonbContext.getMappingContext().getOrCreateClassModel(arrayType);
         ModelDeserializer<JsonParser> typeProcessor = typeProcessor(chain,
                                                                     arrayType,
-                                                                    propertyCustomization,
+                                                                    classModel.getClassCustomization(),
                                                                     JustReturn.instance());
         return createArrayCommonDeserializer(cachedItem, rawType, arrayType, typeProcessor);
     }
 
     private ModelDeserializer<JsonParser> createGenericArray(CachedItem cachedItem,
                                                              Class<?> rawType,
-                                                             LinkedList<Type> chain,
-                                                             Customization propertyCustomization) {
+                                                             LinkedList<Type> chain) {
         GenericArrayType type = (GenericArrayType) cachedItem.type;
         Class<?> component = ReflectionUtils.getRawType(type.getGenericComponentType());
+        ClassModel classModel = jsonbContext.getMappingContext().getOrCreateClassModel(component);
         ModelDeserializer<JsonParser> typeProcessor = typeProcessor(chain,
                                                                     type.getGenericComponentType(),
-                                                                    propertyCustomization,
+                                                                    classModel.getClassCustomization(),
                                                                     JustReturn.instance());
         return createArrayCommonDeserializer(cachedItem, rawType, component, typeProcessor);
     }
@@ -365,12 +366,13 @@
 
     private OptionalDeserializer createOptionalDeserializer(LinkedList<Type> chain,
                                                             Type type,
-                                                            Customization propertyCustomization,
                                                             CachedItem cachedItem) {
         Type colType = type instanceof ParameterizedType
                 ? ((ParameterizedType) type).getActualTypeArguments()[0]
                 : Object.class;
-        ModelDeserializer<JsonParser> typeProcessor = typeProcessor(chain, colType, propertyCustomization, JustReturn.instance());
+        colType = ReflectionUtils.resolveType(chain, colType);
+        ClassModel classModel = jsonbContext.getMappingContext().getOrCreateClassModel(ReflectionUtils.getRawType(colType));
+        ModelDeserializer<JsonParser> typeProcessor = typeProcessor(chain, colType, classModel.getClassCustomization(), JustReturn.instance());
         OptionalDeserializer optionalDeserializer = new OptionalDeserializer(typeProcessor, JustReturn.instance());
         models.put(cachedItem, optionalDeserializer);
         return optionalDeserializer;
diff --git a/src/main/java/org/eclipse/yasson/internal/deserializer/types/DateDeserializer.java b/src/main/java/org/eclipse/yasson/internal/deserializer/types/DateDeserializer.java
index 723cdbe..4e3704a 100644
--- a/src/main/java/org/eclipse/yasson/internal/deserializer/types/DateDeserializer.java
+++ b/src/main/java/org/eclipse/yasson/internal/deserializer/types/DateDeserializer.java
@@ -13,13 +13,30 @@
 package org.eclipse.yasson.internal.deserializer.types;
 
 import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.YearMonth;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
 import java.time.ZonedDateTime;
 import java.time.format.DateTimeFormatter;
+import java.time.temporal.TemporalAccessor;
 import java.util.Date;
 import java.util.Locale;
 
 /**
  * Deserializer of the {@link Date} type.
+ *
+ * <p>
+ * For date-only patterns (e.g., "yyyy-MM-dd"), this deserializer uses {@link DateTimeFormatter#parseBest} to detect the
+ * appropriate temporal type [ZonedDateTime, LocalDateTime, LocalDate, or YearMonth]
+ * and creates the Date object at midnight in the specified
+ * timezone. When no timezone is specified in the pattern, UTC is used as required by Jakarta JSON Binding specification
+ * section 3.5.
+ * </p>
+ * <p>
+ * critical, use {@link java.time.LocalDate} (recommended) or {@link java.sql.Date}.
+ * </p>
  */
 class DateDeserializer extends AbstractDateDeserializer<Date> {
 
@@ -45,13 +62,31 @@
     }
 
     private static Date parseWithOrWithoutZone(String jsonValue, DateTimeFormatter formatter) {
-        ZonedDateTime parsed;
-        if (formatter.getZone() == null) {
-            parsed = ZonedDateTime.parse(jsonValue, formatter.withZone(UTC));
+        final TemporalAccessor best = formatter.parseBest(jsonValue,
+                ZonedDateTime::from,
+                LocalDateTime::from,
+                LocalDate::from,
+                YearMonth::from);
+
+        // If no zone provided in string, use the formatter's zone or UTC per the Jakarta JSON Binding specification
+        // section 3.5
+        final ZoneId zone = formatter.getZone() != null ? formatter.getZone() : ZoneOffset.UTC;
+
+        // Determine the type of the best option
+        final Instant instant;
+        if (best instanceof ZonedDateTime) {
+            instant = ((ZonedDateTime) best).toInstant();
+        } else if (best instanceof LocalDateTime) {
+            instant = ((LocalDateTime) best).atZone(zone).toInstant();
+        } else if (best instanceof LocalDate) {
+            instant = LocalDate.from(best).atStartOfDay(zone).toInstant();
+        } else if (best instanceof YearMonth) {
+            instant = ((YearMonth) best).atDay(1).atStartOfDay(zone).toInstant();
         } else {
-            parsed = ZonedDateTime.parse(jsonValue, formatter);
+            // Fallback
+            instant = Instant.from(best);
         }
-        return Date.from(parsed.toInstant());
+        return Date.from(instant);
     }
 
 }
diff --git a/src/main/java/org/eclipse/yasson/internal/serializer/MapSerializer.java b/src/main/java/org/eclipse/yasson/internal/serializer/MapSerializer.java
index 9b283fb..702d3f1 100644
--- a/src/main/java/org/eclipse/yasson/internal/serializer/MapSerializer.java
+++ b/src/main/java/org/eclipse/yasson/internal/serializer/MapSerializer.java
@@ -16,6 +16,7 @@
 
 import jakarta.json.stream.JsonGenerator;
 
+import org.eclipse.yasson.internal.JsonbContext;
 import org.eclipse.yasson.internal.SerializationContextImpl;
 import org.eclipse.yasson.internal.serializer.types.TypeSerializers;
 
@@ -40,9 +41,15 @@
         return valueSerializer;
     }
 
-    static MapSerializer create(Class<?> keyClass, ModelSerializer keySerializer, ModelSerializer valueSerializer) {
+    static MapSerializer create(Class<?> keyClass, ModelSerializer keySerializer, ModelSerializer valueSerializer, JsonbContext jsonbContext) {
         if (TypeSerializers.isSupportedMapKey(keyClass)) {
-            return new StringKeyMapSerializer(keySerializer, valueSerializer);
+            //Issue #663: A custom JsonbSerializer is available for an already supported Map key. Serialization must
+            //not use normal key:value map. No further checking needed. Wrapping object needs to be used.
+            if (TypeSerializers.hasCustomJsonbSerializer(keyClass, jsonbContext)) {
+                return new ObjectKeyMapSerializer(keySerializer, valueSerializer);
+            } else {
+                return new StringKeyMapSerializer(keySerializer, valueSerializer);
+            }
         } else if (Object.class.equals(keyClass)) {
             return new DynamicMapSerializer(keySerializer, valueSerializer);
         }
@@ -79,7 +86,16 @@
                     }
                     Class<?> keyClass = key.getClass();
                     if (TypeSerializers.isSupportedMapKey(keyClass)) {
-                        continue;
+
+                        //Issue #663: A custom JsonbSerializer is available for an already supported Map key.
+                        //Serialization must not use normal key:value map. No further checking needed. Wrapping object
+                        //needs to be used.
+                        if (TypeSerializers.hasCustomJsonbSerializer(keyClass, context.getJsonbContext())) {
+                            suitable = false;
+                            break;
+                        } else {
+                            continue;
+                        }
                     }
                     //No other checks needed. Map is not suitable for normal key:value map. Wrapping object needs to be used.
                     suitable = false;
diff --git a/src/main/java/org/eclipse/yasson/internal/serializer/SerializationModelCreator.java b/src/main/java/org/eclipse/yasson/internal/serializer/SerializationModelCreator.java
index 522519b..9aa29a7 100644
--- a/src/main/java/org/eclipse/yasson/internal/serializer/SerializationModelCreator.java
+++ b/src/main/java/org/eclipse/yasson/internal/serializer/SerializationModelCreator.java
@@ -302,8 +302,10 @@
         Type resolvedKey = ReflectionUtils.resolveType(chain, keyType);
         Class<?> rawClass = ReflectionUtils.getRawType(resolvedKey);
         ModelSerializer keySerializer = memberSerializer(chain, keyType, ClassCustomization.empty(), true);
-        ModelSerializer valueSerializer = memberSerializer(chain, valueType, propertyCustomization, false);
-        MapSerializer mapSerializer = MapSerializer.create(rawClass, keySerializer, valueSerializer);
+        Type resolvedValue = ReflectionUtils.resolveType(chain, valueType);
+        ClassModel valueClassModel = jsonbContext.getMappingContext().getOrCreateClassModel(ReflectionUtils.getRawType(resolvedValue));
+        ModelSerializer valueSerializer = memberSerializer(chain, valueType, valueClassModel.getClassCustomization(), false);
+        MapSerializer mapSerializer = MapSerializer.create(rawClass, keySerializer, valueSerializer, jsonbContext);
         KeyWriter keyWriter = new KeyWriter(mapSerializer);
         NullVisibilitySwitcher nullVisibilitySwitcher = new NullVisibilitySwitcher(true, keyWriter);
         return new NullSerializer(nullVisibilitySwitcher, propertyCustomization, jsonbContext);
@@ -313,7 +315,8 @@
                                                   Class<?> raw,
                                                   Customization propertyCustomization) {
         Class<?> arrayComponent = raw.getComponentType();
-        ModelSerializer modelSerializer = memberSerializer(chain, arrayComponent, propertyCustomization, false);
+        ClassModel classModel = jsonbContext.getMappingContext().getOrCreateClassModel(arrayComponent);
+        ModelSerializer modelSerializer = memberSerializer(chain, arrayComponent, classModel.getClassCustomization(), false);
         ModelSerializer arraySerializer = ArraySerializer.create(raw, jsonbContext, modelSerializer);
         KeyWriter keyWriter = new KeyWriter(arraySerializer);
         NullVisibilitySwitcher nullVisibilitySwitcher = new NullVisibilitySwitcher(true, keyWriter);
@@ -325,7 +328,8 @@
                                                          Customization propertyCustomization) {
         Class<?> raw = ReflectionUtils.getRawType(type);
         Class<?> component = ReflectionUtils.getRawType(((GenericArrayType) type).getGenericComponentType());
-        ModelSerializer modelSerializer = memberSerializer(chain, component, propertyCustomization, false);
+        ClassModel classModel = jsonbContext.getMappingContext().getOrCreateClassModel(component);
+        ModelSerializer modelSerializer = memberSerializer(chain, component, classModel.getClassCustomization(), false);
         ModelSerializer arraySerializer = ArraySerializer.create(raw, jsonbContext, modelSerializer);
         KeyWriter keyWriter = new KeyWriter(arraySerializer);
         NullVisibilitySwitcher nullVisibilitySwitcher = new NullVisibilitySwitcher(true, keyWriter);
@@ -339,7 +343,9 @@
         Type optType = type instanceof ParameterizedType
                 ? ((ParameterizedType) type).getActualTypeArguments()[0]
                 : Object.class;
-        ModelSerializer modelSerializer = memberSerializer(chain, optType, propertyCustomization, isKey);
+        Type resolvedOptType = ReflectionUtils.resolveType(chain, optType);
+        ClassModel classModel = jsonbContext.getMappingContext().getOrCreateClassModel(ReflectionUtils.getRawType(resolvedOptType));
+        ModelSerializer modelSerializer = memberSerializer(chain, optType, classModel.getClassCustomization(), isKey);
         return new OptionalSerializer(modelSerializer);
     }
 
diff --git a/src/main/java/org/eclipse/yasson/internal/serializer/types/TypeSerializers.java b/src/main/java/org/eclipse/yasson/internal/serializer/types/TypeSerializers.java
index c25fc89..3d52abe 100644
--- a/src/main/java/org/eclipse/yasson/internal/serializer/types/TypeSerializers.java
+++ b/src/main/java/org/eclipse/yasson/internal/serializer/types/TypeSerializers.java
@@ -53,6 +53,7 @@
 import jakarta.json.JsonString;
 import jakarta.json.JsonValue;
 import jakarta.json.bind.JsonbException;
+import jakarta.json.bind.serializer.JsonbSerializer;
 
 import org.eclipse.yasson.internal.JsonbContext;
 import org.eclipse.yasson.internal.model.customization.Customization;
@@ -154,6 +155,17 @@
     }
 
     /**
+     * Whether type has a custom {@link JsonbSerializer} implementation.
+     *
+     * @param clazz        type to serialize
+     * @param jsonbContext jsonb context
+     * @return whether a custom JsonSerializer for the type is available
+     */
+    public static boolean hasCustomJsonbSerializer(Class<?> clazz, JsonbContext jsonbContext) {
+        return jsonbContext.getComponentMatcher().getSerializerBinding(clazz, null).isPresent();
+    }
+
+    /**
      * Create new type serializer.
      *
      * @param clazz         type of the serializer
diff --git a/src/test/java/org/eclipse/yasson/defaultmapping/dates/DatesTest.java b/src/test/java/org/eclipse/yasson/defaultmapping/dates/DatesTest.java
index 287afca..79ad21e 100644
--- a/src/test/java/org/eclipse/yasson/defaultmapping/dates/DatesTest.java
+++ b/src/test/java/org/eclipse/yasson/defaultmapping/dates/DatesTest.java
@@ -102,6 +102,10 @@
     public static class SqlDateFormatted {
         @JsonbDateFormat(value = "yyyy-MM-dd")
         public java.sql.Date sqlDate;
+        @JsonbDateFormat(value = "yyyy-MM-dd")
+        public java.util.Date utilDate;
+
+
     }
 
     @Test
@@ -126,7 +130,101 @@
         assertEquals("2018-01-31", result.sqlDate.toString());
         assertEquals("2018-01-31", result.utilDate.toString());
     }
-    
+
+    @Test
+    public void testMarshallSqlDateFormatted() {
+        final String date = "2026-02-25";
+        final String expectedJson = String.format("{\"sqlDate\":\"%1$s\",\"utilDate\":\"%1$s\"}", date);
+
+        final SqlDateFormatted sqlDateFormatted = new SqlDateFormatted();
+        sqlDateFormatted.sqlDate = java.sql.Date.valueOf(date);
+        // We use a java.sql.Date here as we want to test as if this was a Jakarta Persistence temporal date
+        sqlDateFormatted.utilDate = java.sql.Date.valueOf(date);
+        String jsonString = bindingJsonb.toJson(sqlDateFormatted);
+        assertEquals(expectedJson, jsonString);
+
+        // Unmarshal the object
+        final SqlDateFormatted result = bindingJsonb.fromJson(jsonString, SqlDateFormatted.class);
+        assertEquals(sqlDateFormatted.sqlDate, result.sqlDate);
+        // The Date objects will not be equal unless user.timezone is set to UTC. The sqlDateFormatted.utilDate is
+        // created at midnight in the current timezone (via valueOf()), while result.utilDate is created at midnight UTC
+        // per the JSON-B specification. To verify both represent the same calendar date, we convert each to LocalDate
+        // using its respective timezone: the original uses systemDefault(), the deserialized uses UTC.
+        assertEquals(Instant.ofEpochMilli(sqlDateFormatted.utilDate.getTime()).atZone(ZoneId.systemDefault()).toLocalDate(),
+                result.utilDate.toInstant().atZone(ZoneOffset.UTC).toLocalDate());
+    }
+
+    @Test
+    public void testUnmarshallSqlDateFormatted() {
+        final String date = "2026-02-25";
+        final String expectedString = String.format("{\"sqlDate\":\"%1$s\",\"utilDate\":\"%1$s\"}", date);
+
+        final SqlDateFormatted sqlDateFormatted = bindingJsonb.fromJson(expectedString, SqlDateFormatted.class);
+        assertEquals(date, sqlDateFormatted.sqlDate.toString());
+        // Convert java.util.Date to LocalDate for comparison
+        final LocalDate resultDate = sqlDateFormatted.utilDate.toInstant()
+                .atZone(ZoneOffset.UTC)
+                .toLocalDate();
+        assertEquals(LocalDate.parse(date),  resultDate);
+
+        // Unmarshal the object
+        final String result = bindingJsonb.toJson(sqlDateFormatted);
+        assertEquals(expectedString, result);
+    }
+
+    public static class YearMonthFormatted {
+        @JsonbDateFormat(value = "yyyy-MM")
+        public java.util.Date date;
+    }
+
+    @Test
+    public void testMarshallYearMonthFormat() {
+        final YearMonthFormatted yearMonthFormatted = new YearMonthFormatted();
+        yearMonthFormatted.date = java.sql.Date.valueOf("2026-02-25");
+        String jsonString = bindingJsonb.toJson(yearMonthFormatted);
+        assertEquals("{\"date\":\"2026-02\"}", jsonString);
+    }
+
+    @Test
+    public void testUnmarshallYearMonthFormat() {
+        final YearMonthFormatted yearMonthFormatted = bindingJsonb.fromJson(
+                "{\"date\":\"2026-02\"}",
+                YearMonthFormatted.class);
+        final LocalDate resultDate = yearMonthFormatted.date.toInstant()
+                .atZone(ZoneOffset.UTC)
+                .toLocalDate();
+        assertEquals(LocalDate.of(2026, 2, 1), resultDate);
+    }
+
+    @Test
+    public void testDateOnlyPatternEdgeCases() {
+        // Test various edge cases to ensure date values are preserved correctly
+        testDateRoundTrip("2028-03-01"); // Day after leap year
+        testDateRoundTrip("2026-12-31"); // Last day of year
+        testDateRoundTrip("2028-02-29"); // Leap year day
+        testDateRoundTrip("2027-01-01"); // First day of year
+        testDateRoundTrip("2028-01-31"); // Last day of January
+        testDateRoundTrip("2028-02-01"); // First day of February
+    }
+
+    private void testDateRoundTrip(final String date) {
+        final String json = String.format("{\"sqlDate\":\"%1$s\",\"utilDate\":\"%1$s\"}", date);
+
+        // Deserialize
+        final SqlDateFormatted deserialized = bindingJsonb.fromJson(json, SqlDateFormatted.class);
+
+        // Verify utilDate represents midnight UTC for the specified date
+        final LocalDate resultDate = deserialized.utilDate.toInstant()
+                .atZone(ZoneOffset.UTC)
+                .toLocalDate();
+        assertEquals(LocalDate.parse(date), resultDate, () -> String.format("Date should be %s when viewed in UTC", date));
+
+        // Verify JSON round-trip
+        final String roundTripped = bindingJsonb.toJson(deserialized);
+        assertEquals(json, roundTripped, () -> String.format("JSON should round-trip correctly for %s", date));
+    }
+
+
     @Test
     public void testSqlDateTimeZonesFormatted() {
         testSqlDateWithTZFormatted(TimeZone.getTimeZone(ZoneId.of("Europe/Sofia")));
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 1362361..42eb35a 100644
--- a/src/test/java/org/eclipse/yasson/defaultmapping/generics/GenericsTest.java
+++ b/src/test/java/org/eclipse/yasson/defaultmapping/generics/GenericsTest.java
@@ -12,13 +12,19 @@
 
 package org.eclipse.yasson.defaultmapping.generics;
 
+import static org.eclipse.yasson.Jsonbs.defaultJsonb;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.lang.reflect.Field;
 import java.lang.reflect.Type;
-import java.lang.reflect.WildcardType;
 import java.math.BigDecimal;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedList;
@@ -30,8 +36,6 @@
 import jakarta.json.bind.Jsonb;
 import jakarta.json.bind.JsonbBuilder;
 import jakarta.json.bind.JsonbConfig;
-import java.lang.reflect.Field;
-import java.util.Collection;
 import org.eclipse.yasson.TestTypeToken;
 import org.eclipse.yasson.adapters.model.GenericBox;
 import org.eclipse.yasson.defaultmapping.generics.model.AnotherGenericTestClass;
@@ -48,24 +52,21 @@
 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.LowerBoundTypeVariableWithCollectionAttributeClass;
 import org.eclipse.yasson.defaultmapping.generics.model.MultiLevelExtendedGenericTestClass;
 import org.eclipse.yasson.defaultmapping.generics.model.MultipleBoundsContainer;
 import org.eclipse.yasson.defaultmapping.generics.model.MyCyclicGenericClass;
 import org.eclipse.yasson.defaultmapping.generics.model.PropagatedGenericClass;
 import org.eclipse.yasson.defaultmapping.generics.model.Shape;
 import org.eclipse.yasson.defaultmapping.generics.model.StaticCreatorContainer;
+import org.eclipse.yasson.defaultmapping.generics.model.TreeContainer;
+import org.eclipse.yasson.defaultmapping.generics.model.TreeElement;
 import org.eclipse.yasson.defaultmapping.generics.model.WildCardClass;
 import org.eclipse.yasson.defaultmapping.generics.model.WildcardMultipleBoundsClass;
 import org.eclipse.yasson.serializers.model.Box;
 import org.eclipse.yasson.serializers.model.Crate;
 import org.junit.jupiter.api.Test;
 
-import static org.eclipse.yasson.Jsonbs.defaultJsonb;
-import org.eclipse.yasson.defaultmapping.generics.model.LowerBoundTypeVariableWithCollectionAttributeClass;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
 /**
  * This class contains JSONB default mapping generics tests.
  *
@@ -513,6 +514,18 @@
         final CollectionContainer result = defaultJsonb.fromJson(expectedJson, CollectionContainer.class);
         assertEquals(collectionContainer, result);
     }
+
+    @Test
+    public void genericUpperBoundContainer() {
+        final String expectedJson = "{\"tree\":{\"children\":[{\"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));
+
+    }
     
     public interface FunctionalInterface<T> {
         T getValue();
diff --git a/src/test/java/org/eclipse/yasson/defaultmapping/generics/model/TreeContainer.java b/src/test/java/org/eclipse/yasson/defaultmapping/generics/model/TreeContainer.java
new file mode 100644
index 0000000..f24662a
--- /dev/null
+++ b/src/test/java/org/eclipse/yasson/defaultmapping/generics/model/TreeContainer.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2025 Red Hat, 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;
+
+/**
+ * @author <a href="mailto:jperkins@redhat.com">James R. Perkins</a>
+ */
+public class TreeContainer<T extends TreeTypeContainer<T>> {
+
+    private TreeTypeContainer<T> tree;
+
+    public TreeTypeContainer<T> getTree() {
+        return tree;
+    }
+
+    public void setTree(final TreeTypeContainer<T> tree) {
+        this.tree = tree;
+    }
+}
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
new file mode 100644
index 0000000..8668761
--- /dev/null
+++ b/src/test/java/org/eclipse/yasson/defaultmapping/generics/model/TreeElement.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2025 Red Hat, 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.ArrayList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:jperkins@redhat.com">James R. Perkins</a>
+ */
+public class TreeElement implements TreeTypeContainer<TreeElement> {
+
+    private final String name;
+    private List<TreeElement> children = new ArrayList<>();
+
+    public TreeElement(final String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public List<TreeElement> getChildren() {
+        return children;
+    }
+
+    public void setChildren(final List<TreeElement> children) {
+        this.children = children;
+    }
+}
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
new file mode 100644
index 0000000..120ae73
--- /dev/null
+++ b/src/test/java/org/eclipse/yasson/defaultmapping/generics/model/TreeTypeContainer.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2025 Red Hat, 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@redhat.com">James R. Perkins</a>
+ */
+public interface TreeTypeContainer<T extends TreeTypeContainer<T>> {
+
+    List<T> getChildren();
+
+    void setChildren(List<T> children);
+}
diff --git a/src/test/java/org/eclipse/yasson/records/CarWithGenerics.java b/src/test/java/org/eclipse/yasson/records/CarWithGenerics.java
new file mode 100644
index 0000000..619817a
--- /dev/null
+++ b/src/test/java/org/eclipse/yasson/records/CarWithGenerics.java
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2025 IBM 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
+ */
+
+package org.eclipse.yasson.records;
+
+public record CarWithGenerics<T> (String type, T color)  {
+}
diff --git a/src/test/java/org/eclipse/yasson/records/Color.java b/src/test/java/org/eclipse/yasson/records/Color.java
new file mode 100644
index 0000000..8744500
--- /dev/null
+++ b/src/test/java/org/eclipse/yasson/records/Color.java
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2025 IBM 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
+ */
+
+package org.eclipse.yasson.records;
+    
+public record Color(String name, String code) {
+}
diff --git a/src/test/java/org/eclipse/yasson/records/RecordTest.java b/src/test/java/org/eclipse/yasson/records/RecordTest.java
index 2b6a61e..3c1b72a 100644
--- a/src/test/java/org/eclipse/yasson/records/RecordTest.java
+++ b/src/test/java/org/eclipse/yasson/records/RecordTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021, 2024 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2021, 2025 Oracle 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
@@ -15,6 +15,7 @@
 import jakarta.json.bind.JsonbException;
 
 import org.eclipse.yasson.Jsonbs;
+import org.eclipse.yasson.TestTypeToken;
 import org.eclipse.yasson.internal.properties.MessageKeys;
 import org.eclipse.yasson.internal.properties.Messages;
 import org.junit.jupiter.api.Test;
@@ -106,4 +107,16 @@
         assertThrows(JsonbException.class, () -> Jsonbs.defaultJsonb.fromJson(expected, CarWithDefaultConstructor.class));
     }
 
+    @Test
+    public void testRecordWithGenerics() {
+        CarWithGenerics<Color> car = new CarWithGenerics<>("skoda", new Color("green", "#00FF00"));
+        String expected = "{\"color\":{\"code\":\"#00FF00\",\"name\":\"green\"},\"type\":\"skoda\"}";
+
+        String json = Jsonbs.defaultJsonb.toJson(car);
+        assertThat(json, is(expected));
+        
+        CarWithGenerics<Color> deserialized = Jsonbs.defaultJsonb
+                .fromJson(expected, new TestTypeToken<CarWithGenerics<Color>>() {}.getType());
+        assertThat(deserialized, is(car));  
+    }
 }
diff --git a/src/test/java/org/eclipse/yasson/serializers/MapToEntriesArraySerializerTest.java b/src/test/java/org/eclipse/yasson/serializers/MapToEntriesArraySerializerTest.java
index 3961dd2..36131c2 100644
--- a/src/test/java/org/eclipse/yasson/serializers/MapToEntriesArraySerializerTest.java
+++ b/src/test/java/org/eclipse/yasson/serializers/MapToEntriesArraySerializerTest.java
@@ -13,12 +13,17 @@
 package org.eclipse.yasson.serializers;
 
 import org.junit.jupiter.api.*;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.jupiter.api.Assertions.*;
 
 import java.io.StringReader;
 import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Type;
 import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.time.format.FormatStyle;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.Locale;
@@ -851,6 +856,26 @@
         }
     }
 
+    public static class LocalDateSerializer implements JsonbSerializer<LocalDate> {
+
+        private static final DateTimeFormatter SHORT_FORMAT = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT);
+
+        @Override
+        public void serialize(LocalDate obj, JsonGenerator generator, SerializationContext ctx) {
+            generator.write(SHORT_FORMAT.format(obj));
+        }
+    }
+
+    public static class LocalDateDeserializer implements JsonbDeserializer<LocalDate> {
+
+        private static final DateTimeFormatter SHORT_FORMAT = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT);
+
+        @Override
+        public LocalDate deserialize(JsonParser parser, DeserializationContext ctx, Type rtType) {
+            return LocalDate.parse(parser.getString(), SHORT_FORMAT);
+        }
+    }
+
     public static class MapObject<K, V> {
 
         private Map<K, V> values;
@@ -934,4 +959,53 @@
         MapObjectLocaleString resObject = jsonb.fromJson(json, MapObjectLocaleString.class);
         assertEquals(mapObject, resObject);
     }
+
+    public static class MapObjectLocalDateString extends MapObject<LocalDate, String> {};
+
+    private void verifyMapObjectCustomLocalDateStringSerialization(JsonObject jsonObject, MapObjectLocalDateString mapObject) {
+
+        // Expected serialization is: {"values":[{"key":"short-local-date","value":"string"},...]}
+        assertEquals(1, jsonObject.size());
+        assertNotNull(jsonObject.get("values"));
+        assertEquals(JsonValue.ValueType.ARRAY, jsonObject.get("values").getValueType());
+        JsonArray jsonArray = jsonObject.getJsonArray("values");
+        assertEquals(mapObject.getValues().size(), jsonArray.size());
+        MapObjectLocalDateString resObject = new MapObjectLocalDateString();
+        for (JsonValue jsonValue : jsonArray) {
+            assertEquals(JsonValue.ValueType.OBJECT, jsonValue.getValueType());
+            JsonObject entry = jsonValue.asJsonObject();
+            assertEquals(2, entry.size());
+            assertNotNull(entry.get("key"));
+            assertEquals(JsonValue.ValueType.STRING, entry.get("key").getValueType());
+            assertNotNull(entry.get("value"));
+            assertEquals(JsonValue.ValueType.STRING, entry.get("value").getValueType());
+            resObject.getValues().put(LocalDate.parse(entry.getString("key"), DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT)), entry.getString("value"));
+        }
+        assertEquals(mapObject, resObject);
+    }
+
+    /**
+     * Test for issue #663...
+     * Test a LocalDate/String map as member in a custom class, using a custom LocalDate serializer and deserializer,
+     * even though there's a build-in {@link org.eclipse.yasson.internal.serializer.types.TypeSerializers#isSupportedMapKey(Class)}
+     */
+    @Test
+    public void testMapLocalDateKeyStringValueAsMember() {
+        Jsonb jsonb = JsonbBuilder.create(new JsonbConfig()
+                .withSerializers(new LocalDateSerializer())
+                .withDeserializers(new LocalDateDeserializer()));
+
+        MapObjectLocalDateString mapObject = new MapObjectLocalDateString();
+        mapObject.getValues().put(LocalDate.now(), "today");
+        mapObject.getValues().put(LocalDate.now().plusDays(1), "tomorrow");
+
+        String json = jsonb.toJson(mapObject);
+
+        JsonObject jsonObject = Json.createReader(new StringReader(json)).read().asJsonObject();
+        verifyMapObjectCustomLocalDateStringSerialization(jsonObject, mapObject);
+        MapObjectLocalDateString resObject = jsonb.fromJson(json, MapObjectLocalDateString.class);
+        assertEquals(mapObject, resObject);
+        // ensure the keys are of type java.time.LocalDate
+        assertThat(resObject.getValues().keySet().iterator().next(), instanceOf(LocalDate.class));
+    }
 }
diff --git a/src/test/java/org/eclipse/yasson/serializers/TypeDeserializerOnContainersTest.java b/src/test/java/org/eclipse/yasson/serializers/TypeDeserializerOnContainersTest.java
new file mode 100644
index 0000000..7cd1e3f
--- /dev/null
+++ b/src/test/java/org/eclipse/yasson/serializers/TypeDeserializerOnContainersTest.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (c) 2025 IBM 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
+ */
+
+package org.eclipse.yasson.serializers;
+
+import java.lang.reflect.Type;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+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.config.BinaryDataStrategy;
+import jakarta.json.bind.serializer.DeserializationContext;
+import jakarta.json.bind.serializer.JsonbDeserializer;
+import jakarta.json.stream.JsonParser;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests that {@link jakarta.json.bind.annotation.JsonbTypeDeserializer @JsonbTypeDeserializer} annotated types are
+ * properly detected and used when those types are used as elements/values in containers (Maps, Collections,
+ * Arrays, Optionals).
+ *
+ * @author <a href="mailto:jperkins@ibm.com">James R. Perkins</a>
+ */
+public class TypeDeserializerOnContainersTest {
+
+    // Test interface with type-level deserializer annotation
+    @JsonbTypeDeserializer(TestInterfaceDeserializer.class)
+    public interface TestInterface {
+        String getValue();
+    }
+
+    // Implementation of the test interface
+    public static class TestImpl implements TestInterface {
+        private final String value;
+
+        public TestImpl(final String value) {
+            this.value = value;
+        }
+
+        @Override
+        public String getValue() {
+            return value;
+        }
+    }
+
+    // Custom deserializer for TestInterface
+    public static class TestInterfaceDeserializer implements JsonbDeserializer<TestInterface> {
+        @Override
+        public TestInterface deserialize(final JsonParser parser, final DeserializationContext ctx, final Type rtType) {
+            // Parse the JSON object to get the value field
+            Assertions.assertTrue(parser.hasNext(), "Expected the key name");
+            parser.next();
+            Assertions.assertTrue(parser.hasNext(), "Expected the value");
+            parser.next();
+            final String value = parser.getString();
+            Assertions.assertTrue(parser.hasNext(), "Expected the end of an object");
+            parser.next();
+            return new TestImpl("DESERIALIZED:" + value);
+        }
+    }
+
+    // Container classes for testing
+    public static class MapContainer {
+        public Map<String, TestInterface> map;
+        public Map<?, ?> questionKeyMap;
+        public Map<String, ?> questionValueMap;
+    }
+
+    public static class ListContainer {
+        public List<TestInterface> list;
+        public List<?> questionList;
+    }
+
+    public static class ArrayContainer {
+        public TestInterface[] array;
+    }
+
+    @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
+    public static class OptionalContainer {
+        public Optional<TestInterface> optional;
+        public Optional<?> questionOptional;
+    }
+
+    public static class ByteArrayContainer {
+        public byte[] data;
+    }
+
+    private Jsonb jsonb;
+
+    @BeforeEach
+    public void createJsonb() {
+        // Create a new Jsonb for each test to avoid type caching
+        jsonb = JsonbBuilder.create();
+    }
+
+    @AfterEach
+    public void closeJsonb() throws Exception {
+        if (jsonb != null) {
+            jsonb.close();
+        }
+    }
+
+    @Test
+    public void testTypeDeserializerOnMapValues() {
+        final String json = "{\"map\":{\"key1\":{\"value\":\"value1\"},\"key2\":{\"value\":\"value2\"}}, \"questionKeyMap\":{\"qKey1\":\"value1\",\"qKey2\":\"value2\"},\"questionValueMap\":{\"key1\":\"qValue1\",\"key2\":\"qValue2\"}}";
+
+        final MapContainer result = jsonb.fromJson(json, MapContainer.class);
+
+        Assertions.assertNotNull(result.map);
+        Assertions.assertEquals(2, result.map.size(), () -> String.format("Expected two entries got %s", result.map));
+        Assertions.assertEquals("DESERIALIZED:value1", result.map.get("key1").getValue());
+        Assertions.assertEquals("DESERIALIZED:value2", result.map.get("key2").getValue());
+
+        Assertions.assertNotNull(result.questionKeyMap);
+        Assertions.assertEquals(2, result.questionKeyMap.size(), () -> String.format("Expected two entries got %s", result.questionKeyMap));
+        Assertions.assertEquals("value1", result.questionKeyMap.get("qKey1"));
+        Assertions.assertEquals("value2", result.questionKeyMap.get("qKey2"));
+
+        Assertions.assertNotNull(result.questionValueMap);
+        Assertions.assertEquals(2, result.questionValueMap.size(), () -> String.format("Expected two entries got %s", result.questionValueMap));
+        Assertions.assertEquals("qValue1", result.questionValueMap.get("key1"));
+        Assertions.assertEquals("qValue2", result.questionValueMap.get("key2"));
+    }
+
+    @Test
+    public void testTypeDeserializerOnListElements() {
+        final String json = "{\"list\":[{\"value\":\"value1\"},{\"value\":\"value2\"}], \"questionList\": [\"value1\", \"value2\"]}";
+
+        final ListContainer result = jsonb.fromJson(json, ListContainer.class);
+
+        Assertions.assertNotNull(result.list);
+        Assertions.assertEquals(2, result.list.size(), () -> String.format("Expected two entries got %s", result.list));
+        Assertions.assertEquals("DESERIALIZED:value1", result.list.get(0).getValue());
+        Assertions.assertEquals("DESERIALIZED:value2", result.list.get(1).getValue());
+
+        Assertions.assertNotNull(result.questionList);
+        Assertions.assertEquals(2, result.questionList.size(), () -> String.format("Expected two entries got %s", result.questionList));
+        Assertions.assertEquals("value1", result.questionList.get(0));
+        Assertions.assertEquals("value2", result.questionList.get(1));
+    }
+
+    @Test
+    public void testTypeDeserializerOnArrayElements() {
+        final String json = "{\"array\":[{\"value\":\"value1\"},{\"value\":\"value2\"}]}";
+
+        final ArrayContainer result = jsonb.fromJson(json, ArrayContainer.class);
+
+        Assertions.assertNotNull(result.array);
+        Assertions.assertEquals(2, result.array.length, () -> String.format("Expected two entries got %s", Arrays.toString(result.array)));
+        Assertions.assertEquals("DESERIALIZED:value1", result.array[0].getValue());
+        Assertions.assertEquals("DESERIALIZED:value2", result.array[1].getValue());
+    }
+
+    @Test
+    public void testTypeDeserializerOnOptionalValue() {
+        final String json = "{\"optional\":{\"value\":\"value1\"},\"questionOptional\":\"value2\"}";
+
+        final OptionalContainer result = jsonb.fromJson(json, OptionalContainer.class);
+
+        Assertions.assertNotNull(result.optional);
+        Assertions.assertTrue(result.optional.isPresent(), "Expected value to be present, but the optional was empty.");
+        Assertions.assertEquals("DESERIALIZED:value1", result.optional.get().getValue());
+
+        Assertions.assertNotNull(result.questionOptional);
+        Assertions.assertTrue(result.questionOptional.isPresent(), "Expected value to be present, but the optional was empty.");
+        Assertions.assertEquals("value2", result.questionOptional.get());
+    }
+
+    @Test
+    public void testTypeDeserializerOnByteArray() {
+        final String json = "{\"data\":[1,2,3,4,5]}";
+
+        final ByteArrayContainer result = jsonb.fromJson(json, ByteArrayContainer.class);
+
+        Assertions.assertNotNull(result.data);
+        Assertions.assertEquals(5, result.data.length);
+        Assertions.assertArrayEquals(new byte[]{1, 2, 3, 4, 5}, result.data);
+    }
+
+    @Test
+    public void testTypeDeserializerOnByteArrayWithBase64() throws Exception {
+        try (Jsonb base64Jsonb = JsonbBuilder.create(new JsonbConfig()
+                .withBinaryDataStrategy(BinaryDataStrategy.BASE_64))) {
+
+            // "SGVsbG8=" is "Hello" in base64
+            final String json = "{\"data\":\"SGVsbG8=\"}";
+
+            final ByteArrayContainer result = base64Jsonb.fromJson(json, ByteArrayContainer.class);
+
+            Assertions.assertNotNull(result.data);
+            Assertions.assertArrayEquals("Hello".getBytes(), result.data);
+        }
+    }
+}
diff --git a/src/test/java/org/eclipse/yasson/serializers/TypeSerializerOnContainersTest.java b/src/test/java/org/eclipse/yasson/serializers/TypeSerializerOnContainersTest.java
new file mode 100644
index 0000000..89f3ff2
--- /dev/null
+++ b/src/test/java/org/eclipse/yasson/serializers/TypeSerializerOnContainersTest.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (c) 2025 IBM 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
+ */
+
+package org.eclipse.yasson.serializers;
+
+import java.io.StringReader;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Supplier;
+
+import jakarta.json.Json;
+import jakarta.json.JsonArray;
+import jakarta.json.JsonObject;
+import jakarta.json.JsonReader;
+import jakarta.json.bind.Jsonb;
+import jakarta.json.bind.JsonbBuilder;
+import jakarta.json.bind.JsonbException;
+import jakarta.json.bind.annotation.JsonbTypeSerializer;
+import jakarta.json.bind.serializer.JsonbSerializer;
+import jakarta.json.bind.serializer.SerializationContext;
+import jakarta.json.stream.JsonGenerator;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests that {@link jakarta.json.bind.annotation.JsonbTypeSerializer @JsonbTypeSerializer} annotated types are
+ * properly detected and used when those types are used as elements/values in containers (Maps, Collections,
+ * Arrays, Optionals).
+ *
+ * @author <a href="mailto:jperkins@ibm.com">James R. Perkins</a>
+ */
+public class TypeSerializerOnContainersTest {
+
+    // Test interface with type-level serializer annotation
+    @JsonbTypeSerializer(TestInterfaceSerializer.class)
+    public interface TestInterface {
+        String getValue();
+    }
+
+    // Implementation of the test interface
+    public static class TestImpl implements TestInterface {
+        private final String value;
+
+        public TestImpl(final String value) {
+            this.value = value;
+        }
+
+        @Override
+        public String getValue() {
+            return value;
+        }
+    }
+
+    // Custom serializer for TestInterface
+    public static class TestInterfaceSerializer implements JsonbSerializer<TestInterface> {
+        @Override
+        public void serialize(final TestInterface obj, final JsonGenerator generator, final SerializationContext ctx) {
+            generator.write("SERIALIZED:" + obj.getValue());
+        }
+    }
+
+    // Container classes for testing
+    public static class MapContainer {
+        public final Map<String, TestInterface> map;
+        public final Map<?, ?> questionKeyMap;
+        public final Map<String, ?> questionValueMap;
+
+        public MapContainer(final Map<String, TestInterface> map, final Map<?, ?> questionKeyMap, final Map<String, ?> questionValueMap) {
+            this.map = map;
+            this.questionKeyMap = questionKeyMap;
+            this.questionValueMap = questionValueMap;
+        }
+    }
+
+    public static class ListContainer {
+        public final List<TestInterface> list;
+        public final List<?> questionList;
+
+        public ListContainer(final List<TestInterface> list, final List<?> questionList) {
+            this.list = list;
+            this.questionList = questionList;
+        }
+    }
+
+    public static class ArrayContainer {
+        public final TestInterface[] array;
+
+        public ArrayContainer(TestInterface[] array) {
+            this.array = array;
+        }
+    }
+
+    @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
+    public static class OptionalContainer {
+        public final Optional<TestInterface> optional;
+        public final Optional<?> questionOptional;
+
+        public OptionalContainer(final Optional<TestInterface> optional, final Optional<?> questionOptional) {
+            this.optional = optional;
+            this.questionOptional = questionOptional;
+        }
+    }
+
+    private Jsonb jsonb;
+
+    @BeforeEach
+    public void createJsonb() {
+        // Create a new Jsonb for each test to avoid type caching
+        jsonb = JsonbBuilder.create();
+    }
+
+    @AfterEach
+    public void closeJsonb() throws Exception {
+        if (jsonb != null) {
+            jsonb.close();
+        }
+    }
+
+    @Test
+    public void testTypeSerializerOnMapValues() {
+        final MapContainer container = new MapContainer(Map.of(
+                "key1", new TestImpl("value1"),
+                "key2", new TestImpl("value2")
+        ), Map.of("qKey1", "value1", "qKey2", "value2"),
+                Map.of("key1", "qValue1", "key2", "qValue2")
+        );
+
+        final JsonObject json = toJsonObject(container);
+        final JsonObject map = json.getJsonObject("map");
+        final JsonObject questionKeyMap = json.getJsonObject("questionKeyMap");
+        final JsonObject questionValueMap = json.getJsonObject("questionValueMap");
+
+        Supplier<String> errorMessage = () -> String.format("Expected value not found in %s", map);
+        Assertions.assertEquals("SERIALIZED:value1", map.getString("key1"), errorMessage);
+        Assertions.assertEquals("SERIALIZED:value2", map.getString("key2"), errorMessage);
+
+
+        errorMessage = () -> String.format("Expected value not found in %s", questionKeyMap);
+        Assertions.assertEquals("value1", questionKeyMap.getString("qKey1"), errorMessage);
+        Assertions.assertEquals("value2", questionKeyMap.getString("qKey2"), errorMessage);
+
+
+        errorMessage = () -> String.format("Expected value not found in %s", questionValueMap);
+        Assertions.assertEquals("qValue1", questionValueMap.getString("key1"), errorMessage);
+        Assertions.assertEquals("qValue2", questionValueMap.getString("key2"), errorMessage);
+    }
+
+    @Test
+    public void testTypeSerializerOnListElements() {
+        final ListContainer container = new ListContainer(List.of(
+                new TestImpl("value1"),
+                new TestImpl("value2")
+        ), List.of("qValue1", "qValue2"));
+
+        final JsonObject json = toJsonObject(container);
+        final JsonArray list = json.getJsonArray("list");
+        final JsonArray questionList = json.getJsonArray("questionList");
+
+        Supplier<String> errorMessage = () -> String.format("Expected value not found in %s", list);
+        Assertions.assertEquals(2, list.size(), () -> String.format("Expected a size of 2 in %s", list));
+        Assertions.assertEquals("SERIALIZED:value1", list.getString(0), errorMessage);
+        Assertions.assertEquals("SERIALIZED:value2", list.getString(1), errorMessage);
+
+        errorMessage = () -> String.format("Expected value not found in %s", questionList);
+        Assertions.assertEquals(2, questionList.size(), () -> String.format("Expected a size of 2 in %s", questionList));
+        Assertions.assertEquals("qValue1", questionList.getString(0), errorMessage);
+        Assertions.assertEquals("qValue2", questionList.getString(1), errorMessage);
+    }
+
+    @Test
+    public void testTypeSerializerOnArrayElements() {
+        final ArrayContainer container = new ArrayContainer(new TestInterface[] {
+                new TestImpl("value1"),
+                new TestImpl("value2")
+        });
+
+        final String json = jsonb.toJson(container);
+
+        Assertions.assertEquals("{\"array\":[\"SERIALIZED:value1\",\"SERIALIZED:value2\"]}", json);
+    }
+
+    @Test
+    public void testTypeSerializerOnOptionalValue() {
+        final OptionalContainer container = new OptionalContainer(Optional.of(new TestImpl("value1")), Optional.of("value2"));
+
+        final JsonObject json = toJsonObject(container);
+
+        Assertions.assertEquals("SERIALIZED:value1", json.getString("optional"));
+        Assertions.assertEquals("value2", json.getString("questionOptional"));
+    }
+
+    private JsonObject toJsonObject(final Object object) throws JsonbException {
+        final String value = jsonb.toJson(object);
+        try (
+                StringReader reader = new StringReader(value);
+                JsonReader jsonReader = Json.createReader(reader)
+        ) {
+            return jsonReader.readObject();
+        }
+    }
+}
diff --git a/yasson-tck/pom.xml b/yasson-tck/pom.xml
index bd0d76e..c3d36c7 100644
--- a/yasson-tck/pom.xml
+++ b/yasson-tck/pom.xml
@@ -11,22 +11,13 @@
 
     <properties>
         <jsonb.tck.version>3.0.0</jsonb.tck.version>
-        <yasson.version>3.0.4-SNAPSHOT</yasson.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>
     </properties>
 
-    <!-- TODO: Temporarily enable snapshot repository -->
-    <!-- This can be removed once an official release of jakarta.json.bind-tck is available -->
-    <repositories>
-        <repository>
-            <id>jakarta-snapshots</id>
-            <url>https://jakarta.oss.sonatype.org/content/repositories/staging/</url>
-        </repository>
-    </repositories>
-
     <dependencies>
         <dependency>
             <groupId>jakarta.json.bind</groupId>