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 was 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.16.1</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.16.1</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.16.1</version>
</dependency>
4. Defining the Service
We’ll start by defining a service, 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
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.
As always, the code contained in the article is available over on GitHub.