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 found
  • Class 'NewType' is not registered for polymorphic deserialization
  • API adds new event type and app crashes on deserialization
  • classDiscriminator value 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
  • serialName annotation 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. 1.**Register all subclasses in the serializers module":
  2. 2.```kotlin
  3. 3.import kotlinx.serialization.*
  4. 4.import kotlinx.serialization.json.*
  5. 5.import kotlinx.serialization.modules.SerializersModule
  6. 6.import kotlinx.serialization.modules.polymorphic
  7. 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. 1.**Handle unknown class discriminator with fallback":
  2. 2.```kotlin
  3. 3.@Serializable
  4. 4.@SerialName("unknown")
  5. 5.data class UnknownEvent(
  6. 6.override val id: String,
  7. 7.val rawData: JsonElement
  8. 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. 1.**Configure class discriminator to match server format":
  2. 2.```kotlin
  3. 3.val json = Json {
  4. 4.// Default is "type", change if server uses different field name
  5. 5.classDiscriminator = "__type"
  6. 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 @SerialName annotations matching server's class discriminator values
  • Add a fallback UnknownEvent subclass 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