Skip to main content
Version: dev

Kotlin

The Kotlin target emits Kotlin source only. The compiler does not generate Java files.

Output Layout

For source file addressbook.fdl with package addressbook, Kotlin output is generated under:

  • <kotlin_out>/addressbook/
  • Type files: AddressBook.kt, Person.kt, Dog.kt, Cat.kt, Animal.kt
  • Schema module: AddressbookForyModule.kt

The schema module name is derived from the source file stem. Schemas in the same Kotlin package need distinct generated file names; duplicate generated Kotlin file paths are rejected before files are written.

If option kotlin_package = "..."; is present, the output path and Kotlin package use that option. Otherwise Kotlin uses the FDL package. A Kotlin import graph cannot mix default-package schemas with named Kotlin packages. Registration still uses the FDL package so cross-language type names stay stable.

Type Generation

Messages generate Kotlin data class declarations by default:

@ForyStruct
public data class Person(
@field:ForyField(id = 1)
public val name: String,

@field:ForyField(id = 7)
public val phones: List<PersonPhoneNumber>,

@field:ForyField(id = 8)
public val pet: Animal,
) {
public fun toBytes(): ByteArray = AddressbookForyModule.getFory().serialize(this)

public companion object {
public fun fromBytes(bytes: ByteArray): Person =
AddressbookForyModule.getFory().deserialize(bytes, Person::class.java)
}
}

Messages that participate in compiler-detected construction cycles generate normal mutable classes so the generated serializer can publish the instance before reading back-references:

@ForyStruct
public class Node() {
@ForyField(id = 1)
public var id: String = ""

@Ref
@ForyField(id = 2)
public var parent: Node? = null
}

Generated Kotlin IDL sources express nullability with Kotlin ?, not Fory @Nullable, including mutable classes emitted for compiler-detected construction cycles.

Enums generate Kotlin enum classes with stable Fory enum IDs. Unions generate sealed classes with @ForyUnion; the Fory-provided Unknown(UnknownCase) carrier is marked with @ForyUnknownCase. The marker only selects the carrier and does not add an entry to the schema case table. Schema-defined cases may use case IDs 0..N and hold a single value property. A typed union must have at least one non-Unknown case.

package addressbook

import org.apache.fory.annotation.ForyCase
import org.apache.fory.annotation.ForyUnion
import org.apache.fory.annotation.ForyUnknownCase
import org.apache.fory.type.union.UnknownCase

@ForyUnion
public sealed class Animal {
@ForyUnknownCase
public data class Unknown(public val value: UnknownCase) : Animal()

@ForyCase(id = 0)
public data class Dog(public val value: addressbook.Dog) : Animal()
}

Packaged Kotlin output keeps the schema case name and qualifies the payload type when both have the same simple name. If a target output mode cannot express a legal qualifier for a conflict, the compiler appends Case to the generated case class name.

Kotlin int32, int64, uint32, and uint64 fields use xlang varint encoding by default, so generated Kotlin does not emit @VarInt for the default case. It emits @Fixed or @Tagged only when the schema requests that non-default encoding. duration maps to kotlin.time.Duration, and infinite durations are rejected when encoded. Dense array<float16> and array<bfloat16> use the Java core Float16Array and BFloat16Array carriers. Generated Kotlin IDL uses @ArrayType ByteArray for array<int8>, including nested positions.

Schema Module

Generated schema modules register schema types and resolve KSP-generated serializers from the target class name. The package-owned helper Fory instance uses ForyKotlin.builder().withXlang(true) with the schema module installed, so message toBytes/fromBytes helpers work without caller-managed Fory setup. For addressbook.fdl:

public object AddressbookForyModule : ForyModule {
private val fory: ThreadSafeFory by lazy {
ForyKotlin.builder()
.withXlang(true)
.withRefTracking(true)
.withModule(this)
.buildThreadSafeFory()
}

internal fun getFory(): ThreadSafeFory = fory

override fun install(fory: Fory) {
KotlinSerializers.registerType(fory, Person::class.java, 100L)
KotlinSerializers.registerSerializer(fory, Person::class.java)
KotlinSerializers.registerUnion(fory, Animal::class.java, 106L)
}
}

registerUnion discovers the generated <Target>_ForySerializer; callers do not pass a serializer instance.

gRPC Service Companions

With --grpc, Kotlin emits one <ServiceName>GrpcKt.kt per service, not a Java companion. The generated object exposes SERVICE_NAME, service and method descriptors, <ServiceName>CoroutineImplBase, and <ServiceName>CoroutineStub. See Kotlin gRPC for dependencies, KSP setup, and coroutine and Flow usage.