Consuming a gRPC Service

gRPC clients can be injected in your application code.

Consuming gRPC services requires the gRPC classes to be generated. Place your proto files in src/main/proto and run mvn compile.

Stubs and Injection

gRPC generation provides several stubs, providing different ways to consume a gRPC service. You can inject:

  • a service interface using the Mutiny API,

  • a blocking stub using the gRPC API,

  • a reactive stub based on Mutiny,

  • the gRPC io.grpc.Channel, that lets you create other types of stubs.

import io.quarkus.grpc.GrpcClient;

import hello.Greeter;
import hello.GreeterGrpc.GreeterBlockingStub;
import hello.MutinyGreeterGrpc.MutinyGreeterStub;

class MyBean {

   // A service interface using the Mutiny API
   @GrpcClient("helloService")                   (1)
   Greeter greeter;

   // A reactive stub based on Mutiny
   @GrpcClient("helloService")
   MutinyGreeterGrpc.MutinyGreeterStub mutiny;

   // A blocking stub using the gRPC API
   @GrpcClient
   GreeterGrpc.GreeterBlockingStub helloService; (2)

   @GrpcClient("hello-service")
   Channel channel;

}
1 A gRPC client injection point must be annotated with the @GrpcClient qualifier. This qualifier can be used to specify the name that is used to configure the underlying gRPC client. For example, if you set it to hello-service, configuring the host of the service is done using the quarkus.grpc.clients.hello-service.host.
2 If the name is not specified via the GrpcClient#value() then the field name is used instead, e.g. helloService in this particular case.

The stub class names are derived from the service name used in your proto file. For example, if you use Greeter as a service name as in:

option java_package = "hello";

service Greeter {
    rpc SayHello (HelloRequest) returns (HelloReply) {}
}

Then the service interface name is: hello.Greeter, the Mutiny stub name is: hello.MutinyGreeterGrpc.MutinyGreeterStub and the blocking stub name is: hello.GreeterGrpc.GreeterBlockingStub.

Examples

Service Interface

import io.quarkus.grpc.GrpcClient;

import hello.Greeter;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

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

   @GrpcClient (1)
   Greeter hello;

   @GET
   @Path("/mutiny/{name}")
   public Uni<String> helloMutiny(@PathParam("name") String name) {
      return hello.sayHello(HelloRequest.newBuilder().setName(name).build())
            .onItem().transform(HelloReply::getMessage);
   }
}
1 The service name is derived from the injection point - the field name is used. The quarkus.grpc.clients.hello.host property must be set.

Blocking Stub

---
import io.quarkus.grpc.GrpcClient;

import hello.GreeterGrpc.GreeterBlockingStub;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

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

   @GrpcClient("hello") (1)
   GreeterGrpc.GreeterBlockingStub blockingHelloService;

   @GET
   @Path("/blocking/{name}")
   public String helloBlocking(@PathParam("name") String name) {
      return blockingHelloService.sayHello(HelloRequest.newBuilder().setName(name).build()).getMessage();
   }
}
1 The quarkus.grpc.clients.hello.host property must be set.

Handling streams

gRPC allows sending and receiving streams:

service Streaming {
    rpc Source(Empty) returns (stream Item) {} // Returns a stream
    rpc Sink(stream Item) returns (Empty) {}   // Reads a stream
    rpc Pipe(stream Item) returns (stream Item) {}  // Reads a streams and return a streams
}

Using the Mutiny stub, you can interact with these as follows:

package io.quarkus.grpc.example.streaming;

import io.grpc.examples.streaming.Empty;
import io.grpc.examples.streaming.Item;
import io.grpc.examples.streaming.MutinyStreamingGrpc;
import io.quarkus.grpc.GrpcClient;

import io.smallrye.mutiny.Multi;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("/streaming")
@Produces(MediaType.APPLICATION_JSON)
public class StreamingEndpoint {

    @GrpcClient
    MutinyStreamingGrpc.MutinyStreamingStub streaming;

    @GET
    public Multi<String> invokeSource() {
        // Retrieve a stream
        return client.source(Empty.newBuilder().build())
                .onItem().transform(Item::getValue);
    }

    @GET
    @Path("sink/{max}")
    public Uni<Void> invokeSink(@PathParam("max") int max) {
        // Send a stream and wait for completion
        Multi<Item> inputs = Multi.createFrom().range(0, max)
                .map(i -> Integer.toString(i))
                .map(i -> Item.newBuilder().setValue(i).build());
        return client.sink(inputs).onItem().ignore().andContinueWithNull();
    }

    @GET
    @Path("/{max}")
    public Multi<String> invokePipe(@PathParam("max") int max) {
        // Send a stream and retrieve a stream
        Multi<Item> inputs = Multi.createFrom().range(0, max)
                .map(i -> Integer.toString(i))
                .map(i -> Item.newBuilder().setValue(i).build());
        return client.pipe(inputs).onItem().transform(Item::getValue);
    }

}

Client configuration

For each gRPC service you inject in your application, you can configure the following attributes:

Configuration property fixed at build time - All other configuration properties are overridable at runtime

Configures the gRPC clients

Type

Default

int

9000

The host name / IP on which the service is exposed.

string

required

The classpath path or file path to a server certificate or certificate chain in PEM format.

path

The classpath path or file path to the corresponding certificate private key file in PEM format.

path

An optional trust store which holds the certificate information of the certificates to trust The trust store can be either on classpath or in an external file.

path

Whether plain-text should be used instead of TLS. Enables by default, except it TLS/SSL is configured. In this case, plain-text is disabled.

boolean

The duration after which a keep alive ping is sent.

Duration

The flow control window in bytes. Default is 1MiB.

int

The duration without ongoing RPCs before going to idle mode.

Duration

The amount of time the sender of of a keep alive ping waits for an acknowledgement.

Duration

Whether keep-alive will be performed when there are no outstanding RPC on a connection.

boolean

false

The max number of hedged attempts.

int

5

The max number of retry attempts. Retry must be explicitly enabled.

int

5

The maximum number of channel trace events to keep in the tracer for each channel or sub-channel.

int

The maximum message size allowed for a single gRPC frame (in bytes). Default is 4 MiB.

int

The maximum size of metadata allowed to be received (in bytes). Default is 8192B.

int

The negotiation type for the HTTP/2 connection. Accepted values are: TLS, PLAINTEXT_UPGRADE, PLAINTEXT

string

TLS

Overrides the authority used with TLS and HTTP virtual hosting.

string

The per RPC buffer limit in bytes used for retry.

long

Whether retry is enabled. Note that retry is disabled by default.

boolean

false

long

string

Use a custom load balancing policy. Accepted values are: pick_value, round_robin, grpclb

string

pick_first

The compression to use for each call. The accepted values are gzip and identity.

string

The deadline used for each call. The format uses the standard java.time.Duration format. You can also provide duration values starting with a number. In this case, if the value consists only of a number, the converter treats the value as seconds. Otherwise, PT is implicitly prepended to the value to obtain a standard java.time.Duration format.

Duration

About the Duration format

The format for durations uses the standard java.time.Duration format. You can learn more about it in the Duration#parse() javadoc.

You can also provide duration values starting with a number. In this case, if the value consists only of a number, the converter treats the value as seconds. Otherwise, PT is implicitly prepended to the value to obtain a standard java.time.Duration format.

The client-name is the name set in the @GrpcClient or derived from the injection point if not explicitly defined.

The following examples uses hello as the client name. Don’t forget to replace it with the name you used in in the @GrpcClient annotation.

Enabling TLS

To enable TLS, use the following configuration. Note that all paths in the configuration may either specify a resource on the classpath (typically from src/main/resources or its subfolder) or an external file.

quarkus.grpc.clients.hello.host=localhost

# either a path to a classpath resource or to a file:
quarkus.grpc.clients.hello.ssl.trust-store=tls/ca.pem
When SSL/TLS is configured, plain-text is automatically disabled.

TLS with Mutual Auth

To use TLS with mutual authentication, use the following configuration:

quarkus.grpc.clients.hello.host=localhost
quarkus.grpc.clients.hello.plain-text=false

# all the following may use either a path to a classpath resource or to a file:
quarkus.grpc.clients.hello.ssl.certificate=tls/client.pem
quarkus.grpc.clients.hello.ssl.key=tls/client.key
quarkus.grpc.clients.hello.ssl.trust-store=tls/ca.pem

Client Deadlines

It’s always reasonable to set a deadline (timeout) for a gRPC client, i.e. to specify a duration of time after which the RPC times out and the client receives the status error DEADLINE_EXCEEDED. You can specify the deadline via the quarkus.grpc.clients."service-name".deadline configuration property, e.g.:

quarkus.grpc.clients.hello.host=localhost
quarkus.grpc.clients.hello.deadline=2s (1)
1 Set the deadline for all injected clients.

gRPC Headers

Similarly to HTTP, alongside the message, gRPC calls can carry headers. Headers can be useful e.g. for authentication.

To set headers for a gRPC call, create a client with headers attached and then perform the call on this client:

import javax.enterprise.context.ApplicationScoped;

import examples.Greeter;
import examples.HelloReply;
import examples.HelloRequest;
import io.grpc.Metadata;
import io.quarkus.grpc.GrpcClient;
import io.quarkus.grpc.GrpcClientUtils;
import io.smallrye.mutiny.Uni;

@ApplicationScoped
public class MyService {
    @GrpcClient
    Greeter client;

    public Uni<HelloReply> doTheCall() {
        Metadata extraHeaders = new Metadata();
        if (headers) {
            extraHeaders.put("my-header", "my-interface-value");
        }

        Greeter alteredClient = GrpcClientUtils.attachHeaders(client, extraHeaders); (1)

        return alteredClient.sayHello(HelloRequest.newBuilder().setName(name).build()); (2)
    }
}
1 Alter the client to make calls with the extraHeaders attached
2 Perform the call with the altered client. The original client remains unmodified

GrpcClientUtils work with all flavors of clients.

Client Interceptors

You can implement a gRPC client interceptor by implementing an @ApplicationScoped bean implementing io.grpc.ClientInterceptor:

@ApplicationScoped
public class MyInterceptor implements ClientInterceptor {

    @Override
    public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(MethodDescriptor<ReqT, RespT> method,
            CallOptions callOptions, Channel next) {
       // ...
    }
}
Check the ClientInterceptor JavaDoc to properly implement your interceptor.

When you have multiple client interceptors, you can order them by implementing the javax.enterprise.inject.spi.Prioritized interface:

@ApplicationScoped
public class MyInterceptor implements ClientInterceptor, Prioritized {

    @Override
    public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(MethodDescriptor<ReqT, RespT> method,
            CallOptions callOptions, Channel next) {
       // ...
    }

    @Override
    public int getPriority() {
        return 10;
    }
}

Interceptors with the highest priority are called first. The default priority, used if the interceptor does not implement the Prioritized interface, is 0.