Getting Started with gRPC

This page explains how to start using gRPC in your Quarkus application. While this page describes how to configure it with Maven, it is also possible to use Gradle.

Let’s imagine you have a regular Quarkus project, generated from the Quarkus project generator. The default configuration is enough, but you can also select some extensions if you want.

Solution

We recommend that you follow the instructions in the next sections and create the application step by step. However, you can go right to the completed example.

Clone the Git repository: git clone https://github.com/quarkusio/quarkus-quickstarts.git, or download an archive.

The solution is located in the grpc-plain-text-quickstart directory.

Configuring your project

Add the Quarkus gRPC extension to your build file:

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-grpc</artifactId>
</dependency>
build.gradle
implementation("io.quarkus:quarkus-grpc")

By default, the quarkus-grpc extension relies on the reactive programming model. In this guide we will follow a reactive approach. Under the dependencies section of your pom.xml file, make sure you have the RESTEasy Reactive dependency:

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-resteasy-reactive</artifactId>
</dependency>
build.gradle
implementation("io.quarkus:quarkus-resteasy-reactive")

If you are using Maven, make sure you have the generate-code goal of quarkus-maven-plugin enabled in your pom.xml. If you wish to generate code from different proto files for tests, also add the generate-code-tests goal. Please note that no additional task/goal is required for the Gradle plugin.

<build>
    <plugins>
        <plugin>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-maven-plugin</artifactId>
            <version>${quarkus-plugin.version}</version>
            <extensions>true</extensions>
            <executions>
                <execution>
                    <goals>
                        <goal>build</goal>
                        <goal>generate-code</goal>
                        <goal>generate-code-tests</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

With this configuration, you can put your service and message definitions in the src/main/proto directory. quarkus-maven-plugin will generate Java files from your proto files.

quarkus-maven-plugin retrieves a version of protoc (the protobuf compiler) from Maven repositories. The retrieved version matches your operating system and CPU architecture. If this retrieved version does not work in your context, you can either force to use a different OS classifier with -Dquarkus.grpc.protoc-os-classifier=your-os-classifier (e.g. osx-x86_64). You can also download the suitable binary and specify the location via -Dquarkus.grpc.protoc-path=/path/to/protoc.

Alternatively to using the generate-code goal of the quarkus-maven-plugin, you can use protobuf-maven-plugin to generate these files. See the Generating Java files from proto with protobuf-maven-plugin section for more information.

Let’s start with a simple Hello service. Create the src/main/proto/helloworld.proto file with the following content:

syntax = "proto3";

option java_multiple_files = true;
option java_package = "io.quarkus.example";
option java_outer_classname = "HelloWorldProto";

package helloworld;

// The greeting service definition.
service Greeter {
    // Sends a greeting
    rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
    string name = 1;
}

// The response message containing the greetings
message HelloReply {
    string message = 1;
}

This proto file defines a simple service interface with a single method (SayHello), and the exchanged messages (HelloRequest containing the name and HelloReply containing the greeting message).

Your proto file must not contain option java_generic_services = true;. Generic services are deprecated and are not compatible with Quarkus code generation plugins.

Before coding, we need to generate the classes used to implement and consume gRPC services. In a terminal, run:

$ mvn compile

Once generated, you can look at the target/generated-sources/grpc directory:

target/generated-sources/grpc
└── io
    └── quarkus
        └── example
            ├── Greeter.java
            ├── GreeterBean.java
            ├── GreeterClient.java
            ├── GreeterGrpc.java
            ├── HelloReply.java
            ├── HelloReplyOrBuilder.java
            ├── HelloRequest.java
            ├── HelloRequestOrBuilder.java
            ├── HelloWorldProto.java
            └── MutinyGreeterGrpc.java

These are the classes we are going to use.

proto files with imports

Protocol Buffers specification provides a way to import proto files. The Quarkus code generation mechanism lets you control the scope of dependencies to scan for possible imports by setting the quarkus.generate-code.grpc.scan-for-imports property to one of the following:

  • all - scan all the dependencies

  • none - don’t scan the dependencies, use only what is defined in the src/main/proto or src/test/proto

  • groupId1:artifactId1,groupId2:artifactId2 - scan only the dependencies with group id and artifact id in the list.

If not specified, the property is set to com.google.protobuf:protobuf-java. To override it, set the quarkus.generate-code.grpc.scan-for-imports property in your application.properties to the desired value, e.g.

quarkus.generate-code.grpc.scan-for-imports=all

proto files from dependencies

In some cases, you may want to use proto files from a different project to generate the gRPC stubs. In this case:

  1. Add a dependency on the artifact that contains the proto file to your project

  2. In application.properties, specify the dependencies you want to scan for proto files.

quarkus.generate-code.grpc.scan-for-proto=<groupId>:<artifactId>

The value of the property may be none, which is the default value, or a comma separated list of groupId:artifactId coordinates.

option java_generic_services = true; will automatically be removed from proto file containing it.

If the dependency contains a lot of proto files and you want to generate classes for only a subset of them, you can specify glob patterns per dependency. The path to match are relative to the path src/main/resources in the dependency.

For instance, having the following properties in your application.properties:

quarkus.generate-code.grpc.scan-for-proto-includes."<groupId>\:<artifactId>"=foo/**,bar/**,banana/a-proto.proto
quarkus.generate-code.grpc.scan-for-proto-excludes."<groupId>\:<artifactId>"=foo/private/**,bar/another-proto.proto

will include:

  • all files in the foo/ directory and its subdirectories except for files in foo/private/ and its subdirectories,

  • all files in the bar/ directory and its subdirectories except for another-proto.proto.

  • banana/a-proto.proto file.

: character in the property keys must be escaped.

Different gRPC implementations / types

Another thing to take note as well is that Quarkus' gRPC support currently includes 3 different types of gRPC usage:

  1. old Vert.x gRPC implementation with a separate gRPC server (default)

  2. new Vert.x gRPC implementation on top of the existing HTTP server

  3. xDS gRPC wrapper over grpc-java with a separate Netty based gRPC server

Further docs explain how to enable and use each of them.

Implementing a gRPC service

Now that we have the generated classes let’s implement our hello service.

With Quarkus, implementing a service requires to implement the generated service interface based on Mutiny, a Reactive Programming API integrated in Quarkus, and expose it as a CDI bean. Learn more about Mutiny on the Mutiny guide. The service class must be annotated with the @io.quarkus.grpc.GrpcService annotation.

Implementing a service

Create the src/main/java/org/acme/HelloService.java file with the following content:

package org.acme;

import io.quarkus.example.Greeter;
import io.quarkus.example.HelloReply;
import io.quarkus.example.HelloRequest;
import io.quarkus.grpc.GrpcService;
import io.smallrye.mutiny.Uni;

@GrpcService (1)
public class HelloService implements Greeter {  (2)

    @Override
    public Uni<HelloReply> sayHello(HelloRequest request) { (3)
        return Uni.createFrom().item(() ->
                HelloReply.newBuilder().setMessage("Hello " + request.getName()).build()
        );
    }
}
1 Expose your implementation as a bean.
2 Implement the generated service interface.
3 Implement the methods defined in the service definition (here we have a single method).

You can also use the default gRPC API instead of Mutiny:

package org.acme;

import io.grpc.stub.StreamObserver;
import io.quarkus.example.GreeterGrpc;
import io.quarkus.example.HelloReply;
import io.quarkus.example.HelloRequest;
import io.quarkus.grpc.GrpcService;

@GrpcService (1)
public class HelloService extends GreeterGrpc.GreeterImplBase { (2)

    @Override
    public void sayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) { (3)
        String name = request.getName();
        String message = "Hello " + name;
        responseObserver.onNext(HelloReply.newBuilder().setMessage(message).build()); (4)
        responseObserver.onCompleted(); (5)
    }
}
1 Expose your implementation as a bean.
2 Extends the ImplBase class. This is a generated class.
3 Implement the methods defined in the service definition (here we have a single method).
4 Build and send the response.
5 Close the response.
If your service implementation logic is blocking (use blocking I/O for example), annotate your method with @Blocking. The io.smallrye.common.annotation.Blocking annotation instructs the framework to invoke the annotated method on a worker thread instead of the I/O thread (event-loop).

The gRPC server

The services are served by a server. Available services (CDI beans) are automatically registered and exposed.

By default, the server is exposed on localhost:9000, and uses plain-text (so no TLS) when running normally, and localhost:9001 for tests.

Run the application using: mvn quarkus:dev.

Consuming a gRPC service

In this section, we are going to consume the service we expose. To simplify, we are going to consume the service from the same application, which in the real world, does not make sense.

Open the existing org.acme.ExampleResource class, and edit the content to become:

package org.acme;

import io.quarkus.example.Greeter;
import io.quarkus.example.HelloRequest;
import io.quarkus.grpc.GrpcClient;
import io.smallrye.mutiny.Uni;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

@Path("/hello")
public class ExampleResource {

    @GrpcClient                               (1)
    Greeter hello;                            (2)

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {
        return "hello";
    }

    @GET
    @Path("/{name}")
    public Uni<String> hello(String name) {
        return hello.sayHello(HelloRequest.newBuilder().setName(name).build())
                .onItem().transform(helloReply -> helloReply.getMessage());  (3)
    }
}
1 Inject the service and configure its name. The name is used in the application configuration. If not specified then the field name is used instead: hello in this particular case.
2 Use the generated service interface based on Mutiny API.
3 Invoke the service.

We need to configure the application to indicate where the hello service is found. In the src/main/resources/application.properties file, add the following property:

quarkus.grpc.clients.hello.host=localhost
  • hello is the name used in the @GrpcClient annotation.

  • host configures the service host (here it’s localhost).

Then, open http://localhost:8080/hello/quarkus in a browser, and you should get Hello quarkus!

Packaging the application

Like any other Quarkus applications, you can package it with: mvn package. You can also package the application into a native executable with: mvn package -Dnative.

Generating Java files from proto with protobuf-maven-plugin

Alternatively to using Quarkus code generation to generate stubs for proto files, you can also use protobuf-maven-plugin.

To do it, first define the 2 following properties in the <properties> section:

<grpc.version>1.57.2</grpc.version>
<protoc.version>3.22.0</protoc.version>

They configure the gRPC version and the protoc version.

Then, add to the build section the os-maven-plugin extension and the protobuf-maven-plugin configuration.

<build>
    <extensions>
        <extension>
            <groupId>kr.motd.maven</groupId>
            <artifactId>os-maven-plugin</artifactId>
            <version>${os-maven-plugin-version}</version>
        </extension>
    </extensions>

    <plugins>
        <plugin>
            <groupId>org.xolstice.maven.plugins</groupId>
            <artifactId>protobuf-maven-plugin</artifactId>      (1)
            <version>${protobuf-maven-plugin-version}</version>
            <configuration>
                <protocArtifact>com.google.protobuf:protoc:${protoc.version}:exe:${os.detected.classifier}</protocArtifact> (2)
                <pluginId>grpc-java</pluginId>
                <pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
                <protocPlugins>
                    <protocPlugin>
                        <id>quarkus-grpc-protoc-plugin</id>
                        <groupId>io.quarkus</groupId>
                        <artifactId>quarkus-grpc-protoc-plugin</artifactId>
                        <version>3.4.2</version>
                        <mainClass>io.quarkus.grpc.protoc.plugin.MutinyGrpcGenerator</mainClass>
                    </protocPlugin>
                </protocPlugins>
            </configuration>
            <executions>
                <execution>
                    <id>compile</id>
                    <goals>
                        <goal>compile</goal>
                        <goal>compile-custom</goal>
                    </goals>
                </execution>
                <execution>
                    <id>test-compile</id>
                    <goals>
                        <goal>test-compile</goal>
                        <goal>test-compile-custom</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>

        <!-- ... -->
    </plugins>
</build>
1 The protobuf-maven-plugin that generates stub classes from your gRPC service definition (proto files).
2 The class generation uses a tool named protoc, which is OS-specific. That’s why we use the os-maven-plugin to target the executable compatible with the operating system.
This configuration instructs the protobuf-maven-plugin to generate the default gRPC classes and classes using Mutiny to fit with the Quarkus development experience.
When using protobuf-maven-plugin, instead of the quarkus-maven-plugin, every time you update the proto files, you need to re-generate the classes (using mvn compile).

gRPC classes from dependencies

When gRPC classes - the classes generated from proto files - are in a dependency of the application, then the dependency needs a Jandex index. The jandex-maven-plugin can be used to create a Jandex index. More information on this topic can be found in the Bean Discovery section of the CDI guide.

<build>
    <plugins>
        <plugin>
            <groupId>io.smallrye</groupId>
            <artifactId>jandex-maven-plugin</artifactId>
            <version>3.1.3</version>
            <executions>
                <execution>
                <id>make-index</id>
                <goals>
                    <goal>jandex</goal>
                </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>