HandsOn Introduction to gRPC with Java

Pamoda Wimalasiri
7 min readFeb 11, 2022

--

What is gRPC?

gRPC is a modern open-source high-performance Remote Procedure Call (RPC) framework developed by Google. It can efficiently connect services in and across data centers with pluggable support for load balancing, tracing, health checking and authentication.

Why gRPC over REST?

  1. Protobuf instead of JSON/XML
    REST uses JSON for sending and receiving messages. JSON is flexible, text-based and human-readable but it is not fast enough or light-weight enough for transmitting data between systems.
    gRPC uses Protobuf(protocol buffers) messaging format which is faster and more efficient for data transmission
  2. First-class support for code generation
    REST needs third-party tools to generate the code for API calls.
    gRPC comes with an in-built protoc compiler which provides the code generation features by default.
  3. Built on HTTP 2
    REST is built on HTTP 1.1 which uses a request-response model of communication.
    gRPC uses HTTP2 so that it supports client-response communication and bidirectional streaming.
  4. Works across languages and platforms
    Since the code can be generated for any language, it is easy to create micro-services in any language to interact with each other
Communication across languages

4 Types of API in gRPC

Types of API in gRPC

Following is an example of the four types of APIs. You can understand it further when we are implementing the sample.

service GreetService {// Unary
rpc Greet(GreetRequest) returns (GreetResponse) {};
// Streaming server
rpc GreetManyTimes(GreetManyTimesRequest) returns (stream GreetManyTimesResponse) {};
// Streaming client
rpc LongGreet(stream LongGreetRequest) returns (LongGreetResponse) {};
// Bidirectional streaming
rpc GreetAll(stream GreetAllRequest) returns (stream GreetAllResponse) {};
}

I hope you got a basic idea about gRPC. So, now let’s see how we can implement a gRPC server and client using Java.

We will build a CarParkusing gRPC.

Steps for the sample

  1. Create a maven project
  2. Define a service in a .proto file
  3. Generate server and client code using the protocol buffer compiler
  4. Build the server application, implement the generated service interfaces spawning the gRPC server
  5. Build the client application, making RPC calls using generated stubs

Let’s go through each of these steps in detail. I have shared the complete source code of the sample on GitHub. You can refer to that and there are inline comments for more details. [1]

Create a maven project

  1. Create a maven project. (You can refer to the complete pom of the sample)
  2. Add the following dependencies in the pom file.

<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>

3. Add the protobuf-maven-plugin. This is responsible for code generation.

<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.3.0:exe:${os.detected.classifier}
</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>
io.grpc:protoc-gen-grpc-java:1.4.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>
</plugins>
</build>

<protoSourceRoot> indicates the place where the service definition file is located.

I’m are using java 8 for my sample.

Define the service

Defining the service means specifying the methods that can be remotely called along with their parameters and return types.

This is done in the .proto file using Protocol Buffers. The structure of the payload messages should also be described here.

You can refer to the complete proto file of the sample.

  1. Add the common configurations
syntax = "proto3";
option java_multiple_files = true;
  • syntax : this indicates the syntax of this file
  • java_multiple_files : this says the compiler to generate java code in separate files. Otherwise, all will be in a single java file

2. Define the message formats

message Vehicle {
string vehicle_number = 1;
string vehicle_type = 2;
}
message ParkRequest {
Vehicle vehicle = 1;
}

In this, vehicle_number and vehicle_type are the attributes of the Vehicle message. And the given value is the order. ParkRequest is another message format.

Likewise you can define all the message formats that you will need.

3. Define the service and the methods

service CarParkService {
rpc parkVehicle(ParkRequest) returns (ParkResponse);
}
  • CarParkService : This is the gRPC service
  • parkVehicle : This is a method in the CarParkService
  • ParkRequest : This is the input parameter for the parkVehicle method
  • ParkResponse : This is the response of the parkVehicle method

4. Define another method with streaming

service CarParkService {
rpc parkVehicle(ParkRequest) returns (ParkResponse);
rpc parkVehicleManyTimes(ParkRequest) returns (stream ParkResponseManyTimes);
}

In this method, the server will stream the response.

Generate the source code

Run the mvn clean install command to compile the project and generate the java codes for the defined service.

You will see the generated java classes inside the <SAMPLE_HOME>/target/generated-sources/protobuf/java

Implement the server

  1. Create a separate package for the server. For my sample, I will create the package as org.sample.park.server

2. Implement the service by extending the generated service base class. The base class has all the methods that we have defined in the proto file. I have service implementation in the CarParkServiceImpl class.

public class CarParkServiceImpl extends CarParkServiceGrpc.CarParkServiceImplBase {

3. In the service implementation class, let’s first implement the parkVehicle method by overriding the method of the base class.

public void parkVehicle(ParkRequest request, StreamObserver<ParkResponse> responseObserver) {

String vehicleNo = request.getVehicle().getVehicleNumber();
String vehicleType = request.getVehicle().getVehicleType();

String resultMsg ="The vehicle of number " + vehicleNo + " and type " + vehicleType + " is parked.";
System.out.println(resultMsg);

ParkResponse parkResponse = ParkResponse
.newBuilder()
.setResult(resultMsg)
.build();
responseObserver.onNext(parkResponse);
responseObserver.onComplete();
}

gRPC uses builders for creating objects.

Inside the parkVehicle method, the number and the type of the vehicle is printed and the sent back to the client.

The response is sent when we call responseObserver’s onNext() method.

onCompleted() specifies that RPC is finished, else the connection will be hung, and the client will just wait for more information to come in.

4. Let’s implement the parkVehicleManyTimes method by overriding the method of the base class where the server will send several messages to the client.

@Override
public void parkVehicleManyTimes(ParkRequest request, StreamObserver<ParkResponseManyTimes> responseObserver) {

String vehicleNo = request.getVehicle().getVehicleNumber();
String vehicleType = request.getVehicle().getVehicleType();

String response1 = "The vehicle of number " + vehicleNo + " and type " + vehicleType + " entered the park.";
ParkResponseManyTimes parkResponse1 = ParkResponseManyTimes
.newBuilder()
.setResult(response1)
.build();
responseObserver.onNext(parkResponse1);

Thread.sleep(10000);

// Build and send the second response.
String response2 = "The ground floor is full.";
ParkResponseManyTimes parkResponse2 = ParkResponseManyTimes
.newBuilder()
.setResult(response2)
.build();
responseObserver.onNext(parkResponse2);

Thread.sleep(10000);

// Build and send the third response.
String response3 = "The vehicle of number " + vehicleNo + " and type " + vehicleType + " is parked in the first floor";
ParkResponseManyTimes parkResponse3 = ParkResponseManyTimes
.newBuilder()
.setResult(response3)
.build();
responseObserver.onNext(parkResponse3);

// Complete the communication.
responseObserver.onCompleted();
}

Here, the server is sending 3 responses to the client and before each response, the server will wait for 10 seconds.

5. Write a separate java class to start the server. I will name my class as CarParkServer. Here, we need to add the service and start the server with a given port. The server needs to be kept alive for the client to make requests.

public static void main(String[] args) throws IOException, InterruptedException {

Server server = ServerBuilder.forPort(5003)
.addService(new CarParkServiceImpl())
.build();

server.start();
server.awaitTermination();
}

Implement the Client

  1. Create a separate package for the client. For my sample, I will create the package as org.sample.park.client
  2. Write a class to start the client. I have the class CarParkClient.
  3. In the main method of the client, initiate the communication with the server using a channel.
ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 5003)
.usePlaintext()
.build();

4. Inside the same main method, use the stub of the CarParkService to call the methods of the service. The stub is the primary way for the clients to interact with the server.

CarParkServiceGrpc.CarParkServiceBlockingStub stub =CarParkServiceGrpc.newBlockingStub(channel);

Here we have used a blocking/synchronous stub so that the RPC call waits for the server to respond, and will either return a response or raise an exception. There are two other types of stubs provided by gRPC, which facilitate non-blocking/asynchronous calls.

5. In the same main method, define a ParkRequest to call the parkVehicle method. And then, we call parkVehicle method and to catch the ParkResponse returned by the method.

ParkRequest parkRequest = ParkRequest.newBuilder()
.setVehicle(Vehicle.newBuilder()
.setVehicleNumber("NA-1324")
.setVehicleType("BUS")
.build())
.build();
ParkResponse parkResponse = stub.parkVehicle(parkRequest);
System.out.println("Response for first call: " + parkResponse.getResult());

6. In the same main method, call the parkVehicleManyTimes method and handle the streaming response from the server.

ParkRequest parkRequest2 = ParkRequest.newBuilder()
.setVehicle(Vehicle.newBuilder()
.setVehicleNumber("PE-6723")
.setVehicleType("VAN")
.build())
.build();

System.out.println("Response for the second call: ");
stub.parkVehicleManyTimes(parkRequest2).forEachRemaining(parkResponseManyTimes -> {
System.out.println(parkResponseManyTimes.getResult());
});

Try out the service

  1. Run the server.
  2. Run the client.

Then you can see the corresponding messages get printed.

I know this article is a bit lengthy. But I assure you that is because of the attached code snippets. Otherwise, the steps are simpler. 😌

Hope you understand how gRPC works. You can try out the sample and see how easy it is.

See you soon with another blog. Happy Reading!

[1] https://github.com/pamodaaw/grpc-sample

--

--

Pamoda Wimalasiri
Pamoda Wimalasiri

Written by Pamoda Wimalasiri

Associate Technical Lead @ WSO2 focused on the IAM domain

No responses yet