1. Introduction
gRPC is a high performance, open source RPC framework initially developed by Google. It helps to eliminate boilerplate code and connect polyglot services in and across data centers.
2. Overview
The framework is based on a client-server model of remote procedure calls. A client application can directly call methods on a server application as if it were a local object.
In this tutorial, weβll use the following steps to create a typical client-server application using gRPC:
- Define a service in a .proto file
- Generate server and client code using the protocol buffer compiler
- Create the server application, implementing the generated service interfaces and spawning the gRPC server
- Create the client application, making RPC calls using generated stubs
Letβs define a simple HelloService that returns greetings in exchange for the first and last name.
3. Maven Dependencies
Weβll add the grpc-netty, grpc-protobuf and grpc-stub dependencies:
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty</artifactId>
<version>1.62.2</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.62.2</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.62.2</version>
</dependency>
4. Defining the Service
Weβll start by defining a service and specifying methods that can be called remotely, along with their parameters and return types.
This is done in the .proto file using the protocol buffers. Theyβre also used for describing the structure of the payload messages.
4.1. Basic Configurations
Letβs create a HelloService.proto file for our sample HelloService. Weβll start by adding a few basic configuration details:
syntax = "proto3";
option java_multiple_files = true;
package org.baeldung.grpc;
The first line tells the compiler which syntax this file uses. By default, the compiler generates all the Java code in a single Java file. The second line overrides this setting, meaning everything will be generated in individual files.
Finally, weβll specify the package we want to use for our generated Java classes.
4.2. Defining the Message Structure
Next, weβll define the message:
message HelloRequest {
string firstName = 1;
string lastName = 2;
}
This defines the request payload. Here, each attribute that goes into the message is defined, along with its type.
A unique number needs to be assigned to each attribute, called the tag. The protocol buffer uses this tag to represent the attribute, instead of using the attribute name.
So unlike JSON, where weβd pass the attribute name firstName every single time, the protocol buffer will use the number 1 to represent firstName. The response payload definition is similar to the request.
Note that we can use the same tag across multiple message types:
message HelloResponse {
string greeting = 1;
}
4.3. Defining the Service Contract
Finally, letβs define the service contract. For our HelloService, weβll define a hello() operation:
service HelloService {
rpc hello(HelloRequest) returns (HelloResponse);
}
The hello() operation accepts a unary request and returns a unary response. gRPC also supports streaming by prefixing the stream keyword to the request and response.
5. Generating the Code
Now weβll pass the HelloService.proto file to the protocol buffer compiler, protoc, to generate the Java files. There are multiple ways to trigger this.
5.1. Using Protocol Buffer Compiler
First, weβll need the Protocol Buffer Compiler. We can choose from many precompiled binaries available here.
Additionally, weβll need to obtain the gRPC Java Codegen Plugin.
Finally, we can use the following command to generate the code:
protoc --plugin=protoc-gen-grpc-java=$PATH_TO_PLUGIN -I=$SRC_DIR
--java_out=$DST_DIR --grpc-java_out=$DST_DIR $SRC_DIR/HelloService.proto
5.2. Using Maven Plugin
As developers, we want the code generation to be tightly integrated with our build system. gRPC provides a protobuf-maven-plugin for the Maven build system:
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.6.1</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>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
The os-maven-plugin extension/plugin generates various useful platform-dependent project properties, like ${os.detected.classifier}.
6. Creating the Server
Irrespective of which method we use for code generation, the following key files will be generated:
- HelloRequest.java β contains the HelloRequest type definition
- HelloResponse.java β this contains the HelleResponse type definition
- HelloServiceImplBase.java β this contains the abstract class HelloServiceImplBase, which provides an implementation of all the operations we defined in the service interface
6.1. Overriding the Service Base Class
The default implementation of the abstract class HelloServiceImplBase is to throw the runtime exception io.grpc.StatusRuntimeException, which says that the method is unimplemented.
Weβll extend this class, and override the hello() method mentioned in our service definition:
public class HelloServiceImpl extends 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();
}
}
If we compare the signature of hello() with the one we wrote in the HellService.proto file, weβll notice that it doesnβt return HelloResponse. Instead, it takes the second argument as StreamObserver<HelloResponse>, which is a response observer, a call back for the server to call with its response.
This way the client gets the option to make a blocking call or a non-blocking call.
gRPC uses builders for creating objects. Weβll use HelloResponse.newBuilder() and set the greeting text to build a HelloResponse object. Weβll set this object to the responseObserverβs onNext() method to send it to the client.
Finally, weβll need to call onCompleted() to specify that weβve finished dealing with the RPC; otherwise, the connection will be hung, and the client will just wait for more information to come in.
6.2. Running the Grpc Server
Next, weβll need to start the gRPC server to listen for incoming requests:
public class GrpcServer {
public static void main(String[] args) {
Server server = ServerBuilder
.forPort(8080)
.addService(new HelloServiceImpl()).build();
server.start();
server.awaitTermination();
}
}
Here, we again use the builder to create a gRPC server on port 8080, and add the HelloServiceImpl service that we defined. start() will start the server. In our example, weβll call awaitTermination() to keep the server running in the foreground, blocking the prompt.
7. Creating the Client
gRPC provides a channel construct that abstracts out the underlying details, like connection, connection pooling, load balancing, etc.
Weβll create a channel using ManagedChannelBuilder. Here weβll specify the server address and port.
Weβll use plain text without any encryption:
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("Baeldung")
.setLastName("gRPC")
.build());
channel.shutdown();
}
}
Then weβll need to create a stub, which weβll use to make the actual remote call to hello(). The stub is the primary way for clients to interact with the server. When using auto-generated stubs, the stub class will have constructors for wrapping the channel.
Here weβre using 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 that facilitate non-blocking/asynchronous calls.
Now itβs time to make the hello() RPC call. Weβll pass the HelloRequest. We can use the auto-generated setters to set the firstName and lastName attributes of the HelloRequest object.
Finally, the server returns the HelloResponse object.
8. Conclusion
In this article, we learned how to use gRPC to ease the development of communication between two services by focusing on defining the service, and letting the gRPC handle all the boilerplate code.
