The English version of quarkus.io is the official project site. Translated sites are community supported on a best-effort basis.

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 io.smallrye.mutiny.Uni;

import hello.Greeter;

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;

   @GET
   @Path("/mutiny/{name}")
   public Uni<String> helloMutiny(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 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("hello") (1)
   GreeterGrpc.GreeterBlockingStub blockingHelloService;

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

处理流

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 io.smallrye.mutiny.Uni;

import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.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 streaming.source(Empty.newBuilder().build())
                .onItem().transform(Item::getValue);
    }

    @GET
    @Path("sink/{max}")
    public Uni<Void> invokeSink(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 streaming.sink(inputs).onItem().ignore().andContinueWithNull();
    }

    @GET
    @Path("/{max}")
    public Multi<String> invokePipe(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 streaming.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

Configuration property

类型

默认

If set to true, and a Stork load balancer is used, connections with all available service instances will be requested proactively. This means better load balancing at the cost of having multiple active connections.

Environment variable: QUARKUS_GRPC_CLIENT_STORK_PROACTIVE_CONNECTIONS

Show more

boolean

true

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 the @GrpcClient annotation.

When you enable quarkus.grpc.clients."client-name".xds.enabled, it’s the xDS that should handle most of the configuration above.

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 Stub Deadlines

If you need to configure a deadline for a gRPC stub, i.e. to specify a duration of time after which the stub will always return 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 stubs.
Do not use this feature to implement an RPC timeout. To implement an RPC timeout, either use Mutiny call.ifNoItem().after(…​) or Fault Tolerance @Timeout.

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 jakarta.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

A gRPC client interceptor can be implemented by a CDI bean that also implements the io.grpc.ClientInterceptor interface. You can annotate an injected client with @io.quarkus.grpc.RegisterClientInterceptor to register the specified interceptor for the particular client instance. The @RegisterClientInterceptor annotation is repeatable. Alternatively, if you want to apply the interceptor to any injected client then annotate the interceptor bean with @io.quarkus.grpc.GlobalInterceptor.

Global Client Interceptor Example
import io.quarkus.grpc.GlobalInterceptor;

import io.grpc.ClientInterceptor;

@GlobalInterceptor (1)
@ApplicationScoped
public class MyInterceptor implements ClientInterceptor {

    @Override
    public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(MethodDescriptor<ReqT, RespT> method,
            CallOptions callOptions, Channel next) {
       // ...
    }
}
1 This interceptor is applied to all injected gRPC clients.

It’s also possible to annotate a producer method as a global interceptor:

import io.quarkus.grpc.GlobalInterceptor;

import jakarta.enterprise.inject.Produces;

public class MyProducer {
    @GlobalInterceptor
    @Produces
    public MyInterceptor myInterceptor() {
        return new MyInterceptor();
    }
}
Check the ClientInterceptor JavaDoc to properly implement your interceptor.
@RegisterClientInterceptor Example
import io.quarkus.grpc.GrpcClient;
import io.quarkus.grpc.RegisterClientInterceptor;

import hello.Greeter;

@ApplicationScoped
class MyBean {

    @RegisterClientInterceptor(MySpecialInterceptor.class) (1)
    @GrpcClient("helloService")
    Greeter greeter;
}
1 Registers the MySpecialInterceptor for this particular client.

When you have multiple client interceptors, you can order them by implementing the jakarta.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.

gRPC Client metrics

Enabling metrics collection

gRPC client metrics are automatically enabled when the application also uses the quarkus-micrometer extension. Micrometer collects the metrics of all the gRPC clients used by the application.

As an example, if you export the metrics to Prometheus, you will get:

# HELP grpc_client_responses_received_messages_total The total number of responses received
# TYPE grpc_client_responses_received_messages_total counter
grpc_client_responses_received_messages_total{method="SayHello",methodType="UNARY",service="helloworld.Greeter",} 6.0
# HELP grpc_client_requests_sent_messages_total The total number of requests sent
# TYPE grpc_client_requests_sent_messages_total counter
grpc_client_requests_sent_messages_total{method="SayHello",methodType="UNARY",service="helloworld.Greeter",} 6.0
# HELP grpc_client_processing_duration_seconds The total time taken for the client to complete the call, including network delay
# TYPE grpc_client_processing_duration_seconds summary
grpc_client_processing_duration_seconds_count{method="SayHello",methodType="UNARY",service="helloworld.Greeter",statusCode="OK",} 6.0
grpc_client_processing_duration_seconds_sum{method="SayHello",methodType="UNARY",service="helloworld.Greeter",statusCode="OK",} 0.167411625
# HELP grpc_client_processing_duration_seconds_max The total time taken for the client to complete the call, including network delay
# TYPE grpc_client_processing_duration_seconds_max gauge
grpc_client_processing_duration_seconds_max{method="SayHello",methodType="UNARY",service="helloworld.Greeter",statusCode="OK",} 0.136478028

The service name, method and type can be found in the tags.

Disabling metrics collection

To disable the gRPC client metrics when quarkus-micrometer is used, add the following property to the application configuration:

quarkus.micrometer.binder.grpc-client.enabled=false

Custom exception handling

If any of the gRPC services or server interceptors throw an (custom) exception, you can add your own ExceptionHandlerProvider as a CDI bean in your application, to provide a custom handling of those exceptions.

e.g.

@ApplicationScoped
public class HelloExceptionHandlerProvider implements ExceptionHandlerProvider {
    @Override
    public <ReqT, RespT> ExceptionHandler<ReqT, RespT> createHandler(ServerCall.Listener<ReqT> listener,
            ServerCall<ReqT, RespT> serverCall, Metadata metadata) {
        return new HelloExceptionHandler<>(listener, serverCall, metadata);
    }

    @Override
    public Throwable transform(Throwable t) {
        if (t instanceof HelloException he) {
            return new StatusRuntimeException(Status.ABORTED.withDescription(he.getName()));
        } else {
            return ExceptionHandlerProvider.toStatusException(t, true);
        }
    }

    private static class HelloExceptionHandler<A, B> extends ExceptionHandler<A, B> {
        public HelloExceptionHandler(ServerCall.Listener<A> listener, ServerCall<A, B> call, Metadata metadata) {
            super(listener, call, metadata);
        }

        @Override
        protected void handleException(Throwable t, ServerCall<A, B> call, Metadata metadata) {
            StatusRuntimeException sre = (StatusRuntimeException) ExceptionHandlerProvider.toStatusException(t, true);
            Metadata trailers = sre.getTrailers() != null ? sre.getTrailers() : metadata;
            call.close(sre.getStatus(), trailers);
        }
    }
}

Dev Mode

By default, when starting the application in dev mode, a gRPC server is started, even if no services are configured. You can configure the gRPC extension’s dev mode behavior using the following properties.

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

Configuration property

类型

默认

Start gRPC server in dev mode even if no gRPC services are implemented. By default set to true to ease incremental development of new services using dev mode.

Environment variable: QUARKUS_GRPC_DEV_MODE_FORCE_SERVER_START

Show more

boolean

true

Inject mock clients

In your @QuarkusTest, you can use @InjectMock to inject the Mutiny client of a gRPC service:

@QuarkusTest
public class GrpcMockTest {

    @InjectMock
    @GrpcClient("hello")
    Greeter greeter;

    @Test
    void test1() {
        HelloRequest request = HelloRequest.newBuilder().setName("neo").build();
        Mockito.when(greeter.sayHello(Mockito.any(HelloRequest.class)))
                .thenReturn(Uni.createFrom().item(HelloReply.newBuilder().setMessage("hello neo").build()));
        Assertions.assertEquals(greeter.sayHello(request).await().indefinitely().getMessage(), "hello neo");
    }
}
Only the Mutiny client can be mocked, channels, and other stubs cannot be mocked.

Related content