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:
-
A service interface using the Mutiny API
-
the class name is
${JAVA_PACKAGE}.${NAME_OF_THE_SERVICE}
-
-
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 jakarta.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
.
Quarkus exposes both the reflection service v1 and v1alpha .
|
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
Type |
Default |
|
---|---|---|
Do we use separate HTTP server to serve gRPC requests. Set this to false if you want to use new Vert.x gRPC support, which uses existing Vert.x HTTP server. Environment variable: Show more |
boolean |
|
Explicitly enable use of XDS. Environment variable: Show more |
boolean |
|
Use secure credentials. Environment variable: Show more |
boolean |
|
Explicitly enable use of in-process. Environment variable: Show more |
boolean |
|
Set in-process name. Environment variable: Show more |
string |
|
The gRPC Server port. Environment variable: Show more |
int |
|
The gRPC Server port used for tests. Environment variable: Show more |
int |
|
The gRPC server host. Environment variable: Show more |
string |
|
The gRPC handshake timeout. Environment variable: Show more |
||
The max inbound message size in bytes. Environment variable: Show more |
int |
|
The max inbound metadata size in bytes Environment variable: Show more |
int |
|
The classpath path or file path to a server certificate or certificate chain in PEM format. Environment variable: Show more |
path |
|
The classpath path or file path to the corresponding certificate private key file in PEM format. Environment variable: Show more |
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. Environment variable: Show more |
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. Environment variable: Show more |
string |
|
A parameter to specify the password of the key store file. If not given, the default ("password") is used. Environment variable: Show more |
string |
|
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. Environment variable: Show more |
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. Environment variable: Show more |
string |
|
A parameter to specify the password of the trust store file. Environment variable: Show more |
string |
|
The cipher suites to use. If none is given, a reasonable default is selected. Environment variable: Show more |
list of string |
|
Sets the ordered list of enabled SSL/TLS protocols. If not set, it defaults to Note that setting an empty list, and enabling SSL/TLS is invalid. You must at least have one protocol. Environment variable: Show more |
list of string |
|
Configures the engine to require/request client authentication. NONE, REQUEST, REQUIRED Environment variable: Show more |
|
|
Disables SSL, and uses plain text instead. If disabled, configure the ssl configuration. Environment variable: Show more |
boolean |
|
Whether ALPN should be used. Environment variable: Show more |
boolean |
|
The path to the certificate file. Environment variable: Show more |
string |
|
The path to the private key file. Environment variable: Show more |
string |
|
Enables the gRPC Reflection Service. By default, the reflection service is only exposed in Environment variable: Show more |
boolean |
|
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. Environment variable: Show more |
int |
|
Sets a custom keep-alive duration. This configures the time before sending a Environment variable: Show more |
||
Sets a custom permit-keep-alive duration. This configures the most aggressive keep-alive time clients are permitted to configure. The server will try to detect clients exceeding this rate and when detected will forcefully close the connection. Environment variable: Show more |
||
Sets whether to allow clients to send keep-alive HTTP/2 PINGs even if there are no outstanding RPCs on the connection. Environment variable: Show more |
boolean |
|
gRPC compression, e.g. "gzip" Environment variable: Show more |
string |
About the Duration format
To write duration values, use the standard You can also use a simplified format, starting with a number:
In other cases, the simplified format is translated to the
|
When you disable quarkus.grpc.server.use-separate-server , you are then
using the new Vert.x gRPC server implementation which uses the existing HTTP
server. Which means that the server port is now 8080 (or the port
configured with quarkus.http.port ). Also, most of the other configuration
properties are no longer applied, since it’s the HTTP server that should
already be properly configured.
|
When you enable quarkus.grpc.server.xds.enabled , it’s the xDS that should
handle most of the configuration above.
|
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) {
// ...
}
}
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 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 jakarta.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() {
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");
}
}
Trying out your services manually
In the dev mode, you can try out your gRPC services in the Quarkus Dev UI. Just go to http://localhost:8080/q/dev-ui and click on Services under the gRPC tile.
Please note that your application needs to expose the "normal" HTTP port for
the Dev UI to be accessible. If your application does not expose any HTTP
endpoints, you can create a dedicated profile with a dependency on
quarkus-vertx-http
:
<profiles>
<profile>
<id>development</id>
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-vertx-http</artifactId>
</dependency>
</dependencies>
</profiles>
</profile>
Having it, you can run the dev mode with: mvn quarkus:dev -Pdevelopment
.
If you use Gradle, you can simply add a dependency for the quarkusDev
task:
dependencies {
quarkusDev 'io.quarkus:quarkus-vertx-http'
}
gRPC Server metrics
Enabling metrics collection
gRPC server metrics are automatically enabled when the application also uses
the quarkus-micrometer
extension.
Micrometer collects the metrics of all the gRPC services implemented by the
application.
As an example, if you export the metrics to Prometheus, you will get:
# HELP grpc_server_responses_sent_messages_total The total number of responses sent
# TYPE grpc_server_responses_sent_messages_total counter
grpc_server_responses_sent_messages_total{method="SayHello",methodType="UNARY",service="helloworld.Greeter",} 6.0
# HELP grpc_server_processing_duration_seconds The total time taken for the server to complete the call
# TYPE grpc_server_processing_duration_seconds summary
grpc_server_processing_duration_seconds_count{method="SayHello",methodType="UNARY",service="helloworld.Greeter",statusCode="OK",} 6.0
grpc_server_processing_duration_seconds_sum{method="SayHello",methodType="UNARY",service="helloworld.Greeter",statusCode="OK",} 0.016216771
# HELP grpc_server_processing_duration_seconds_max The total time taken for the server to complete the call
# TYPE grpc_server_processing_duration_seconds_max gauge
grpc_server_processing_duration_seconds_max{method="SayHello",methodType="UNARY",service="helloworld.Greeter",statusCode="OK",} 0.007985236
# HELP grpc_server_requests_received_messages_total The total number of requests received
# TYPE grpc_server_requests_received_messages_total counter
grpc_server_requests_received_messages_total{method="SayHello",methodType="UNARY",service="helloworld.Greeter",} 6.0
The service name, method and type can be found in the tags.
Disabling metrics collection
To disable the gRPC server metrics when quarkus-micrometer
is used, add
the following property to the application configuration:
quarkus.micrometer.binder.grpc-server.enabled=false
Use virtual threads
To use virtual threads in your gRPC service implementation, check the dedicated guide.