Implementing a gRPC Service

gRPC service implementations exposed as CDI beans are automatically registered and served by quarkus-grpc.

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

Generated Code

Quarkus generates a few implementation classes for services declared in the proto file:

  1. A service interface using the Mutiny API

    • the class name is ${JAVA_PACKAGE}.${NAME_OF_THE_SERVICE}

  2. An implementation base class using the gRPC API

    • the class name is structured as follows: ${JAVA_PACKAGE}.${NAME_OF_THE_SERVICE}Grpc.${NAME_OF_THE_SERVICE}ImplBase

For example, if you use the following proto file snippet:

option java_package = "hello"; (1)

service Greeter { (2)
    rpc SayHello (HelloRequest) returns (HelloReply) {}
}
1 hello is the java package for the generated classes.
2 Greeter is the service name.

Then the service interface is hello.Greeter and the implementation base is the abstract static nested class: hello.GreeterGrpc.GreeterImplBase.

You’ll need to implement the service interface or extend the base class with your service implementation bean as described in the following sections.

Implementing a Service with the Mutiny API

To implement a gRPC service using the Mutiny API, create a class that implements the service interface. Then, implement the methods defined in the service interface. If you don’t want to implement a service method just throw an java.lang.UnsupportedOperationException from the method body (the exception will be automatically converted to the appropriate gRPC exception). Finally, implement the service and add the @GrpcService annotation:

import io.quarkus.grpc.GrpcService;
import hello.Greeter;

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

    @Override
    public Uni<HelloReply> sayHello(HelloRequest request) {
        return Uni.createFrom().item(() ->
                HelloReply.newBuilder().setMessage("Hello " + request.getName()).build()
        );
    }
}
1 A gRPC service implementation bean must be annotated with the @GrpcService annotation and should not declare any other CDI qualifier. All gRPC services have the javax.inject.Singleton scope. Additionally, the request context is always active during a service call.
2 hello.Greeter is the generated service interface.
The service implementation bean can also extend the Mutiny implementation base, where the class name is structured as follows: Mutiny${NAME_OF_THE_SERVICE}Grpc.${NAME_OF_THE_SERVICE}ImplBase.

Implementing a Service with the default gRPC API

To implement a gRPC service using the default gRPC API, create a class that extends the default implementation base. Then, override the methods defined in the service interface. Finally, implement the service and add the @GrpcService annotation:

import io.quarkus.grpc.GrpcService;

@GrpcService
public class HelloService extends GreeterGrpc.GreeterImplBase {

    @Override
    public void sayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {
        String name = request.getName();
        String message = "Hello " + name;
        responseObserver.onNext(HelloReply.newBuilder().setMessage(message).build());
        responseObserver.onCompleted();
    }
}

Blocking Service Implementation

By default, all the methods from a gRPC service run on the event loop. As a consequence, you must not block. If your service logic must block, annotate the method with io.smallrye.common.annotation.Blocking:

@Override
@Blocking
public Uni<HelloReply> sayHelloBlocking(HelloRequest request) {
    // Do something blocking before returning the Uni
}

Handling Streams

gRPC allows receiving and returning 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 Mutiny, you can implement these as follows:

import io.quarkus.grpc.GrpcService;

@GrpcService
public class StreamingService implements Streaming {

    @Override
    public Multi<Item> source(Empty request) {
        // Just returns a stream emitting an item every 2ms and stopping after 10 items.
        return Multi.createFrom().ticks().every(Duration.ofMillis(2))
                .select().first(10)
                .map(l -> Item.newBuilder().setValue(Long.toString(l)).build());
    }

    @Override
    public Uni<Empty> sink(Multi<Item> request) {
        // Reads the incoming streams, consume all the items.
        return request
                .map(Item::getValue)
                .map(Long::parseLong)
                .collect().last()
                .map(l -> Empty.newBuilder().build());
    }

    @Override
    public Multi<Item> pipe(Multi<Item> request) {
        // Reads the incoming stream, compute a sum and return the cumulative results
        // in the outbound stream.
        return request
                .map(Item::getValue)
                .map(Long::parseLong)
                .onItem().scan(() -> 0L, Long::sum)
                .onItem().transform(l -> Item.newBuilder().setValue(Long.toString(l)).build());
    }
}

Health Check

For the implemented services, Quarkus gRPC exposes health information in the following format:

syntax = "proto3";

package grpc.health.v1;

message HealthCheckRequest {
  string service = 1;
}

message HealthCheckResponse {
  enum ServingStatus {
    UNKNOWN = 0;
    SERVING = 1;
    NOT_SERVING = 2;
  }
  ServingStatus status = 1;
}

service Health {
  rpc Check(HealthCheckRequest) returns (HealthCheckResponse);

  rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse);
}

Clients can specify the fully qualified service name to get the health status of a specific service or skip specifying the service name to get the general status of the gRPC server.

For more details, check out the gRPC documentation

Additionally, if Quarkus SmallRye Health is added to the application, a readiness check for the state of the gRPC services will be added to the MicroProfile Health endpoint response, that is /q/health.

Reflection Service

Quarkus gRPC Server implements the reflection service. This service allows tools like grpcurl or grpcox to interact with your services.

The reflection service is enabled by default in dev mode. In test or production mode, you need to enable it explicitly by setting quarkus.grpc.server.enable-reflection-service to true.

Scaling

By default, quarkus-grpc starts a single gRPC server running on a single event loop.

If you wish to scale your server, you can set the number of server instances by setting quarkus.grpc.server.instances.

Server Configuration

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

Configure the gRPC server

Type

Default

The gRPC Server port.

int

9000

The gRPC Server port used for tests.

int

9001

The gRPC server host.

string

0.0.0.0

The gRPC handshake timeout.

Duration

The max inbound message size in bytes.

int

The max inbound metadata size in bytes

int

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 key store which holds the certificate information instead of specifying separate files. The key store can be either on classpath or an external file.

path

An optional parameter to specify the type of the key store file. If not given, the type is automatically detected based on the file name.

string

A parameter to specify the password of the key store file. If not given, the default ("password") is used.

string

password

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

path

An optional parameter to specify type of the trust store file. If not given, the type is automatically detected based on the file name.

string

A parameter to specify the password of the trust store file.

string

The cipher suites to use. If none is given, a reasonable default is selected.

list of string

The list of protocols to explicitly enable.

list of string

TLSv1.3,TLSv1.2

Configures the engine to require/request client authentication. NONE, REQUEST, REQUIRED

none, request, required

none

Disables SSL, and uses plain text instead. If disabled, configure the ssl configuration.

boolean

true

Whether ALPN should be used.

boolean

true

The path to the certificate file.

string

The path to the private key file.

string

Enables the gRPC Reflection Service. By default, the reflection service is only exposed in dev mode. This setting allows overriding this choice and enable the reflection service every time.

boolean

false

Number of gRPC server verticle instances. This is useful for scaling easily across multiple cores. The number should not exceed the amount of event loops.

int

1

Sets a custom keep-alive duration. This configures the time before sending a keepalive ping when there is no read activity.

Duration

gRPC compression, e.g. "gzip"

string

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.

Example of Configuration

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.server.ssl.certificate=tls/server.pem
quarkus.grpc.server.ssl.key=tls/server.key
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.server.ssl.certificate=tls/server.pem
quarkus.grpc.server.ssl.key=tls/server.key
quarkus.grpc.server.ssl.trust-store=tls/ca.jks
quarkus.grpc.server.ssl.trust-store-password=*****
quarkus.grpc.server.ssl.client-auth=REQUIRED

Server Interceptors

gRPC server interceptors let you perform logic, such as authentication, before your service is invoked.

You can implement a gRPC server interceptor by creating an @ApplicationScoped bean implementing io.grpc.ServerInterceptor:

@ApplicationScoped
// add @GlobalInterceptor for interceptors meant to be invoked for every service
public class MyInterceptor implements ServerInterceptor {

    @Override
    public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> serverCall,
            Metadata metadata, ServerCallHandler<ReqT, RespT> serverCallHandler) {
        // ...
    }
}
Check the ServerInterceptor JavaDoc to properly implement your interceptor.

To apply an interceptor to all exposed services, annotate it with @io.quarkus.grpc.GlobalInterceptor. To apply an interceptor to a single service, register it on the service with @io.quarkus.grpc.RegisterInterceptor:

import io.quarkus.grpc.GrpcService;
import io.quarkus.grpc.RegisterInterceptor;

@GrpcService
@RegisterInterceptor(MyInterceptor.class)
public class StreamingService implements Streaming {
    // ...
}

When you have multiple server interceptors, you can order them by implementing the javax.enterprise.inject.spi.Prioritized interface. Please note that all the global interceptors are invoked before the service-specific interceptors.

@ApplicationScoped
public class MyInterceptor implements ServerInterceptor, Prioritized {

    @Override
    public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> serverCall,
            Metadata metadata, ServerCallHandler<ReqT, RespT> serverCallHandler) {
        // ...
    }

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

Testing your services

The easiest way to test a gRPC service is to use a gRPC client as described in Consuming a gRPC Service.

Please note that in the case of using a client to test an exposed service that does not use TLS, there is no need to provide any configuration. E.g. to test the HelloService defined above, one could create the following test:

public class HelloServiceTest implements Greeter {

    @GrpcClient
    Greeter client;

    @Test
    void shouldReturnHello() throws Exception {
        CompletableFuture<String> message = new CompletableFuture<>();
        client.sayHello(HelloRequest.newBuilder().setName("Quarkus").build())
                .subscribe().with(reply -> message.complete(reply.getMessage()));
        assertThat(message.get(5, TimeUnit.SECONDS)).isEqualTo("Hello Quarkus");
    }
}