Skip to content

fix(kotlin-generator): add @AvroAlias on union subtype wrappers#477

Closed
jantoebes wants to merge 2 commits intoavro-kotlin:mainfrom
jantoebes:fix-union-subtype-avro-alias
Closed

fix(kotlin-generator): add @AvroAlias on union subtype wrappers#477
jantoebes wants to merge 2 commits intoavro-kotlin:mainfrom
jantoebes:fix-union-subtype-avro-alias

Conversation

@jantoebes
Copy link
Copy Markdown

Summary

Value-class wrappers generated inside a sealed interface for a complex Avro union had no @AvroAlias pointing at the inner Avro full name, so PolymorphicResolver could not map the incoming schema fullName (e.g. com.example.MyRecord, int, map) to the wrapper SerialName (ForMyRecord, ForInt, ForMap) at runtime.

This caused SerializationException: Serializer for subclass ... is not found on decode for every multi-type union.

Fix

Emit @AvroAlias(subSchema.fullName) on each value-class wrapper so AbstractPolymorphicDecoder can resolve the subtype via the existing alias lookup in PolymorphicResolver (see core/src/main/kotlin/com/github/avrokotlin/avro4k/internal/PolymorphicResolver.kt).

Before

public sealed interface TestSchema {
    @JvmInline
    @Serializable
    public value class ForString(public val `value`: String) : TestSchema
    // ...
}

After

public sealed interface TestSchema {
    @AvroAlias("string")
    @JvmInline
    @Serializable
    public value class ForString(public val `value`: String) : TestSchema
    // ...
}

Test evidence

  • expected-sources fixtures updated automatically by the local updateExpectedGeneratedSources test path.
  • CI=true ./gradlew test passes across all modules (core, kotlin-generator, gradle-plugin, confluent-kafka-serializer).

Notes

Applies to every fullName-bearing subschema (records, enums, fixed, and primitives/arrays/maps whose fullName == simpleName). No behavioural change for single-type or nullable-only unions (they do not hit generateSealedInterface).

Value-class wrappers generated inside a sealed interface for a complex
Avro union had no @AvroAlias pointing at the inner Avro full name, so
PolymorphicResolver could not map the incoming schema fullName
(e.g. "com.example.MyRecord", "int", "map") to the wrapper
SerialName ("ForMyRecord", "ForInt", "ForMap") at runtime.

This caused SerializationException: 'Serializer for subclass ... is not
found' on decode for every multi-type union.

Fix: emit @AvroAlias(subSchema.fullName) on each value-class wrapper so
AbstractPolymorphicDecoder can resolve the subtype via the existing
alias lookup in PolymorphicResolver.
@Chuckame
Copy link
Copy Markdown
Member

Hello, thanks for the contribution. However, you are fixing the symptom, and not the root cause.

Quickly thinking, the issue should be in the polymorphic resolver/decoder, as the string is wrapped into a value class, it is actually only using the serial name of the type, while it should instead use the produced schema name of the type. You can try to fix it!

Probably create a test in the core module, testing that encoding and decoding a union with a mix of value class' primitives and named types like enums / record. I think you will hit the same error 👀

@jantoebes
Copy link
Copy Markdown
Author

You're right, that was fixing the symptom. I've opened #479 which fixes the root cause directly in PolymorphicResolver - instead of using the serial name as the lookup key, it now resolves the actual Avro schema name the type produces in the union (unwrapping value classes down to the primitive type name). The alias workaround in the generator is no longer needed with that fix in place.

@Chuckame Chuckame closed this May 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants