Skip to content

[gRPC] Android duplicate classes with lite/protolite protobuf dependencies #716

@Jaakkko

Description

@Jaakkko

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

  1. Kotlin version
    2.3.10

  2. Gradle version
    9.4.0

  3. OS (Or at least KMP platform)
    macOS, Android target

  4. 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
  1. 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
    
  2. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions