Kotlin and Protocol Buffers work excellently together, especially for Android development. Kotlin's concise syntax and null safety combine well with Protobuf's efficient serialization, making them ideal for mobile apps and backend services.
This guide shows you how to use Protocol Buffers in Kotlin projects, covering both Android and JVM applications. If you're new to Protocol Buffers, check out our Protobuf vs JSON comparison first.
What You'll Need
- •Kotlin: Version 1.6 or higher (download from kotlinlang.org)
- •Gradle: For dependency management (or Maven)
- •Protocol Buffer Compiler: We'll configure this with Gradle (see protobuf-gradle-plugin)
Step 1: Configure Gradle
Update your build.gradle.kts with the protobuf-gradle-plugin:
plugins {
kotlin("jvm") version "1.9.0"
id("com.google.protobuf") version "0.9.4"
}
dependencies {
implementation("com.google.protobuf:protobuf-kotlin:3.25.0")
implementation("com.google.protobuf:protobuf-java:3.25.0")
}
protobuf {
protoc {
artifact = "com.google.protobuf:protoc:3.25.0"
}
generateProtoTasks {
all().forEach { task ->
task.builtins {
create("kotlin")
}
}
}
}For Android projects, use protobuf-javalite instead for smaller APK size. See the Android DataStore documentation for more details.
Step 2: Set Up Project Structure
kotlin-protobuf-example/ ├── build.gradle.kts ├── src/ │ └── main/ │ ├── proto/ │ │ └── person.proto │ └── kotlin/ │ └── Main.kt
Step 3: Create Your .proto File
Create src/main/proto/person.proto. Learn more about proto3 syntax in the official language guide:
syntax = "proto3";
package person;
option java_multiple_files = true;
option java_package = "com.example.person";
message Person {
string name = 1;
int32 id = 2;
string email = 3;
repeated string phone_numbers = 4;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
string number = 1;
PhoneType type = 2;
}
repeated PhoneNumber phones = 5;
bool is_active = 6;
}
message AddressBook {
repeated Person people = 1;
}Step 4: Use Protocol Buffers in Kotlin
Create src/main/kotlin/Main.kt. The Kotlin DSL provides a more idiomatic way to work with protobuf compared to Java's builder pattern:
import com.example.person.*
import java.io.File
fun main() {
// Create a Person using Kotlin DSL
val person = person {
name = "Maria Garcia"
id = 1234
email = "[email protected]"
phoneNumbers.addAll(listOf("555-1234", "555-5678"))
phones.add(
phoneNumber {
number = "555-9999"
type = Person.PhoneType.MOBILE
}
)
isActive = true
}
println("Created Person:")
println("Name: ${person.name}")
println("ID: ${person.id}")
println("Email: ${person.email}")
// Serialize to bytes
val bytes = person.toByteArray()
println("\nSerialized to ${bytes.size} bytes")
// Deserialize from bytes
val decoded = Person.parseFrom(bytes)
println("\nDeserialized Person:")
println("Name: ${decoded.name}")
println("Active: ${decoded.isActive}")
// Save to file
File("person.bin").writeBytes(bytes)
println("\nSaved to person.bin")
// Read from file
val fileData = File("person.bin").readBytes()
val fromFile = Person.parseFrom(fileData)
println("Read from file: ${fromFile.name}")
// Create AddressBook
val addressBook = addressBook {
people.add(person)
people.add(
person {
name = "Jane Smith"
id = 5678
email = "[email protected]"
isActive = true
}
)
}
println("\nAddress book has ${addressBook.peopleCount} people")
// Use when expression with sealed enum
person.phonesList.forEach { phone ->
val typeStr = when (phone.type) {
Person.PhoneType.MOBILE -> "Mobile"
Person.PhoneType.HOME -> "Home"
Person.PhoneType.WORK -> "Work"
else -> "Unknown"
}
println("Phone: ${phone.number} ($typeStr)")
}
}Step 5: Build and Run
./gradlew build ./gradlew run
Expected output:
Created Person: Name: Maria Garcia ID: 1234 Email: [email protected] Serialized to 78 bytes Deserialized Person: Name: Maria Garcia Active: true Saved to person.bin Read from file: Maria Garcia Address book has 2 people
Kotlin-Specific Features
The protobuf-kotlin library provides several Kotlin-friendly features:
DSL Builders
val person = person {
name = "Maria"
id = 123
// Concise, type-safe builder
}Copy and Modify
val updated = person.copy {
email = "[email protected]"
}Null Safety
// Kotlin handles protobuf fields safely val email: String = person.email // Never null val hasEmail = person.email.isNotEmpty()
Extension Functions
fun Person.displayName() = "ID: $id - $name" println(person.displayName())
Using in Android
For Android, use the Lite version for smaller APK size. Learn more in the Android DataStore guide:
// In build.gradle.kts (app module)
dependencies {
implementation("com.google.protobuf:protobuf-javalite:3.25.0")
}
protobuf {
protoc {
artifact = "com.google.protobuf:protoc:3.25.0"
}
generateProtoTasks {
all().forEach { task ->
task.builtins {
create("java") {
option("lite")
}
create("kotlin") {
option("lite")
}
}
}
}
}Use with DataStore for type-safe persistent storage:
val dataStore = context.createDataStore(
fileName = "person.pb",
serializer = PersonSerializer
)
// Save
dataStore.updateData { person }
// Read
val personFlow: Flow<Person> = dataStore.dataThis is much more efficient than using JSON with Kotlin data classes for Android app data.
Best Practices for Kotlin
Follow these Protobuf API best practices and Kotlin-specific guidelines:
Use DSL Builders
Kotlin DSL builders make protobuf code more readable and idiomatic. Always prefer them over Java-style builders. See more in our best practices guide.
Leverage Coroutines
Use suspend functions for I/O operations with protobuf data. Learn more about Kotlin coroutines.
Use Lite for Android
Always use protobuf-javalite for Android apps to reduce APK size and improve performance. Check out our performance optimization guide.
Common Issues
Issue: Protobuf plugin not found
Solution: Add the plugin to your root build.gradle.kts:id("com.google.protobuf") version "0.9.4" apply false
Issue: Generated files not found
Solution: Run ./gradlew generateProto to generate code manually.
Issue: DSL builders not available
Solution: Make sure you included protobuf-kotlin dependency and enabled Kotlin generation in the protobuf plugin.
Related Tools
Conclusion
Protocol Buffers with Kotlin offers a great developer experience. The Kotlin DSL makes the code readable and concise, while Protobuf provides efficient serialization. This combination is especially powerful for Android development.
Whether you're building mobile apps or backend services, Kotlin and Protobuf give you type safety, performance, and modern language features all in one package.
Want to explore more? Try our JSON to Protobuf converter to convert existing data schemas, or read about using Protobuf with gRPC for building efficient APIs.