Introduction
Kotlinx serialization supports polymorphic serialization where a base type can serialize to and from JSON with a class discriminator field. When the server sends a class discriminator value that is not registered as a subclass, deserialization fails with Serializer for class 'X' is not found. This commonly happens when the API adds new types that the client does not yet know about, causing the entire response to fail parsing.
Symptoms
kotlinx.serialization.SerializationException: Serializer for class 'X' is not foundClass 'NewType' is not registeredfor polymorphic deserialization- API adds new event type and app crashes on deserialization
classDiscriminatorvalue in JSON does not match registered serial names- Polymorphic serialization works for some types but not others
Error output:
``
kotlinx.serialization.SerializationException:
Serializer for class 'NotificationEvent' is not found.
Please ensure that class is marked as '@Serializable' and
that the serialization compiler plugin is applied.
Class discriminator value 'push_notification' is not registered.
Common Causes
- Server sends new subclass not registered in client's serializers module
serialNameannotation does not match class discriminator value- Polymorphic module not included in Json configuration
- Class discriminator field name differs from server's field name
- Subclass registered with different serial name than JSON contains
Step-by-Step Fix
- 1.**Register all subclasses in the serializers module":
- 2.```kotlin
- 3.import kotlinx.serialization.*
- 4.import kotlinx.serialization.json.*
- 5.import kotlinx.serialization.modules.SerializersModule
- 6.import kotlinx.serialization.modules.polymorphic
- 7.import kotlinx.serialization.modules.subclass
@Serializable sealed class NotificationEvent { abstract val id: String }
@Serializable @SerialName("message") data class MessageEvent(override val id: String, val text: String) : NotificationEvent()
@Serializable @SerialName("system") data class SystemEvent(override val id: String, val level: String) : NotificationEvent()
// NEW type from server - must be registered @Serializable @SerialName("push_notification") data class PushEvent(override val id: String, val title: String, val body: String) : NotificationEvent()
val json = Json { serializersModule = SerializersModule { polymorphic(NotificationEvent::class) { subclass(MessageEvent::class) subclass(SystemEvent::class) subclass(PushEvent::class) // Must register new types! } } } ```
- 1.**Handle unknown class discriminator with fallback":
- 2.```kotlin
- 3.@Serializable
- 4.@SerialName("unknown")
- 5.data class UnknownEvent(
- 6.override val id: String,
- 7.val rawData: JsonElement
- 8.) : NotificationEvent()
// Custom serializer that catches unknown types object NotificationEventSerializer : KSerializer<NotificationEvent> { override val descriptor = buildClassSerialDescriptor("NotificationEvent")
override fun deserialize(decoder: Decoder): NotificationEvent { val element = decoder.decodeSerializableValue(JsonElement.serializer()) as JsonObject val type = element["type"]?.jsonPrimitive?.content
return when (type) { "message" -> Json.decodeFromJsonElement(MessageEvent.serializer(), element) "system" -> Json.decodeFromJsonElement(SystemEvent.serializer(), element) "push_notification" -> Json.decodeFromJsonElement(PushEvent.serializer(), element) else -> { // Fallback for unknown types UnknownEvent( id = element["id"]?.jsonPrimitive?.content ?: "", rawData = element ) } } }
override fun serialize(encoder: Encoder, value: NotificationEvent) { encoder.encodeSerializableValue(JsonElement.serializer(), Json.encodeToJsonElement(value)) } } ```
- 1.**Configure class discriminator to match server format":
- 2.```kotlin
- 3.val json = Json {
- 4.// Default is "type", change if server uses different field name
- 5.classDiscriminator = "__type"
- 6.// Or "class", "$type", etc.
serializersModule = SerializersModule { polymorphic(NotificationEvent::class) { subclass(MessageEvent::class) subclass(SystemEvent::class) } } }
// Server JSON format: // {"__type": "message", "id": "123", "text": "Hello"} ```
Prevention
- Always register new server types in the serializers module
- Use
@SerialNameannotations matching server's class discriminator values - Add a fallback
UnknownEventsubclass for unregistered types - Monitor deserialization errors in production for new unknown types
- Use content-based decoding (custom serializer) for maximum flexibility
- Test polymorphic deserialization with all known and unknown types