This projects implements client-side gRPC for Android, JVM, Native (including iOS), JavaScript and WASM.
JVM/Android | Native (including iOS) | JavaScript (Browser + NodeJs)) | WasmJs (Browser + NodeJs)) | |
---|---|---|---|---|
Unary |
âś… | âś… | âś… | âś… |
Client-streaming |
✅ | ✅ | ❌ | ❌ |
Server-streaming |
âś… | âś… | âś… | âś… |
Bidi-streaming |
✅ | ✅ | ❌ | ❌ |
Proto Type | Kotlin Type |
---|---|
double |
Double |
float |
Float |
int32 |
Int |
int64 |
Long |
uint32 |
UInt |
uint64 |
ULong |
sint32 |
Int |
sint64 |
Long |
fixed32 |
UInt |
fixed64 |
ULong |
sfixed32 |
Int |
sfixed64 |
Long |
bool |
Boolean |
string |
String |
bytes |
ByteArray |
Proto Option | Support Status |
---|---|
java_package |
âś… Supported |
java_outer_classname |
âś… Supported |
java_multiple_files |
âś… Supported |
deprecated |
âś… Supported |
packed |
âś… Supported |
optimize_for |
❌ Not Supported |
Proto Struct | Support Status |
---|---|
Messages | âś… Supported |
Enums | âś… Supported |
Services | âś… Supported |
For reference, see the official documentation. Well-known types support must be enabled in your gradle config (see Setup).
Protobuf Type | Supported |
---|---|
any.proto |
âś… Supported |
api.proto |
âś… Supported |
duration.proto |
âś… Supported |
empty.proto |
âś… Supported |
field_mask.proto |
âś… Supported |
source_context.proto |
âś… Supported |
struct.proto |
âś… Supported |
timestamp.proto |
âś… Supported |
type.proto |
âś… Supported |
wrappers.proto |
âś… Supported |
- âś… Generates DSL syntax to create messages
This plugin generates a kotlin class for each message/enum defined in the proto files. Assume you have the following proto file:
syntax = "proto3";
package com.example;
option java_multiple_files = true;
message HelloRequest {
string greeting = 1;
}
message HelloResponse {
string response = 1;
}
service HelloService {
rpc sayHello (HelloRequest) returns (HelloResponse);
rpc sayHelloMultipleTimes (HelloRequest) returns (stream HelloResponse);
}
In your common module, you can create proto objects like this:
val request = helloRequest {
greeting = "My greeting"
}
The request syntax is very similar to the one provided by gRPC Java. Add this code to your common module:
suspend fun makeCall(): String {
val channel = Channel.Builder()
.forAddress("localhost", 8082) // replace with your address and your port
.usePlaintext() // To force grpc to allow plaintext traffic, if you don't call this https is used.
.build()
// For each service a unique class is generated.
val stub = HelloServiceStub(channel)
val request = helloRequest {
greeting = "My greeting"
}
return try {
val response: HelloResponse = stub
.withDeadlineAfter(10, TimeUnit.SECONDS) // Specify a deadline if you need to
.myRpc(request)
//Handle response
response.response
} catch (e: StatusException) {
"An exception occurred: $e"
}
}
Wraps a message inside an Any
message.
val myMessage = myMessage {} // Some message
val wrapped = Any.wrap(myMessage)
Extracts a message from an Any
object.
val extractedMessage: MyMessage = wrapped.unwrap(MyMessage.Companion)
Checks if an Any
object holds a specific message type.
if (wrapped.isType(MyMessage.Companion)) {
println("The message is of type MyMessage")
}
Creates a Duration
from seconds.
val duration = Duration.ofSeconds(120)
Creates a Duration
from milliseconds.
val duration = Duration.ofMillis(500)
Creates a Duration
from kotlin.time.Duration
.
val duration = Duration.fromDuration(1.5.seconds)
Converts a Duration
to kotlin.time.Duration
.
val kotlinDuration: kotlin.time.Duration = duration.toDuration()
Creates a Timestamp
from Instant
.
val timestamp = Timestamp.fromInstant(Instant.now())
Converts a Timestamp
to Instant
.
val instant: Instant = timestamp.toInstant()
Unknown fields are automatically captured when parsing messages and also serialized back to the wire. You can access them using the generated property:
class ExampleMessage {
val unknownFields: List<UnknownField>
}
You can intercept calls to modify what is sent to the server and received from the server. Example:
val loggingInterceptor = object : CallInterceptor {
override fun onStart(methodDescriptor: MethodDescriptor, metadata: Metadata): Metadata {
println("Call started ${methodDescriptor.fullMethodName}")
return super.onStart(methodDescriptor, metadata)
}
override fun onClose(
methodDescriptor: MethodDescriptor,
status: Status,
trailers: Metadata
): Pair<Status, Metadata> {
println("Call closed ${methodDescriptor.fullMethodName}")
return super.onClose(methodDescriptor, status, trailers)
}
}
val channel = Channel.Builder
.forAddress("localhost", 8080)
.withInterceptors(loggingInterceptor)
.build()
In your top-level build.gradle.kts, add the following:
buildscript {
repositories {
//...
gradlePluginPortal()
}
// ...
}
Add the following to your plugins block:
plugins {
kotlin("multiplatform")
//...
id("io.github.timortel.kmpgrpc.plugin") version "<latest version>"
}
Add the library as a dependency:
repositories {
// ...
mavenCentral()
}
kotlin {
// Required
applyDefaultHierarchyTemplate()
// ...
}
kmpGrpc {
// declare the targets you need.
common() // required
jvm()
android()
js()
native() // for native targets like iOS
// Optional: if the protobuf well known types should be included
// https://protobuf.dev/reference/protobuf/google.protobuf/
includeWellKnownTypes = true
// Specify the folders where your proto files are located, you can list multiple.
protoSourceFolders = project.files("<source to your protos>")
}
See an example implementation of an Android app and an iOS app in the examples
folder.
To build the native targets locally, you will need to have rust installed on your local machine. Once setup, you can run the following Gradle commands:
- To build the library
gradle kmp-grpc-core:publishToMavenLocal
- To build the plugin
gradle kmp-grpc-plugin:publishToMavenLocal
By default, kmp-grpc-core prints trace logs. To deactivate, build the library with -Pio.github.timortel.kmp-grpc.internal.native.release=true
.
Feel free to implement improvements, bug fixes and features and create a pull request.
The plugin generates kotlin code for all provided proto files. No protoc
is needed. The networking code is handled
by gRPC for JVM and by tonic for all native targets. For JavaScript, the requests are handled by ktor.
Copyright 2025 Tim Ortel
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.