A Guide to gRPC With Java

gRPC is a high-performance, open-source RPC (Remote Procedure Call) framework created by Google. It’s designed for efficient communication between distributed systems and microservices. In this post, I will cover the common uses of gPRC.

Getting started with gRPC

In this section, I’m gonna to use the hello-world demo to show how to develop a gRPC application. The process of developing a gPRC application:

  1. Creating and initializing a gRPC project with Java.
  2. Creating a service interface definition using protocol buffers.
  3. Generating server-side (server skeleton) and client-side (client stub) code for the programming language of your choice, which simplifies the server- and client-side logic by providing the low-level communication abstractions.
  4. Implementing a service’s business logic.
  5. Running a gRPC server with the service you implemented.
  6. Invoking the service through the gRPC client application.

Creating and initializing a gRPC project with Java

Using Gradle

1. Create a Java application with the gradle init command.

$ mkdir grpc-demo && cd grpc-demo
$ gradle --configuration-cache \
init --use-defaults \
--type java-application \
--package com.taogen.grpc

2. Gradle configuration

1) Add plugins

  • protobuf

2) Add dependencies

  • grpc-related dependencies
  • protobuf-java

3) Add protobuf configurations

The following is the complete Gradle configuration.

app/build.gradle.kts

import com.google.protobuf.gradle.id

plugins {
// Apply the application plugin to add support for building a CLI application in Java.
application
// Add protobuf plugin
id("com.google.protobuf") version "0.9.5"
}

object Versions {
const val grpc = "1.76.0"
const val protobuf = "4.33.0"
}

repositories {
// Use Maven Central for resolving dependencies.
mavenCentral()
}

dependencies {
// Use JUnit Jupiter for testing.
testImplementation(libs.junit.jupiter)
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
// This dependency is used by the application.
implementation(libs.guava)
// grpc
implementation("io.grpc:grpc-netty:${Versions.grpc}")
implementation("io.grpc:grpc-protobuf:${Versions.grpc}")
implementation("io.grpc:grpc-stub:${Versions.grpc}")
// protobuf
implementation("com.google.protobuf:protobuf-java:${Versions.protobuf}")
}

// Apply a specific Java toolchain to ease working on different environments.
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}

application {
// Define the main class for the application.
mainClass = "com.taogen.grpc.App"
}

/**
* Customizing Protobuf compilation
*/
protobuf {
// Locate external executables
// By default, the plugin will search for the protoc executable in the system search path. We recommend you to take the advantage of pre-compiled protoc that published on Maven Central.
protoc {
artifact = "com.google.protobuf:protoc:${Versions.protobuf}"
}

// Locate the codegen plugins
plugins {
// Locate a plugin with name 'grpc'.
id("grpc") {
artifact = "io.grpc:protoc-gen-grpc-java:${Versions.grpc}"
}
}
// Customize code generation tasks
generateProtoTasks {
all().forEach {
it.builtins {
java {
// Optionally specify options for the builtin java codegen.
// This yields "--java_out=example_option1=true,example_option2:/path/to/output"
}
}
it.plugins {
id("grpc") {
// Add grpc output without any option. grpc must have been defined in the protobuf.plugins block.
// This yields "--grpc_out=/path/to/output" on the protoc commandline.
}
}
}
}
}

tasks.named<Test>("test") {
// Use JUnit Platform for unit tests.
useJUnitPlatform()
}

4) Custom protobuf configuration

See the Appendixes at the end of this post.

Using Maven

1. Create a Java project with the following Maven command line

mvn -B archetype:generate \
-DgroupId=com.taogen.grpc \
-DartifactId=grpc-demo \
-DarchetypeArtifactId=maven-archetype-quickstart \
-DjavaCompilerVersion=21 \
-DarchetypeVersion=1.5

2. Maven configuration

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.taogen.grpc</groupId>
<artifactId>grpc-demo</artifactId>
<version>1.0-SNAPSHOT</version>

<name>grpc-demo</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.release>21</maven.compiler.release>
<protobuf-java.version>4.33.0</protobuf-java.version>
<grpc.version>1.76.0</grpc.version>
<reactor-grpc.version>1.2.4</reactor-grpc.version>
</properties>

<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.11.0</version>
<scope>test</scope>
</dependency>
<!-- grpc -->
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>${grpc.version}</version>
</dependency>
<!-- protobuf -->
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>${protobuf-java.version}</version>
</dependency>
<!-- reactor-grpc -->
<dependency>
<groupId>com.salesforce.servicelibs</groupId>
<artifactId>reactor-grpc-stub</artifactId>
<version>${reactor-grpc.version}</version>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<version>3.8.0</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>io.github.ascopes</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<configuration>
<protoc>${protobuf-java.version}</protoc>

<!-- Vanilla protoc plugins - these are platform-specific executables
just like protoc is. -->
<binaryMavenPlugins>
<binaryMavenPlugin>
<groupId>io.grpc</groupId>
<artifactId>protoc-gen-grpc-java</artifactId>
<version>${grpc.version}</version>
</binaryMavenPlugin>
</binaryMavenPlugins>

<!-- JVM plugins are distributed as JARs rather than native system
executables. The protobuf-maven-plugin will automatically bootstrap
them for you, without the need for additional
tooling for your platform. It should "just work". -->
<jvmMavenPlugins>
<jvmMavenPlugin>
<groupId>com.salesforce.servicelibs</groupId>
<artifactId>reactor-grpc</artifactId>
<version>${reactor-grpc.version}</version>
</jvmMavenPlugin>
</jvmMavenPlugins>
</configuration>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

Creating a service interface definition using protocol buffers

Add the directory for protobuf

# Gradle
mkdir -p app/src/main/proto
# Maven
mkdir -p src/main/protobuf

Add the proto file

File Path

  • Using Gradle: app/src/main/proto/hello_world.proto
  • Using Maven: src/main/protobuf/hello_world.proto
syntax = "proto3";

option java_multiple_files = true;

package com.taogen.grpc;

service HelloService {
rpc hello(HelloRequest) returns (HelloResponse);
}

message HelloRequest {
string firstName = 1;
string lastName = 2;
}

message HelloResponse {
string greeting = 1;
}
  • option java_multiple_files = true: By default, the compiler generates all the Java code in a single Java file. Setting the option java_multiple_files to true, the gRPC base code will be generated in individual files.

Generating server-side (server skeleton) and client-side (client stub) code

Using Gradle

Running the following command line to generate gRPC base code using the protobuf plugin protobuf-gradle-plugin.

./gradlew generateProto
# or
./gradlew build

The directory of generated code is app/build/generated/sources/proto.

Using Maven

Running the following command line to generate gRPC base code using the protobuf plugin io.github.ascopes:protobuf-maven-plugin.

mvn protobuf:generate
# or
mvn clean package

The directory of generated code is target/generated-sources.

Implementing a service’s business logic

Override the methods of HelloServiceGrpc.HelloServiceImplBase.

com/taogen/grpc/service/HelloServiceImpl.java

package com.taogen.grpc.service;

import com.taogen.grpc.HelloRequest;
import com.taogen.grpc.HelloResponse;
import com.taogen.grpc.HelloServiceGrpc;
import io.grpc.stub.StreamObserver;

/**
* @author taogen
*/
public class HelloServiceImpl extends HelloServiceGrpc.HelloServiceImplBase {
@Override
public void hello(
HelloRequest request, StreamObserver<HelloResponse> responseObserver) {

String greeting = new StringBuilder()
.append("Hello, ")
.append(request.getFirstName())
.append(" ")
.append(request.getLastName())
.toString();

HelloResponse response = HelloResponse.newBuilder()
.setGreeting(greeting)
.build();

responseObserver.onNext(response);
responseObserver.onCompleted();
}
}

Running a gRPC server with the service you implemented

Add your services to the gRPC server and start the server.

com/taogen/grpc/GrpcServer.java

package com.taogen.grpc;

import com.taogen.grpc.service.HelloServiceImpl;
import io.grpc.Server;
import io.grpc.ServerBuilder;

import java.io.IOException;

/**
* @author taogen
*/
public class GrpcServer {
public static void main(String[] args) throws IOException, InterruptedException {
Server server = ServerBuilder
.forPort(8080)
.addService(new HelloServiceImpl())
.build();

server.start();
System.out.println("gRPC server started on port 8080");
server.awaitTermination();
}
}

Running the main() method to start the gRPC server.

Invoking the service through the gRPC client application

com/taogen/grpc/GrpcClient.java

package com.taogen.grpc;

import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;

/**
* @author taogen
*/
public class GrpcClient {
public static void main(String[] args) {
ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 8080)
.usePlaintext()
.build();

HelloServiceGrpc.HelloServiceBlockingStub stub
= HelloServiceGrpc.newBlockingStub(channel);

HelloResponse helloResponse = stub.hello(HelloRequest.newBuilder()
.setFirstName("Thomas")
.setLastName("Jones")
.build());
System.out.println("Response: " + helloResponse.getGreeting());

channel.shutdown();
}
}

Running the main() method to see the message from the gRPC server response.

Response: Hello, Thomas Jones

Streaming

Server-side streaming

In simple RPC you always had a single request and single response in the communication between the gRPC server and gRPC client. In server-side streaming RPC, the server sends back a sequence of responses after getting the client’s request message. This sequence of multiple responses is known as a “stream.” After sending all the server responses, the server marks the end of the stream by sending the server’s status details as trailing metadata to the client.

1. Create the protobuf file

user_info.proto

syntax = "proto3";

import "google/protobuf/wrappers.proto";

option java_multiple_files = true;

package com.taogen.grpc;

service UserService {
rpc searchUsers(google.protobuf.StringValue) returns (stream User);
}

message User {
int32 id = 1;
string name = 2;
int32 age = 3;
string gender = 4;
}
  • import "google/protobuf/wrappers.proto": wrappers.proto is a standard protobuf file provided by Google. It defines wrapper message types for primitive values (like int32, bool, string).

2. Generate new gRPC base code

./gradlew build

3. Implementing the service’s business logic

com/taogen/grpc/service/UserServiceImpl.java

package com.taogen.grpc.service;

import com.google.protobuf.StringValue;
import com.taogen.grpc.User;
import com.taogen.grpc.UserServiceGrpc;
import io.grpc.stub.StreamObserver;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
* @author taogen
*/
public class UserServiceImpl extends UserServiceGrpc.UserServiceImplBase {
private static final Map<Integer, User> userMap = new HashMap<>();
static {
userMap.put(1, User.newBuilder().setId(1).setName("Alice").setAge(18).setGender("Female").build());
userMap.put(2, User.newBuilder().setId(2).setName("Bob").setAge(20).setGender("Male").build());
userMap.put(3, User.newBuilder().setId(3).setName("Cathy").setAge(19).setGender("Female").build());
userMap.put(4, User.newBuilder().setId(4).setName("Alice").setAge(18).setGender("Female").build());
userMap.put(5, User.newBuilder().setId(5).setName("Alice").setAge(18).setGender("Female").build());
}

@Override
public void searchUsers(StringValue request, StreamObserver<User> responseObserver) {
userMap.values().stream()
.filter(user -> user.getName().equals(request.getValue()))
.forEach(user -> {
responseObserver.onNext(user); // Stream each matching user
try {
Thread.sleep(3000); // Simulate delay
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
responseObserver.onCompleted();
}
}

Server-side streaming response to user list data:

users.forEach(user -> {
responseObserver.onNext(user);
});

4. Start the gRPC server

Add the UserServiceImpl instance to gRPC server and restart the gRPC server.

com/taogen/grpc/GrpcServer.java

Server server = ServerBuilder
.addService(new UserServiceImpl())
...

It’s similar code in the previous section “Running a gRPC server with the service you implemented”.

5. Call the service method in the client

com/taogen/grpc/client/UserServiceClient.java

package com.taogen.grpc.client;

import com.google.protobuf.StringValue;
import com.taogen.grpc.HelloRequest;
import com.taogen.grpc.HelloResponse;
import com.taogen.grpc.HelloServiceGrpc;
import com.taogen.grpc.UserServiceGrpc;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;

/**
* @author taogen
*/
public class UserServiceClient {

public static void main(String[] args) {
ManagedChannel channel = ManagedChannelBuilder
.forAddress("localhost", 8080)
.usePlaintext()
.build();
UserServiceGrpc.UserServiceBlockingStub stub = UserServiceGrpc.newBlockingStub(channel);

stub.searchUsers(StringValue.newBuilder().setValue("Alice").build())
.forEachRemaining(userResponse -> {
System.out.println("User ID: " + userResponse.getId());
System.out.println("User Name: " + userResponse.getName());
System.out.println("User Age: " + userResponse.getAge());
System.out.println("User Gender: " + userResponse.getGender());
System.out.println("-----");
});

channel.shutdown();
}
}

Client-side streaming

1. Create the protobuf file

user_info.proto

syntax = "proto3";

import "google/protobuf/wrappers.proto";

option java_multiple_files = true;

package com.taogen.grpc;

service UserService {
rpc addUsers(stream User) returns (google.protobuf.StringValue);
}

message User {
int32 id = 1;
string name = 2;
int32 age = 3;
string gender = 4;
}

2. Generate new gRPC base code

./gradlew build

3. Implementing the service’s business logic

UserServiceImpl.java

package com.taogen.grpc.service;

import com.google.protobuf.StringValue;
import com.taogen.grpc.User;
import com.taogen.grpc.UserServiceGrpc;
import io.grpc.stub.StreamObserver;

import java.util.*;
import java.util.stream.Collectors;

/**
* @author taogen
*/
public class UserServiceImpl extends UserServiceGrpc.UserServiceImplBase {
private static final Map<Integer, User> userMap = new HashMap<>();

static {
userMap.put(1, User.newBuilder().setId(1).setName("Alice").setAge(18).setGender("Female").build());
userMap.put(2, User.newBuilder().setId(2).setName("Bob").setAge(20).setGender("Male").build());
userMap.put(3, User.newBuilder().setId(3).setName("Cathy").setAge(19).setGender("Female").build());
userMap.put(4, User.newBuilder().setId(4).setName("Alice").setAge(18).setGender("Female").build());
userMap.put(5, User.newBuilder().setId(5).setName("Alice").setAge(18).setGender("Female").build());
}

@Override
public StreamObserver<User> addUsers(StreamObserver<StringValue> responseObserver) {
return new StreamObserver<>() {
List<Integer> ids = new ArrayList<>();

@Override
public void onNext(User value) {
System.out.println("Received user: " + value);
int maxId = userMap.keySet().stream().mapToInt(Integer::intValue).max().orElse(0);
int id = maxId + 1;
ids.add(id);
User newUser = User.newBuilder()
.setId(id)
.setName(value.getName())
.setAge(value.getAge())
.setGender(value.getGender())
.build();
userMap.put(id, newUser);
}

@Override
public void onError(Throwable t) {
System.err.println("Add users failed: " + t);
}

@Override
public void onCompleted() {
System.out.println("All users added.");
StringValue response = StringValue.newBuilder()
.setValue(ids.stream()
.map(String::valueOf)
.collect(Collectors.joining(",")))
.build();
responseObserver.onNext(response);
responseObserver.onCompleted();
}
};
}
}

4. Start the gRPC server

Add the UserServiceImpl instance to gRPC server and restart the gRPC server.

com/taogen/grpc/GrpcServer.java

Server server = ServerBuilder
.addService(new UserServiceImpl())
...

It’s similar code in the previous section “Running a gRPC server with the service you implemented”.

5. Call the service method in the client

com/taogen/grpc/client/UserServiceClient2.java

package com.taogen.grpc.client;

import com.google.protobuf.StringValue;
import com.taogen.grpc.User;
import com.taogen.grpc.UserServiceGrpc;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.stub.StreamObserver;

/**
* @author taogen
*/
public class UserServiceClient2 {

public static void main(String[] args) throws InterruptedException {
ManagedChannel channel = ManagedChannelBuilder
.forAddress("localhost", 8080)
.usePlaintext()
.build();

UserServiceGrpc.UserServiceStub stub = UserServiceGrpc.newStub(channel);

StreamObserver<StringValue> requestObserver = new StreamObserver<StringValue>() {

@Override
public void onNext(StringValue value) {
System.out.println(value.getValue());
}

@Override
public void onError(Throwable t) {
System.err.println("Error from server: " + t);
}

@Override
public void onCompleted() {
System.out.println("Stream completed.");
}
};

// Begin streaming
StreamObserver<User> userStreamObserver = stub.addUsers(requestObserver);

// Send several messages
for (int i = 0; i < 3; i++) {
User user = User.newBuilder()
.setName("User" + i)
.setAge(20 + i)
.setGender(i % 2 == 0 ? "Male" : "Female")
.build();
userStreamObserver.onNext(user);
Thread.sleep(3000); // Simulate delay
}
// Tell the server we're done
userStreamObserver.onCompleted();

Thread.sleep(3000); // Wait for async request to complete
channel.shutdown();
}
}

Client-side streaming:

  1. uses UserServiceGrpc.UserServiceStub rather than UserServiceGrpc.UserServiceBlockingStub.
  2. Send users one by one.
    for (User user: users) {
    userStreamObserver.onNext(user);
    }

Bidirectional streaming

Bidirectional streaming combines server-side streaming and client-side streaming.

1. Create the protobuf file

user_info.proto

syntax = "proto3";  

option java_multiple_files = true;

package com.taogen.grpc;

service UserInfo {
rpc updateUsers(stream User) returns (stream UpdateStatus);
}

message User {
int32 id = 1;
string name = 2;
int32 age = 3;
string gender = 4;
}

message UpdateStatus {
int32 id = 1;
string status = 2;
string message = 3;
}

2. Generate new gRPC base code

./gradlew build

3. Implementing the service’s business logic

com/taogen/grpc/service/UserServiceImpl.java

package com.taogen.grpc.service;

import com.google.protobuf.StringValue;
import com.taogen.grpc.UpdateStatus;
import com.taogen.grpc.User;
import com.taogen.grpc.UserServiceGrpc;
import io.grpc.stub.StreamObserver;

import java.util.*;
import java.util.stream.Collectors;

/**
*
* @author taogen
*/
public class UserServiceImpl extends UserServiceGrpc.UserServiceImplBase {
private static final Map<Integer, User> userMap = new HashMap<>();

static {
userMap.put(1, User.newBuilder().setId(1).setName("Alice").setAge(18).setGender("Female").build());
userMap.put(2, User.newBuilder().setId(2).setName("Bob").setAge(20).setGender("Male").build());
userMap.put(3, User.newBuilder().setId(3).setName("Cathy").setAge(19).setGender("Female").build());
userMap.put(4, User.newBuilder().setId(4).setName("Alice").setAge(18).setGender("Female").build());
userMap.put(5, User.newBuilder().setId(5).setName("Alice").setAge(18).setGender("Female").build());
}

@Override
public StreamObserver<User> updateUsers(StreamObserver<UpdateStatus> responseObserver) {
return new StreamObserver<>() {
List<UpdateStatus> updateStatusList = new ArrayList<>();
@Override
public void onNext(User value) {
System.out.println("Received user: " + value);
if (userMap.containsKey(value.getId())) {
userMap.put(value.getId(), value);
updateStatusList.add(UpdateStatus.newBuilder()
.setId(value.getId())
.setStatus("OK")
.setMessage("Update successfully")
.build());
} else {
updateStatusList.add(UpdateStatus.newBuilder()
.setId(value.getId())
.setStatus("Error")
.setMessage("User not found")
.build());
}
}

@Override
public void onError(Throwable t) {
System.err.println("Update users failed: " + t);
}

@Override
public void onCompleted() {
System.out.println("All users updated.");
for (UpdateStatus updateStatus : updateStatusList) {
responseObserver.onNext(updateStatus);
}
responseObserver.onCompleted();
}
};
}
}

4. Start the gRPC server

Add the UserServiceImpl instance to gRPC server and restart the gRPC server.

com/taogen/grpc/GrpcServer.java

Server server = ServerBuilder
.addService(new UserServiceImpl())
...

It’s similar code in the previous section “Running a gRPC server with the service you implemented”.

5. Call the service method in the client

com/taogen/grpc/client/UserServiceClient3.java

package com.taogen.grpc.client;

import com.taogen.grpc.UpdateStatus;
import com.taogen.grpc.User;
import com.taogen.grpc.UserServiceGrpc;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.stub.StreamObserver;

/**
*
* @author taogen
*/
public class UserServiceClient3 {

public static void main(String[] args) throws InterruptedException {
ManagedChannel channel = ManagedChannelBuilder
.forAddress("localhost", 8080)
.usePlaintext()
.build();

UserServiceGrpc.UserServiceStub stub = UserServiceGrpc.newStub(channel);

StreamObserver<UpdateStatus> requestObserver = new StreamObserver<UpdateStatus>() {

@Override
public void onNext(UpdateStatus value) {
System.out.println(value.toString());
}

@Override
public void onError(Throwable t) {
System.err.println("Error from server: " + t);
}

@Override
public void onCompleted() {
System.out.println("Stream completed.");
}
};

// Begin streaming
StreamObserver<User> userStreamObserver = stub.updateUsers(requestObserver);

// Send several messages
for (int i = 0; i < 3; i++) {
User user = User.newBuilder()
.setId(i+1)
.setName("User" + i)
.setAge(20 + i)
.setGender(i % 2 == 0 ? "Male" : "Female")
.build();
userStreamObserver.onNext(user);
}

User notExistUser = User.newBuilder()
.setId(-1)
.setName("NotExistUser")
.setAge(20)
.setGender("Male")
.build();
userStreamObserver.onNext(notExistUser);
// Tell the server we're done
userStreamObserver.onCompleted();

Thread.sleep(3000); // Wait for async request to complete
channel.shutdown();
}
}

Appendixes

Custom protobuf configuration

/**
* Customizing proto source directories
*/
sourceSets {
main {
proto {
// In addition to the default 'src/main/proto'
srcDir("src/main/protobuf")
}
}
test {
proto {
// In addition to the default 'src/test/proto'
srcDir("src/test/protobuf")
}
}
}

protobuf {
// Customize code generation tasks
generateProtoTasks {
all().forEach {
it.builtins {
java {
// Optionally specify options for the builtin java codegen.
// This yields "--java_out=example_option1=true,example_option2:/path/to/output"
}
}
it.plugins {
// Add grpc output without any option. grpc must have been defined in the protobuf.plugins block.
// This yields "--grpc_out=/path/to/output" on the protoc commandline.
id("grpc") {
// Use subdirectory 'grpcjava' instead of the default 'grpc'
outputSubDir = "grpcjava"
}
}
}
}
}

References

[1] gRPC: Up and Running by Kasun Indrasiri and Danesh Kuruppu

[2] protobuf-gradle-plugin - GitHub

[3] protobuf-maven-plugin - GitHub