Describe the bug
Android projects that use kotlinx-rpc can run into duplicate-class failures when another library brings the Android lite/protolite protobuf stack.
Firebase Firestore is one concrete example of that broader problem. It is a useful minimal reproducer because the current kotlinx-rpc Android/JVM dependency graph brings full protobuf/gRPC artifacts while Firestore brings the Android lite/protolite protobuf stack.
The issue is reproducible in samples/grpc-kmp-app with a single dependency addition to shared/build.gradle.kts, but the underlying problem appears to be the general Android dependency shape rather than anything specific to that sample.
Observed conflict shape:
kotlinx-rpc brings grpc-protobuf, protobuf-java, protobuf-java-util, and proto-google-common-protos
- Firestore brings
grpc-protobuf-lite, protobuf-javalite, and protolite-well-known-types
- Android packaging fails because these artifacts contain overlapping
com.google.protobuf.* and com.google.api.* classes
To Reproduce
-
Kotlin version
2.3.10
-
Gradle version
9.4.0
-
OS (Or at least KMP platform)
macOS, Android target
-
Minimal reproducer in code
Start from samples/grpc-kmp-app as a minimal reproducer for the general Android compatibility issue.
Baseline:
./gradlew :androidApp:checkDebugDuplicateClasses
- result: success
Then add Firestore to shared/build.gradle.kts to introduce a representative lite/protolite protobuf dependency:
kotlin {
// ...
sourceSets {
androidMain.dependencies {
implementation("com.google.firebase:firebase-firestore:26.2.0")
}
commonMain.dependencies {
api(libs.kotlinx.rpc.grpc.core)
api(libs.kotlinx.rpc.protobuf.core)
}
}
}
Then run:
./gradlew :androidApp:checkDebugDuplicateClasses
-
Error description
The sample already contains this exclusion in androidApp/build.gradle.kts:
configurations.all {
exclude(group = "com.google.protobuf", module = "protobuf-javalite")
}
Even with that exclusion, the build fails with duplicate classes such as:
Duplicate class com.google.api.Advice found in modules
proto-google-common-protos-2.63.2.jar
and protolite-well-known-types-18.0.1.aar
-
Dependency paths
protobuf-java is brought in via:
com.google.protobuf:protobuf-java:4.34.0
\--- com.google.protobuf:protobuf-java-util:4.34.0
\--- org.jetbrains.kotlinx:kotlinx-rpc-protobuf-api-jvm:0.11.0-grpc-188
and also via:
com.google.protobuf:protobuf-java:3.25.8 -> 4.34.0
+--- com.google.api.grpc:proto-google-common-protos:2.63.2
| \--- io.grpc:grpc-protobuf:1.80.0
| \--- org.jetbrains.kotlinx:kotlinx-rpc-grpc-core-jvm:0.11.0-grpc-188
\--- io.grpc:grpc-protobuf:1.80.0
proto-google-common-protos is brought in via:
com.google.api.grpc:proto-google-common-protos:2.63.2
\--- io.grpc:grpc-protobuf:1.80.0
\--- org.jetbrains.kotlinx:kotlinx-rpc-grpc-core-jvm:0.11.0-grpc-188
Expected behavior
Adding Firestore to an Android project that uses kotlinx-rpc gRPC/protobuf artifacts should not immediately produce duplicate-class failures.
At minimum, one of these should be true:
kotlinx-rpc publishes an Android-safe dependency graph that can coexist with Firebase/Firestore
kotlinx-rpc documents a supported exclusion/alignment strategy for Android apps using Firestore
kotlinx-rpc avoids pulling full protobuf/common-protos artifacts into the Android runtime path when they are not required
Additional context
Things tried during investigation:
-
Forcing protobuf-javalite to Firestore-compatible versions did not fix the issue.
The failing duplicate set changed, but the build still failed.
-
Excluding only protobuf-java did not fix the issue.
The failure then surfaced as proto-google-common-protos vs protolite-well-known-types.
-
Excluding both protobuf-java and proto-google-common-protos did allow the sample to pass:
allprojects {
configurations.configureEach {
exclude(group = "com.google.protobuf", module = "protobuf-java")
exclude(group = "com.google.api.grpc", module = "proto-google-common-protos")
}
}
With those exclusions:
:androidApp:checkDebugDuplicateClasses succeeded
:androidApp:assembleDebug succeeded
-
However, that workaround still looks risky for general use because protobuf-java-util remains on the runtime classpath via kotlinx-rpc-protobuf-api-jvm.
-
In one limited unary-call path, generated request/response marshallers appeared to use the plain WireEncoder / WireDecoder path rather than protobuf-java-util, so that specific path looked plausibly safe.
But this does not prove safety for broader usage, especially with streaming calls, well-known types, or other protobuf utility-heavy features.
So the current state seems to be:
- the sample is a minimal reproducible example for the Android dependency conflict
- there is a build-level workaround using exclusions
- but the workaround is not clearly supported or guaranteed safe for all
kotlinx-rpc Android use cases
Describe the bug
Android projects that use
kotlinx-rpccan run into duplicate-class failures when another library brings the Android lite/protolite protobuf stack.Firebase Firestore is one concrete example of that broader problem. It is a useful minimal reproducer because the current
kotlinx-rpcAndroid/JVM dependency graph brings full protobuf/gRPC artifacts while Firestore brings the Android lite/protolite protobuf stack.The issue is reproducible in
samples/grpc-kmp-appwith a single dependency addition toshared/build.gradle.kts, but the underlying problem appears to be the general Android dependency shape rather than anything specific to that sample.Observed conflict shape:
kotlinx-rpcbringsgrpc-protobuf,protobuf-java,protobuf-java-util, andproto-google-common-protosgrpc-protobuf-lite,protobuf-javalite, andprotolite-well-known-typescom.google.protobuf.*andcom.google.api.*classesTo Reproduce
Kotlin version
2.3.10Gradle version
9.4.0OS (Or at least KMP platform)
macOS, Android targetMinimal reproducer in code
Start from
samples/grpc-kmp-appas a minimal reproducer for the general Android compatibility issue.Baseline:
./gradlew :androidApp:checkDebugDuplicateClassesThen add Firestore to
shared/build.gradle.ktsto introduce a representative lite/protolite protobuf dependency:kotlin { // ... sourceSets { androidMain.dependencies { implementation("com.google.firebase:firebase-firestore:26.2.0") } commonMain.dependencies { api(libs.kotlinx.rpc.grpc.core) api(libs.kotlinx.rpc.protobuf.core) } } }Then run:
Error description
The sample already contains this exclusion in
androidApp/build.gradle.kts:configurations.all { exclude(group = "com.google.protobuf", module = "protobuf-javalite") }Even with that exclusion, the build fails with duplicate classes such as:
Dependency paths
protobuf-javais brought in via:and also via:
proto-google-common-protosis brought in via:Expected behavior
Adding Firestore to an Android project that uses
kotlinx-rpcgRPC/protobuf artifacts should not immediately produce duplicate-class failures.At minimum, one of these should be true:
kotlinx-rpcpublishes an Android-safe dependency graph that can coexist with Firebase/Firestorekotlinx-rpcdocuments a supported exclusion/alignment strategy for Android apps using Firestorekotlinx-rpcavoids pulling full protobuf/common-protos artifacts into the Android runtime path when they are not requiredAdditional context
Things tried during investigation:
Forcing
protobuf-javaliteto Firestore-compatible versions did not fix the issue.The failing duplicate set changed, but the build still failed.
Excluding only
protobuf-javadid not fix the issue.The failure then surfaced as
proto-google-common-protosvsprotolite-well-known-types.Excluding both
protobuf-javaandproto-google-common-protosdid allow the sample to pass:allprojects { configurations.configureEach { exclude(group = "com.google.protobuf", module = "protobuf-java") exclude(group = "com.google.api.grpc", module = "proto-google-common-protos") } }With those exclusions:
:androidApp:checkDebugDuplicateClassessucceeded:androidApp:assembleDebugsucceededHowever, that workaround still looks risky for general use because
protobuf-java-utilremains on the runtime classpath viakotlinx-rpc-protobuf-api-jvm.In one limited unary-call path, generated request/response marshallers appeared to use the plain
WireEncoder/WireDecoderpath rather thanprotobuf-java-util, so that specific path looked plausibly safe.But this does not prove safety for broader usage, especially with streaming calls, well-known types, or other protobuf utility-heavy features.
So the current state seems to be:
kotlinx-rpcAndroid use cases