ProtoStream supports Kotlin entities via kapt (Kotlin Annotation Processing Tool). This guide covers how to annotate Kotlin classes and configure your build.
Kotlin properties have private backing fields. The ProtoStream annotation processor rejects @ProtoField on private fields. Use the @get: use-site target to place the annotation on the generated getter method instead:
// Wrong - annotation lands on the private backing field
@ProtoField(number = 1)
var name: String? = null
// Correct - annotation lands on the getter method
@get:ProtoField(number = 1)
var name: String? = nullThis applies to both var and val properties, including data class constructor parameters.
class Person {
@get:ProtoField(number = 1)
var name: String? = null
@get:ProtoField(number = 2, defaultValue = "0")
var age: Int = 0
}Use @ProtoFactory on the constructor and @get:ProtoField on each property:
data class Person @ProtoFactory constructor(
@get:ProtoField(number = 1) val name: String,
@get:ProtoField(number = 2, defaultValue = "0") val age: Int,
@get:ProtoField(number = 3) val email: String?
)Kotlin default parameter values work as expected:
data class Config @ProtoFactory constructor(
@get:ProtoField(number = 1) val name: String,
@get:ProtoField(number = 2, defaultValue = "8080") val port: Int = 8080,
@get:ProtoField(number = 3) val description: String? = null
)Enum annotations do not need the @get: prefix:
enum class Status {
@ProtoEnumValue(number = 0) ACTIVE,
@ProtoEnumValue(number = 1) INACTIVE,
@ProtoEnumValue(number = 2) PENDING
}class Order {
@get:ProtoField(number = 1)
var id: String? = null
@get:ProtoField(number = 2)
var items: List<String>? = null
}Kotlin boolean properties named with an is prefix (e.g. isActive) generate non-standard accessor names (isActive() / setActive()). ProtoStream handles this correctly:
class Feature {
@get:ProtoField(number = 1, defaultValue = "false")
var isEnabled: Boolean = false
@get:ProtoField(number = 2, defaultValue = "false")
var isVisible: Boolean = false
}Define your schema interface exactly as in Java:
@ProtoSchema(
schemaFileName = "my_schema.proto",
schemaPackageName = "com.example",
includeClasses = [Person::class, Status::class, Order::class],
syntax = ProtoSyntax.PROTO3
)
interface MySchema : GeneratedSchemaThe annotation processor generates MySchemaImpl. Use it to register your schema:
val ctx = ProtobufUtil.newSerializationContext()
MySchemaImpl().register(ctx)
// Serialize
val bytes = ProtobufUtil.toWrappedByteArray(ctx, person)
// Deserialize
val restored = ProtobufUtil.fromWrappedByteArray<Person>(ctx, bytes)<properties>
<version.kotlin>2.1.10</version.kotlin>
<version.protostream>6.0.9</version.protostream>
</properties>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>${version.kotlin}</version>
</dependency>
<dependency>
<groupId>org.infinispan.protostream</groupId>
<artifactId>protostream</artifactId>
<version>${version.protostream}</version>
</dependency>
<dependency>
<groupId>org.infinispan.protostream</groupId>
<artifactId>protostream-processor</artifactId>
<version>${version.protostream}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
<plugins>
<!-- Kotlin plugin must be declared before maven-compiler-plugin -->
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${version.kotlin}</version>
<executions>
<execution>
<id>kapt</id>
<goals>
<goal>kapt</goal>
</goals>
<configuration>
<sourceDirs>
<sourceDir>${project.basedir}/src/main/kotlin</sourceDir>
</sourceDirs>
<annotationProcessorPaths>
<annotationProcessorPath>
<groupId>org.infinispan.protostream</groupId>
<artifactId>protostream-processor</artifactId>
<version>${version.protostream}</version>
</annotationProcessorPath>
</annotationProcessorPaths>
</configuration>
</execution>
<execution>
<id>compile</id>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<sourceDirs>
<sourceDir>${project.basedir}/src/main/kotlin</sourceDir>
<sourceDir>${project.build.directory}/generated-sources/kapt/compile</sourceDir>
</sourceDirs>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<executions>
<execution>
<id>default-compile</id>
<phase>none</phase>
</execution>
<execution>
<id>java-compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<proc>none</proc>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>plugins {
kotlin("jvm") version "2.1.10"
kotlin("kapt") version "2.1.10"
}
dependencies {
implementation("org.infinispan.protostream:protostream:6.0.9")
kapt("org.infinispan.protostream:protostream-processor:6.0.9")
}plugins {
id 'org.jetbrains.kotlin.jvm' version '2.1.10'
id 'org.jetbrains.kotlin.kapt' version '2.1.10'
}
dependencies {
implementation 'org.infinispan.protostream:protostream:6.0.9'
kapt 'org.infinispan.protostream:protostream-processor:6.0.9'
}Annotated properties and classes must be public or protected. Kotlin internal visibility causes name mangling in bytecode that the annotation processor cannot resolve.
Proto3 does not distinguish between null and default values for scalar types. A null String? will deserialize as "", a null Int? as 0, etc. Design your Kotlin types accordingly if using proto3.
Kotlin val properties have no setter. Without @ProtoFactory, the processor cannot instantiate and populate the object. Always use @ProtoFactory on the constructor of classes with val-only properties.
kapt is in maintenance mode. JetBrains recommends KSP (Kotlin Symbol Processing) for new projects. ProtoStream currently requires kapt. KSP support may be added in a future release.