1. Introduction

gRPC is an open-source RPC (Remote Procedure Call) platform developed by Google that provides highly performant and efficient communication in any kind of environment and across data centers. Moreover, gRPC’s pluggable support of load balancing, tracing, health check, and authentication makes it a good candidate to be used in distributed computing and microservices.

With gRPC, a client application calls a method on a server application using generated stubs that hide the complexity of distributed applications and services.

In this tutorial, we’ll explore gRPC components, and we’ll see how to implement a gRPC server and client in Kotlin.

2. Components

gRPC starts with a service definition. A service definition is an interface for the service and contains methods, parameters, and expected return types.

Then, based on the defined service interface, the clients use a stub to call the server. On the other side, the server implements the service for the interface and runs a gRPC server to handle client requests.

gRPC by default uses Protobuf as the interface description language to describe service definition and payload. However, we could use other libraries like Gson for encoding and decoding instead of Protobuf.

Now that we have some understanding of how gRPC works, let’s take a look at the implementation.

3. Dependencies

Let’s start with adding the dependencies for gRPC to our POM:

<dependency>
    <groupId>io.grpc</groupId>
    <artifactId>grpc-netty</artifactId>
    <version>1.46.0</version>
</dependency>
<dependency>
    <groupId>io.grpc</groupId>
    <artifactId>grpc-protobuf</artifactId>
    <version>1.53.0</version>
</dependency>
<dependency>
    <groupId>io.grpc</groupId>
    <artifactId>grpc-stub</artifactId>
    <version>1.46.0</version>
</dependency>
<dependency>
    <groupId>io.grpc</groupId>
    <artifactId>grpc-kotlin-stub</artifactId>
    <version>1.2.0</version>
</dependency>
<dependency>
    <groupId>com.google.protobuf</groupId>
    <artifactId>protobuf-kotlin</artifactId>
    <version>3.18.1</version>
</dependency>
<dependency>
    <groupId>com.google.protobuf</groupId>
    <artifactId>protobuf-java</artifactId>
    <version>3.18.1</version> 
</dependency>

4. Proto Files

Let’s now define our service in a .proto file. For our tutorial, we’ll be using the same service definition we used before with our Java tutorial:

syntax = "proto3";

package com.baeldung.grpc.helloworld;

option java_multiple_files = true;

service HelloService {
  rpc hello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

To use these proto files in our code, we have to create stubs of them. To achieve this goal, we’ll use the protocol buffer compiler protoc:

protoc --plugin=protoc-gen-grpc-java=build/exe/java_plugin/protoc-gen-grpc-java \
  --grpc-java_out="$OUTPUT_FILE" --proto_path="$DIR_OF_PROTO_FILE" "$PROTO_FILE"

Moreover, we have an easier option – to embed this stub creation step in our pom.xml as we’ll see below.

5. Maven Plugin

Now, let’s add the org.xolstice.maven.plugins:protobuf-maven-plugin plugin to our pom.xml so that we can create requests, responses, and stubs from the proto files when executing the Maven compile goal.

This makes use of the grpc-kotlin implementation:

<plugin>
    <groupId>org.xolstice.maven.plugins</groupId>
    <artifactId>protobuf-maven-plugin</artifactId>
    <version>0.6.1</version>
    <executions>
        <execution>
            <id>compile</id>
            <goals>
                <goal>compile</goal>
            </goals>
            <configuration>
                <protocArtifact>com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}</protocArtifact>
                <pluginId>grpc-java</pluginId>
                <pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
                <protocPlugins>
                    <protocPlugin>
                        <id>grpc-kotlin</id>
                        <groupId>io.grpc</groupId>
                        <artifactId>protoc-gen-grpc-kotlin</artifactId>
                        <version>${grpc.kotlin.version}</version>
                        <classifier>jdk7</classifier>
                        <mainClass>io.grpc.kotlin.generator.GeneratorRunner</mainClass>
                    </protocPlugin>
                </protocPlugins>
            </configuration>
        </execution>
    </executions>
</plugin>

6. Server Implementation

To implement the functionality of the service, let’s start by overriding HelloServiceCoroutineImplBase:

class HelloService : HelloServiceGrpcKt.HelloServiceCoroutineImplBase() {
    override suspend fun hello(request: HelloRequest): HelloReply {
        return HelloReply.newBuilder()
            .setMessage("Hello, ${request.name}")
            .build()
    }
}

Then, we can start the gRPC server with HelloService:

fun helloServer() {
    val helloService = HelloService()
    val server = ServerBuilder
        .forPort(15001)
        .addService(helloService)
        .build()

    Runtime.getRuntime().addShutdownHook(Thread {
        server.shutdown()
        server.awaitTermination()
    })

    server.start()
    server.awaitTermination()
}

fun main(args: Array<String>) {
    helloServer()
}

7. Client Implementation

To set up the client, let’s build the ManagedChannel:

val channel = ManagedChannelBuilder.forAddress("localhost", 15001)
    .usePlaintext()
    .build()

By using the gRPC channel, the client connects to a gRPC server on a specified host and port.

Now, let’s create the stub:

val stub = HelloServiceGrpc.newBlockingStub(channel)

Finally, let’s create the request and call the server:

val response = stub.hello(HelloRequest.newBuilder().setName("Baeldung").build())

Here, we create a blocking stub, but there are also options for future or async stubs.

We could call a future stub to get the response as com.google.common.util.concurrent.ListenableFuture. Then, we’d have the possibility to cancel the call or get the response with a timeout:

HelloServiceGrpc.newFutureStub(channel)

Additionally, we could create an asynchronous stub and get the response in a reactive manner in the callback:

HelloServiceGrpc.newStub(channel).hello(
    HelloRequest.newBuilder().setName("Baeldung").build(), object: StreamObserver<HelloReply> {
        override fun onNext(response: HelloReply?) {
            //consume response
        }

        override fun onError(throwable: Throwable?) {
            //handle error
        }

        override fun onCompleted() {
            //on complete
        }
    }
)

8. Conclusion

In this article, we looked at gRPC and how we could implement it on the server and client-side with Kotlin.

As usual, the complete source code of this article is available over on GitHub.

Comments are closed on this article!