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.
What You'll Need
- •Kotlin: Version 1.6 or higher
- •Gradle: For dependency management
- •Protocol Buffer Compiler: We'll configure this with Gradle
Step 1: Configure Gradle
Update your build.gradle.kts
:
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.
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
:
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
:
import com.example.person.* import java.io.File fun main() { // Create a Person using Kotlin DSL val person = person { name = "John Doe" id = 1234 email = "john.doe@example.com" 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 = "jane@example.com" 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: John Doe ID: 1234 Email: john.doe@example.com Serialized to 78 bytes Deserialized Person: Name: John Doe Active: true Saved to person.bin Read from file: John Doe Address book has 2 people
Kotlin-Specific Features
DSL Builders
val person = person { name = "John" id = 123 // Concise, type-safe builder }
Copy and Modify
val updated = person.copy { email = "newemail@example.com" }
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:
// 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:
val dataStore = context.createDataStore( fileName = "person.pb", serializer = PersonSerializer ) // Save dataStore.updateData { person } // Read val personFlow: Flow<Person> = dataStore.data
Best Practices for Kotlin
Use DSL Builders
Kotlin DSL builders make protobuf code more readable and idiomatic. Always prefer them over Java-style builders.
Leverage Coroutines
Use suspend
functions for I/O operations with protobuf data.
Use Lite for Android
Always use protobuf-javalite for Android apps to reduce APK size and improve performance.
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.