gRPC & Java

5 minute read

Google released gRPC as the new open source framework in the year 2015, since then it has changing the way data is exchanged between services across multiple data centers.

If you looking to build a truly language independent micro service which is reliable, high performing, easily scalable in distributed environment. Then definitely gRPC must be your choice.

What is gRPC?

In gRPC, a client application can directly call server methods as if they are local to the client. This makes developer’s task easy to create distributed services.

String-2

To put it simply, You create a prototype of a service with its request & response, share it across server & client and they generate respective methods and implement them, now client invokes the method with request which is transferred to server, the method executes on server and returns the response back to client, but at client’s end it would seem as working with a local method.

In the real world, the biggest challenge is handling database in micro services, having a distributed persistence layer could work wonders.

Core Feature

Some of the important features of gRPC:

  1. It support 10+ languages, you can write your service in java and even a python client will be able to access it.
  2. It uses http 2 protocol, its highly efficient for working with large volume of data.
  3. It provides are very simple service declaration, easy to understand.
  4. It support bidirectional streams.
  5. It provides lot of plugins such as authentication, tracing, load balancing & health check.

Maven Dependencies

Lets add grpc dependencies:

    <dependencies>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-all</artifactId>
            <version>1.18.0</version>
        </dependency>
    </dependencies>

Define Service

Let us create a simple calculator service to add two value:

We will start by defining a method with parameters and return value, but this has to be a prototype.

We do this by creating .proto file using protocol buffers, there are used to describe messages.

So we create calculator.proto under /src/main/proto

syntax = "proto3"; //this directs compiler to use version 3

option java_multiple_files = true; //we want generate different java files after compilation. By default all the classes are generated in a single file.
package in.kuros.grpc; // defines the package structure of generated classes.

//request payload
message OperandRequest {
    int32 X = 1; // message with type, along with tag: 1
    int32 Y = 2; // tag: 2
}

//response payload
message AddResponse {
    int64 result = 1;
}

// service contract
service Calculator {
    rpc add(OperandRequest) returns (AddResponse); // method name: add, parameter type: OperandRequest, response type: AddResponse 
}

A unique number needs to be assigned to each attribute, called as the tag. This tag is used by the protocol buffer to represent the attribute instead of using the attribute name. So, unlike JSON where we would pass attribute name X every single time, protocol buffer would use the number 1 to represent X. Response payload definition is similar to the request.

So at the end after generation, we will have three classes (in terms of java), Operands, AddResponse & Calculator which takes operands and returns addResponse;

Generating class files for java.

Now we pass the HelloService.proto file to the protocol buffer compiler protoc to generate the Java files. There are multiple ways to trigger this.

Protocol Buffer compiler

Download the compiler, and follow readme.txt

Use the command to generate code

$ protoc -I=$SRC_DIR --java_out=$DST_DIR $SRC_DIR/proto/calculator.proto

Maven compiler plugin

You don’t want to execute command to generate code every time, so we will make use of maven plugin to execute at build time

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

Generated files

Once you the run the generation, some key files will be generated under /target/generated-sources/protobuf

  • OperandRequest.java - contains the OperandRequest definition.
  • AddResponse.java - contains the AddResponse definition.
  • CalculatorGrpc.java - this contains abstract inner class CalculatorImplBase which provides an implementation wrapper.

Server Side Implementation

Now its time write the addition logic on the server side. so we will create an implementation class CalculatorImpl which will extend CalculatorImplBase and provide implementation.

import in.kuros.grpc.AddResponse;
import in.kuros.grpc.CalculatorGrpc.CalculatorImplBase;
import in.kuros.grpc.OperandRequest;
import io.grpc.stub.StreamObserver;

public class CalculatorImpl extends CalculatorImplBase {

    @Override
    public void add(final OperandRequest request, final StreamObserver<AddResponse> responseObserver) {
        final long sum = request.getX() + request.getY();

        final AddResponse addResponse = AddResponse
                .newBuilder()
                .setResult(sum)
                .build();

        try {
            System.out.println("Sleeping to 5 sec");
            Thread.sleep(5 * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        responseObserver.onNext(addResponse);
        responseObserver.onCompleted();
    }
}

We have provide a sleep of 5 sec to simulate long taking operations.

start the gRPC server

Next, we need to start the gRPC server to listen for incoming requests:

import io.grpc.Server;
import io.grpc.ServerBuilder;

public class GrpcServer {

    public static void main(String[] args) throws Exception {
        final Server server = ServerBuilder.forPort(8080)
                .addService(new CalculatorImpl())
                .build();

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

So, here we have created a serve to listen at port 8080 for incoming request, registered our calculator service. In our example, we will call awaitTermination() to keep the server running in foreground blocking the prompt.

Creating a client

First we need to create a gRPC channel for our stub, specifying the server address and port we want to connect to.

We use a ManagedChannelBuilder to create the channel.

ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 8080)
                .usePlaintext()
                .build();

Now we can use the channel to create our stubs using the newStub and newBlockingStub methods provided in the RouteGuideGrpc class we generated from our .proto.

making sync calls

We create a blocking stub

    final CalculatorGrpc.CalculatorBlockingStub blockingStub = CalculatorGrpc.newBlockingStub(channel);

    System.out.println("Making blocking call");
    final AddResponse blockResponse = blockingStub.add(OperandRequest.newBuilder().setX(10).setY(20).build());
    System.out.println("blocking call result: " + blockResponse.getResult());

making async calls

We use newStub method to make aysnc calls, but we need to provide a StreamObserver implementation to it.

    final CalculatorGrpc.CalculatorStub asyncStub = CalculatorGrpc.newStub(channel);
        
    StreamObserver<AddResponse> streamObserver = new StreamObserver<AddResponse>() {
        public void onNext(final AddResponse addResponse) {
            System.out.println("async call result: " + addResponse.getResult());
        }
    
        public void onError(final Throwable throwable) {
            System.out.println(throwable);
        }
    
        public void onCompleted() {
            System.out.println("Async call stopped listening");
        }
    };


    System.out.println("Making blocking call");
    asyncStub.add(OperandRequest.newBuilder().setX(10).setY(20).build(), streamObserver);
    System.out.println("Async invoked" + blockResponse.getResult());

    channel.awaitTermination(10, TimeUnit.SECONDS);

complete client code:

import in.kuros.grpc.AddResponse;
import in.kuros.grpc.CalculatorGrpc;
import in.kuros.grpc.OperandRequest;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.stub.StreamObserver;

import java.util.concurrent.TimeUnit;

public class GrpcClient {

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

        final CalculatorGrpc.CalculatorBlockingStub blockingStub = CalculatorGrpc.newBlockingStub(channel);

        System.out.println("Making blocking call");
        final AddResponse blockResponse = blockingStub.add(OperandRequest.newBuilder().setX(10).setY(20).build());
        System.out.println("blocking call result: " + blockResponse.getResult());


        final CalculatorGrpc.CalculatorStub asyncStub = CalculatorGrpc.newStub(channel);

        StreamObserver<AddResponse> streamObserver = new StreamObserver<AddResponse>() {
            public void onNext(final AddResponse addResponse) {
                System.out.println("async call result: " + addResponse.getResult());
            }

            public void onError(final Throwable throwable) {
                System.out.println(throwable);
            }

            public void onCompleted() {
                System.out.println("Async call stopped listening");
            }
        };


        System.out.println("Making blocking call");
        asyncStub.add(OperandRequest.newBuilder().setX(10).setY(20).build(), streamObserver);
        System.out.println("Async invoked" + blockResponse.getResult());

        channel.awaitTermination(10, TimeUnit.SECONDS);

    }
}

Next, we 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 interacts with the server. When using auto generate stubs, the stub class will have constructors for wrapping the channel.

Conclusion

In this post we have learned how to work with a gRPC Server/Client in java. As usual find the complete code

If you liked this article, you can buy me a coffee

Categories:

Updated:

Kumar Rohit
WRITTEN BY

Kumar Rohit

I like long drives, bike trip & good food. I have passion for coding, especially for Clean-Code.

Leave a comment