Building a gRPC service with Java

gRPC is a language-neutral, platform-neutral remote procedure call (RPC) framework and toolset developed at Google. It lets you define a service using Protocol Buffers, a particularly powerful binary serialization toolset and language. It then lets you generate idiomatic client and server stubs from your service definition in a variety of languages.

In this codelab, you'll learn how to build a Java-based service that exposes an API using the gRPC framework, and then writing a client to use the generated gRPC client-side stub.

What you'll learn

  • The Protocol Buffer Language
  • How to implement a gRPC service using Java
  • How to implement a gRPC client using Java

How will you use use this tutorial?

Read it through only Read it and complete the exercises

How would you rate your experience with building Node.js apps?

Novice Intermediate Proficient

How would you rate your experience with building Go apps?

Novice Intermediate Proficient

Self-paced environment setup

  1. Sign in to Cloud Console and create a new project or reuse an existing one. (If you don't already have a Gmail or G Suite account, you must create one.)

MIsCwOp-0U1r3jQ74M8pVqrxXo0G_vjvdgyXkOS740hp0Qn8BFsHZCgjs3Ok3JJFABjY-lTCRbuy2hYvuTXX5yRuPUNTv_s1SKcKjqYDJ-rBRe7ktWeWe1KJl7gLXglMB-oV3__vxA

YwGK3w76ht-vTgqsiunQRWLdoiVFuzQcv0vGTNvsvb0VLMYeO3F15_ukdonJljPCrnjg7HJi2dL5wbr9PBqYAs6Rc8egezCHONRx-Q3u2Usz7vxX6TDibEcfVW2clGwC0orSohSojg

OFUB65_bgYxo-g4Re0fuijqEK_qnMmkfG0LhpZVa8HRBhxt_aCWQvpuJAX8nQInwQUbLGQvGrFByb2o03RBA8_hdasQdAyO0-Mk5V13PIJOI3BjxI4mxk6hvwyBaocqu1i8MtdrLAQ

Remember the project ID, a unique name across all Google Cloud projects (the name above has already been taken and will not work for you, sorry!). It will be referred to later in this codelab as PROJECT_ID.

  1. Next, you'll need to enable billing in Cloud Console in order to use Google Cloud resources.

Running through this codelab shouldn't cost much, if anything at all. Be sure to to follow any instructions in the "Cleaning up" section which advises you how to shut down resources so you don't incur billing beyond this tutorial. New users of Google Cloud are eligible for the $300USD Free Trial program.

Google Cloud Shell

While this codelab can be operated from your computer, in this codelab we will be using Google Cloud Shell, a command line environment running in the Cloud.

This Debian-based virtual machine is loaded with all the development tools you'll need. It offers a persistent 5GB home directory and runs in Google Cloud, greatly enhancing network performance and authentication. This means that all you will need for this codelab is a browser (yes, it works on a Chromebook).

  1. To activate Cloud Shell from the Cloud Console, simply click Activate Cloud Shell FO8DjOjOwp7E_AgllbzMOCqv_SfH_lLUWN9tCFIMPwa5VZTiOY6qzPMcOUva-9jDf31uRWMLr3cWpByhh-MWFxNFka7hUxqGiuZlTQDBzJGAu6nimCRIy5adfbJpf49hB_wvafIUSw (it should only take a few moments to provision and connect to the environment).

PE_JeA9fwUGvNb-_SWAgKPgMFbmN-J7iPILKxDXJCjKJmvUlf1yVa1xFYYYK9gns1HcotuA9wl9fw2qoJU086LLY1LqnQM0f-FXaHXlRQPX3Wv3uTxCgZN-Nlxz8-otZ6VLlQVNdxQ

Screen Shot 2017-06-14 at 10.13.43 PM.png

Once connected to Cloud Shell, you should see that you are already authenticated and that the project is already set to your PROJECT_ID.

gcloud auth list

Command output

Credentialed accounts:
 - <myaccount>@<mydomain>.com (active)
gcloud config list project

Command output

[core]
project = <PROJECT_ID>

If, for some reason, the project is not set, simply issue the following command:

gcloud config set project <PROJECT_ID>

Looking for your PROJECT_ID? Check out what ID you used in the setup steps or look it up in the Cloud Console dashboard:

C-g7EL2Qa_3wZNljH4_WtymHrRniD_sBQM2ONLh6ZeEG1CZt7P0ZRCSr7zVKW9HGAcXye2XfcaVRiW6BX-Xy93QDKeXpMxm_0SzFdBo3P1A8Dwh0Abrtpp3F7sppadwXsOVtXKyIOQ

Cloud Shell also sets some environment variables by default, which may be useful as you run future commands.

echo $GOOGLE_CLOUD_PROJECT

Command output

<PROJECT_ID>
  1. Finally, set the default zone and project configuration.
gcloud config set compute/zone us-central1-f

You can choose a variety of different zones. For more information, see Regions & Zones.

Create a new Java project with Maven:

$ mvn archetype:generate -DgroupId=com.example.grpc \
 -DartifactId=grpc-hello-server \
 -DarchetypeArtifactId=maven-archetype-quickstart \
 -DinteractiveMode=false
$ cd grpc-hello-server

Add a gRPC Definition File

In gRPC, service payloads (request and response) and the service operations need to be captured in an IDL (Interface Definition Language). gRPC uses Protobuffer 3 syntax to define message payloads and operations. Let's create a proto file for a simple Greeting Service with a Hello Request and Hello Response.

First, create a new proto directory to hold the new proto file:

$ mkdir -p src/main/proto

Then, create a new proto file src/main/proto/GreetingService.proto.

You can use vim,nano,or emacs to edit the file:

src/main/proto/GreetingService.proto

syntax = "proto3";
package com.example.grpc;

// Request payload
message HelloRequest {
  // Each message attribute is strongly typed.
  // You also must assign a "tag" number.
  // Each tag number is unique within the message.
  string name = 1;

  // This defines a strongly typed list of String
  repeated string hobbies = 2;

  // There are many more basics types, like Enum, Map
  // See https://developers.google.com/protocol-buffers/docs/proto3
  // for more information.
}

message HelloResponse {
  string greeting = 1;
}

// Defining a Service, a Service can have multiple RPC operations
service GreetingService {
  // Define a RPC operation
  rpc greeting(HelloRequest) returns (HelloResponse);
}

Add gRPC Dependencies and Plugin

Once you have the definition, we can generate both the server-side stub and the client-side stub from this file. You'll need to add the gRPC dependencies and plugins.

First, add the gRPC dependencies to the pom.xml:

pom.xml

<project>
  ...
  <dependencies>
    <dependency>
      <groupId>io.grpc</groupId>
      <artifactId>grpc-netty-shaded</artifactId>
      <version>1.24.0</version>
    </dependency>
    <dependency>
      <groupId>io.grpc</groupId>
      <artifactId>grpc-protobuf</artifactId>
      <version>1.24.0</version>
    </dependency>
    <dependency>
      <groupId>io.grpc</groupId>
      <artifactId>grpc-stub</artifactId>
      <version>1.24.0</version>
    </dependency>

    ...
  </dependencies>
  ...
</project>

Then add the plugin:

pom.xml

<project>
  ...
  <dependencies>
    ...
  </dependencies>
  <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.9.0:exe:${os.detected.classifier}</protocArtifact>
          <pluginId>grpc-java</pluginId>
          <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.24.0:exe:${os.detected.classifier}</pluginArtifact>
        </configuration>
        <executions>
          <execution>
            <goals>
              <goal>compile</goal>
              <goal>compile-custom</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

</project>

Generate the Stubs

When you build the application, the plugin will convert the proto definitions into Java code.

$ mvn -DskipTests package

To see the generated files:

$ find target/generated-sources

Implement the Service

First, create a new GreetingServiceImpl class that will implement the greeting operation:

src/main/java/com/example/grpc/GreetingServiceImpl.java

package com.example.grpc;

import io.grpc.stub.StreamObserver;

public class GreetingServiceImpl extends GreetingServiceGrpc.GreetingServiceImplBase {
  @Override
  public void greeting(GreetingServiceOuterClass.HelloRequest request,
        StreamObserver<GreetingServiceOuterClass.HelloResponse> responseObserver) {
  // HelloRequest has toString auto-generated.
    System.out.println(request);

    // You must use a builder to construct a new Protobuffer object
    GreetingServiceOuterClass.HelloResponse response = GreetingServiceOuterClass.HelloResponse.newBuilder()
      .setGreeting("Hello there, " + request.getName())
      .build();

    // Use responseObserver to send a single response back
    responseObserver.onNext(response);

    // When you are done, you must call onCompleted.
    responseObserver.onCompleted();
  }
}

Implement the Server

Finally, you'll need to start a server to listen on a port and register this service implementation. Edit the App class and it's main method:

src/main/java/com/example/grpc/App.java

package com.example.grpc;

import io.grpc.*;

public class App
{
    public static void main( String[] args ) throws Exception
    {
      // Create a new server to listen on port 8080
      Server server = ServerBuilder.forPort(8080)
        .addService(new GreetingServiceImpl())
        .build();

      // Start the server
      server.start();

      // Server threads are running in the background.
      System.out.println("Server started");
      // Don't exit the main thread. Wait until server is terminated.
      server.awaitTermination();
    }
}

Finally, run the server:

$ mvn -DskipTests package exec:java -Dexec.mainClass=com.example.grpc.App
...
Server Started

The generator already generated all of the client-side stubs. For simplicity of the lab, we'll use the same Maven project, but simply add a new Client class with a new main method.

First, click on + to open a new Cloud Shell session so you don't need to terminate the server:

1ff0fda960b9adfc.png

In the new session, switch to the grpc-hello-server directory:

$ cd grpc-hello-server

Then, add the new Client class:

src/main/java/com/example/grpc/Client.java

package com.example.grpc;

import io.grpc.*;

public class Client
{
    public static void main( String[] args ) throws Exception
    {
      // Channel is the abstraction to connect to a service endpoint
      // Let's use plaintext communication because we don't have certs
      final ManagedChannel channel = ManagedChannelBuilder.forTarget("localhost:8080")
        .usePlaintext(true)
        .build();

      // It is up to the client to determine whether to block the call
      // Here we create a blocking stub, but an async stub,
      // or an async stub with Future are always possible.
      GreetingServiceGrpc.GreetingServiceBlockingStub stub = GreetingServiceGrpc.newBlockingStub(channel);
      GreetingServiceOuterClass.HelloRequest request =
        GreetingServiceOuterClass.HelloRequest.newBuilder()
          .setName("Ray")
          .build();

      // Finally, make the call using the stub
      GreetingServiceOuterClass.HelloResponse response = 
        stub.greeting(request);

      System.out.println(response);

      // A Channel should be shutdown before stopping the process.
      channel.shutdownNow();
    }
}

Finally, run the client:

$ mvn -DskipTests package exec:java -Dexec.mainClass=com.example.grpc.Client
...
greeting: "Hello there, Ray"

That's it! Pretty simple right?

There is a whole lot more you can try. For example, you can build a streaming service easily simply by adding the stream keyword in the proto file to either the request or the response parameter, e.g.

src/main/proto/GreetingService.proto

syntax = "proto3";
package com.example.grpc;

...

// Defining a Service, a Service can have multiple RPC operations
service GreetingService {
  // MODIFY HERE: Update the return to streaming return.
  rpc greeting(HelloRequest) returns (stream HelloResponse);
}

Update your server to send multiple responses rather than just one. You can do this by making multiple responseObserver.onNext(...) calls:

src/main/java/com/example/grpc/GreetingServiceImpl.java

package com.example.grpc;

import io.grpc.stub.StreamObserver;

public class GreetingServiceImpl extends GreetingServiceGrpc.GreetingServiceImplBase {
  @Override
  public void greeting(GreetingServiceOuterClass.HelloRequest request,
        StreamObserver<GreetingServiceOuterClass.HelloResponse> responseObserver) {
 
    ...

    // Feel free to construct different responses if you'd like.
    responseObserver.onNext(response);
    responseObserver.onNext(response);
    responseObserver.onNext(response);

    // When you are done, you must call onCompleted.
    responseObserver.onCompleted();
  }
}

The client must use an asynchronous stub instead of the blocking stub. Update the client code making sure to update the stub type to GreetingServiceStub:

src/main/java/com/example/grpc/Client.java

package com.example.grpc;

import io.grpc.*;

// New import
import io.grpc.stub.*;

public class Client
{
    public static void main( String[] args ) throws Exception
    {
      final ManagedChannel channel = ManagedChannelBuilder.forTarget("localhost:8080")
        .usePlaintext(true)
        .build();

      // Replace the previous synchronous code with asynchronous code.
      // This time use an async stub:
       GreetingServiceGrpc.GreetingServiceStub stub = GreetingServiceGrpc.newStub(channel);

      // Construct a request
      GreetingServiceOuterClass.HelloRequest request =
        GreetingServiceOuterClass.HelloRequest.newBuilder()
          .setName("Ray")
          .build();

      // Make an Asynchronous call. Listen to responses w/ StreamObserver
      stub.greeting(request, new StreamObserver<GreetingServiceOuterClass.HelloResponse>() {
        public void onNext(GreetingServiceOuterClass.HelloResponse response) {
          System.out.println(response);
        }
        public void onError(Throwable t) {
        }
        public void onCompleted() {
          // Typically you'll shutdown the channel somewhere else.
          // But for the purpose of the lab, we are only making a single
          // request. We'll shutdown as soon as this request is done.
          channel.shutdownNow();
        }
      });
    }
}

Rebuild the application:

$ mvn -DskipTests package

Restart both the server and the client each in its own Cloud Shell session.

To start the server:

$ mvn exec:java -Dexec.mainClass=com.example.grpc.App
...
Server Started

To start the client:

$ mvn exec:java -Dexec.mainClass=com.example.grpc.Client
...
greeting: "Hello there, Ray"
greeting: "Hello there, Ray"
greeting: "Hello there, Ray"

What we've covered:

  • The Protocol Buffer Language
  • How to implement a gRPC server using Java
  • How to implement a gRPC client using Java

Next Steps:

Give us your feedback

  • Please take a moment to complete our very short survey