GRPC in Java

Kavindu Gayan
8 min readOct 6, 2023

What is grpc?

gRPC (Google Remote Procedure Call) is an open-source, high-performance framework for building efficient and distributed microservices and remote procedure call (RPC) systems. It was developed by Google and is now part of the Cloud Native Computing Foundation (CNCF). gRPC is designed to facilitate communication between distributed systems, making it easier for different services or components to communicate with each other, regardless of the programming languages they are written in.

Key features of gRPC include:

  1. Protocol Buffers (ProtoBuf): gRPC uses Protocol Buffers as its interface definition language (IDL) for defining the structure of messages and service methods. ProtoBuf is a language-agnostic data serialization format that allows you to define message types and services in a clear and concise way.

2. HTTP/2: gRPC uses HTTP/2 as its transport protocol. HTTP/2 is a modern, efficient, and multiplexed protocol that reduces latency and allows multiple RPCs to be multiplexed over a single connection. This improves performance compared to traditional HTTP/1.1-based RPC systems.

3. Code Generation: gRPC generates client and server code in various programming languages from the ProtoBuf service definition. This makes it easy to create clients and servers that can communicate seamlessly.

4. Bi-directional Streaming: gRPC supports bidirectional streaming, allowing both the client and server to send a stream of messages to each other. This is useful for use cases like real-time communication and data synchronization.

5. Pluggable: gRPC supports pluggable authentication, load balancing, and other features, making it flexible and adaptable to various deployment scenarios.

6. Language Support: gRPC provides support for multiple programming languages, including but not limited to, Python, Java, C++, Go, and more. This allows developers to build heterogeneous systems with components written in different languages.

Overall, gRPC is a powerful tool for building efficient and interoperable microservices and distributed systems, and it has gained popularity in the world of cloud-native and containerized applications. It’s commonly used in scenarios where high-performance communication between services is crucial, such as microservices architectures and cloud-native applications.

Why grpc over http?

gRPC has some advantages over traditional HTTP when it comes to certain use cases, especially in scenarios where performance, efficiency, and type safety are critical considerations. Here are some reasons why you might choose gRPC over HTTP:

  1. Efficiency: gRPC uses HTTP/2 as its transport protocol, which is more efficient than the older HTTP/1.1 in terms of reducing latency and improving data compression. HTTP/2 allows multiple requests and responses to be multiplexed over a single connection, reducing the overhead associated with opening and maintaining multiple connections.

2. Binary Serialization: gRPC uses Protocol Buffers (ProtoBuf) for message serialization. ProtoBuf is a binary serialization format that is more compact and efficient than JSON or XML used in typical HTTP APIs. This results in smaller payload sizes and faster data transmission.

3. Strongly Typed Contracts: With gRPC, your API contracts are defined using Protocol Buffers. This provides a strong type system, enabling better validation and code generation. Clients can be automatically generated from the API contract, ensuring that they use the correct data types and follow the defined service interface.

4. Bidirectional Streaming: gRPC supports bidirectional streaming, allowing both clients and servers to send a stream of messages. This is beneficial for real-time communication scenarios where both sides need to send and receive data simultaneously.

5. Code Generation: gRPC generates client and server code in multiple programming languages from the API contract. This reduces the potential for manual errors in client-server communication and ensures that both sides are in sync.

6. Load Balancing and Service Discovery: gRPC provides built-in support for load balancing and service discovery, making it easier to build resilient and scalable distributed systems.

7. Interoperability: While gRPC is most commonly associated with Protobuf, it also supports other serialization formats like JSON. This means you can use gRPC to communicate with systems that require JSON-based APIs, making it versatile and interoperable.

8. Bi-directional Authentication: gRPC supports mutual authentication, allowing both the client and server to verify each other’s identities. This is important for securing communication in microservices architectures.

However, it’s essential to note that gRPC may not always be the best choice for every situation. For simple, human-readable APIs, or when you need broad compatibility with existing web-based systems, traditional HTTP-based APIs (RESTful or GraphQL) might still be more appropriate. The choice between gRPC and HTTP depends on your specific requirements and the trade-offs you are willing to make in terms of complexity, performance, and tooling.

Sample code

grpc Server

pom.xml

<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty</artifactId>
<version>1.36.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.36.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.36.0</version>
</dependency>
...

<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.6.2</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:3.12.0:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.36.0:exe:${os.detected.classifier}</pluginArtifact>
<protoSourceRoot>src/main/resources</protoSourceRoot>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.1.1</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<mainClass>lk.iit.retail.RetailServer</mainClass>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

proto sample

syntax = "proto3";
option java_multiple_files = true;
package lk.iit.retail.grpc.generated;

message UpdateCatalogueRequest {
string catalogueId = 1;
int32 updateQuantity = 2;
}

message UpdateCatalogueResponse {
bool isUpdate = 1;
}

service UpdateCatalogueService {
rpc updateCatalogue(UpdateCatalogueRequest) returns (UpdateCatalogueResponse);
}

message GetCatalogueRequest {
}

message Catalogue {
int32 id = 1;
string name = 2;
int32 quantity = 3;
}

message GetCatalogueResponse {
repeated Catalogue catalogues = 1;
}

service GetCatalogueService {
rpc getCatalogue(GetCatalogueRequest) returns (GetCatalogueResponse);
}

message AddShoppingCartRequest {
int32 catalogueId = 1;
int32 quantity = 2;
int32 shopperId = 3;
}

message AddShoppingCartResponse {
bool isUpdated = 1;
}

service AddShoppingCartService {
rpc addShoppingCart(AddShoppingCartRequest) returns (AddShoppingCartResponse);
}

message CheckoutRequest {
int32 shopperId = 1;
}

message CheckoutResponse {
bool isCheckout = 1;
}

service CheckoutCartService {
rpc checkoutCart(CheckoutRequest) returns (CheckoutResponse);
}

message NotifyRequest {
int32 notifyType = 1;
string notifyPayload = 2;
}

message NotifyResponse {
bool isUpdate = 1;
}

service UpdateNotifyService {
rpc updateNotifyService(NotifyRequest) returns (NotifyResponse);
}

Here’s a breakdown of the key components in the code:

  1. syntax = "proto3";: This line specifies that the protocol buffer file uses version 3 of the syntax. Protocol Buffers has different versions, and proto3 is the latest at the time of writing.
  2. option java_multiple_files = true;: This option is specific to generating Java code from the protobuf definition. It instructs the protobuf compiler to generate one .java file per message or service defined in the .proto file, as opposed to a single combined file.
  3. package lk.iit.retail.grpc.generated;: This line defines the package namespace for the generated Java code. It's a common practice to organize protobuf definitions into packages similar to how Java packages are used.
  4. message: Messages are the fundamental data structures in Protocol Buffers. They are similar to classes or structs in other programming languages. Messages can contain fields with specific data types.
  5. Service Definitions: Services define RPC methods that can be called remotely. For example, the UpdateCatalogueService defines an updateCatalogue RPC method that takes an UpdateCatalogueRequest message as input and returns an UpdateCatalogueResponse message as output.
  6. Catalogue and GetCatalogueResponse: These messages are used to represent data structures. GetCatalogueResponse contains a field named catalogues, which is marked as repeated, indicating that it can contain a list of Catalogue objects.
  7. service GetCatalogueService, service AddShoppingCartService, etc.: These are service definitions that define various RPC methods used for different functionalities.
  8. int32, string, bool, and repeated: These are data types used within message fields.
  • UpdateCatalogueRequest and UpdateCatalogueResponse are two message types.
  • UpdateCatalogueService is a service definition that includes an RPC (Remote Procedure Call) method named updateCatalogue.
  • int32, string, bool, and repeated: These are data types used within message fields.
  • int32 is a 32-bit signed integer.
  • string is a string of characters.
  • bool is a boolean (true or false).
  • repeated indicates that a field can contain zero or more values of the specified type, making it similar to an array or list.

How to generate the stub

mvn clean package

This command will invoke the protobuf-maven-plugin to generate the Java code for your protobuf definitions, including the gRPC stubs.After a successful build, you can find the generated Java source code (including gRPC stubs) in the target directory of your project. Typically, the generated code will be located in the target/generated-sources/protobuf/java directory.

Here’s an example of how to use the generated gRPC stubs to create a client:

import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;
import lk.iit.retail.grpc.generated.UpdateCatalogueRequest;
import lk.iit.retail.grpc.generated.UpdateCatalogueResponse;
import lk.iit.retail.grpc.generated.UpdateCatalogueServiceGrpc;

public class GrpcServer {

public static void main(String[] args) throws Exception {
// Define the gRPC server port
int serverPort = 8080;

// Create a new gRPC server
Server server = ServerBuilder.forPort(serverPort)
.addService(new UpdateCatalogueServiceImpl()) // Add your service implementation here
.build();

System.out.println("Starting gRPC server on port " + serverPort);

// Start the server
server.start();

// Block until the server is terminated
server.awaitTermination();
}

// Define your gRPC service implementation
static class UpdateCatalogueServiceImpl extends UpdateCatalogueServiceGrpc.UpdateCatalogueServiceImplBase {
@Override
public void updateCatalogue(UpdateCatalogueRequest request,
StreamObserver<UpdateCatalogueResponse> responseObserver) {
// Process the request and generate a response
boolean isUpdateSuccessful = processUpdate(request);

// Build the response
UpdateCatalogueResponse response = UpdateCatalogueResponse.newBuilder()
.setIsUpdate(isUpdateSuccessful)
.build();

// Send the response to the client
responseObserver.onNext(response);
responseObserver.onCompleted();
}

// Implement your update logic here
private boolean processUpdate(UpdateCatalogueRequest request) {
// Your update logic goes here
// Return true if the update is successful, false otherwise
return true;
}
}
}

In this code:

  1. We specify the gRPC server’s port in serverPort.
  2. We create a new gRPC server using ServerBuilder.forPort(serverPort) and add your service implementation using .addService(new UpdateCatalogueServiceImpl()). You should add your own service implementation for your gRPC service.
  3. Inside the UpdateCatalogueServiceImpl class, we override the updateCatalogue method, which is the implementation of your gRPC service method.
  4. In the processUpdate method, you can implement your specific logic for handling the request and generating a response.
  5. We build the response (UpdateCatalogueResponse) based on the result of your update logic.
  6. Finally, we send the response to the client using responseObserver.onNext(response) and responseObserver.onCompleted().

grpc client

import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import lk.iit.retail.grpc.generated.UpdateCatalogueRequest;
import lk.iit.retail.grpc.generated.UpdateCatalogueResponse;
import lk.iit.retail.grpc.generated.UpdateCatalogueServiceGrpc;

public class GrpcClient {

public static void main(String[] args) {
// Define the gRPC server address and port
String serverAddress = "localhost";
int serverPort = 8080;

// Create a gRPC channel to the server
ManagedChannel channel = ManagedChannelBuilder.forAddress(serverAddress, serverPort)
.usePlaintext() // Remove this for secure connections
.build();

// Create a gRPC stub for the UpdateCatalogueService
UpdateCatalogueServiceGrpc.UpdateCatalogueServiceBlockingStub stub =
UpdateCatalogueServiceGrpc.newBlockingStub(channel);

// Create a request
UpdateCatalogueRequest request = UpdateCatalogueRequest.newBuilder()
.setCatalogueId("123")
.setUpdateQuantity(5)
.build();

// Send the request and receive a response
UpdateCatalogueResponse response = stub.updateCatalogue(request);

// Process the response
if (response.getIsUpdate()) {
System.out.println("Update successful.");
} else {
System.out.println("Update failed.");
}

// Shutdown the channel when done
channel.shutdown();
}
}

In this code:

  1. We specify the gRPC server’s address and port in serverAddress and serverPort.
  2. We create a gRPC channel to connect to the server using ManagedChannelBuilder. The usePlaintext() method is used for non-secure connections. For secure connections, you should configure TLS.
  3. We create a gRPC stub for the UpdateCatalogueService using the generated UpdateCatalogueServiceGrpc.newBlockingStub(channel) method.
  4. We create a gRPC request object (UpdateCatalogueRequest) with the necessary data.
  5. We send the request using the stub and receive a response (UpdateCatalogueResponse).
  6. We process the response based on your application’s logic.
  7. Finally, we shut down the gRPC channel when we are done.

--

--