Quarkus gRPC extension that enables implementing and consuming gRPC services with reactive and imperative programming models.
—
Interceptor system for implementing cross-cutting concerns like authentication, logging, and metrics across gRPC services and clients. Interceptors can be registered globally or for specific services/clients.
Denotes an interceptor that should be registered for all gRPC services or all injected gRPC clients. The interceptor will be automatically applied without explicit registration.
/**
* Denotes a {@link io.grpc.ServerInterceptor} that should be registered for all gRPC services, or a
* {@link io.grpc.ClientInterceptor} that should be registered for all injected gRPC clients.
*
* @see RegisterInterceptor
* @see RegisterClientInterceptor
*/
@Target({ FIELD, PARAMETER, TYPE, METHOD })
@Retention(RUNTIME)
public @interface GlobalInterceptor {
}Usage Examples:
import io.quarkus.grpc.GlobalInterceptor;
import io.grpc.ServerInterceptor;
import io.grpc.Metadata;
import io.grpc.ServerCall;
import io.grpc.ServerCallHandler;
import jakarta.enterprise.context.ApplicationScoped;
// Global server interceptor
@GlobalInterceptor
@ApplicationScoped
public class LoggingServerInterceptor implements ServerInterceptor {
@Override
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
ServerCall<ReqT, RespT> call,
Metadata headers,
ServerCallHandler<ReqT, RespT> next) {
String methodName = call.getMethodDescriptor().getFullMethodName();
System.out.println("Received call to: " + methodName);
return next.startCall(call, headers);
}
}
// Global client interceptor
@GlobalInterceptor
@ApplicationScoped
public class AuthClientInterceptor implements ClientInterceptor {
@Override
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
MethodDescriptor<ReqT, RespT> method,
CallOptions callOptions,
Channel next) {
return new ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(
next.newCall(method, callOptions)) {
@Override
public void start(Listener<RespT> responseListener, Metadata headers) {
// Add auth token to all outgoing calls
Key<String> authKey = Key.of("authorization", Metadata.ASCII_STRING_MARSHALLER);
headers.put(authKey, "Bearer " + getAuthToken());
super.start(responseListener, headers);
}
};
}
}Registers a ServerInterceptor for a particular gRPC service. This annotation is repeatable, allowing multiple interceptors to be registered for the same service.
/**
* Registers a {@link ServerInterceptor} for a particular gRPC service.
*
* @see GlobalInterceptor
*/
@Repeatable(RegisterInterceptors.class)
@Target(TYPE)
@Retention(RUNTIME)
public @interface RegisterInterceptor {
Class<? extends ServerInterceptor> value();
}
/**
* Container annotation for repeatable {@link RegisterInterceptor} annotations.
*/
@Target(TYPE)
@Retention(RUNTIME)
@Documented
public @interface RegisterInterceptors {
RegisterInterceptor[] value();
}Usage Examples:
import io.quarkus.grpc.RegisterInterceptor;
import io.quarkus.grpc.GrpcService;
// Single interceptor
@RegisterInterceptor(AuthInterceptor.class)
@GrpcService
public class SecureService implements MutinyService {
public Uni<SecureResponse> secureMethod(SecureRequest request) {
// This method will be intercepted by AuthInterceptor
return Uni.createFrom().item(SecureResponse.newBuilder().build());
}
}
// Multiple interceptors
@RegisterInterceptor(LoggingInterceptor.class)
@RegisterInterceptor(MetricsInterceptor.class)
@RegisterInterceptor(AuthInterceptor.class)
@GrpcService
public class FullyInterceptedService implements MutinyService {
public Uni<Response> method(Request request) {
// Interceptors will be applied in registration order
return Uni.createFrom().item(Response.newBuilder().build());
}
}Registers a ClientInterceptor for an injected gRPC client. This annotation is repeatable and supports programmatic creation.
/**
* Registers a {@link ClientInterceptor} for an injected gRPC client.
*
* @see GlobalInterceptor
*/
@Qualifier
@Repeatable(RegisterClientInterceptor.List.class)
@Target({ FIELD, PARAMETER })
@Retention(RUNTIME)
public @interface RegisterClientInterceptor {
Class<? extends ClientInterceptor> value();
@Target({ FIELD, PARAMETER })
@Retention(RUNTIME)
@interface List {
RegisterClientInterceptor[] value();
}
final class Literal extends AnnotationLiteral<RegisterClientInterceptor>
implements RegisterClientInterceptor {
private static final long serialVersionUID = 1L;
private final Class<? extends ClientInterceptor> value;
public static Literal of(Class<? extends ClientInterceptor> value);
public Class<? extends ClientInterceptor> value();
}
}Usage Examples:
import io.quarkus.grpc.RegisterClientInterceptor;
import io.quarkus.grpc.GrpcClient;
public class ClientWithInterceptors {
// Single client interceptor
@Inject
@GrpcClient("user-service")
@RegisterClientInterceptor(RetryInterceptor.class)
MutinyUserServiceGrpc userClient;
// Multiple client interceptors
@Inject
@GrpcClient("payment-service")
@RegisterClientInterceptor(AuthInterceptor.class)
@RegisterClientInterceptor(LoggingInterceptor.class)
MutinyPaymentServiceGrpc paymentClient;
// Programmatic interceptor registration
public void registerInterceptorProgrammatically() {
var literal = RegisterClientInterceptor.Literal.of(CustomInterceptor.class);
// Use literal in CDI lookup
}
}@ApplicationScoped
public class AuthInterceptor implements ServerInterceptor {
@Override
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
ServerCall<ReqT, RespT> call,
Metadata headers,
ServerCallHandler<ReqT, RespT> next) {
Key<String> authKey = Key.of("authorization", Metadata.ASCII_STRING_MARSHALLER);
String token = headers.get(authKey);
if (token == null || !isValidToken(token)) {
call.close(Status.UNAUTHENTICATED.withDescription("Invalid or missing auth token"),
new Metadata());
return new ServerCall.Listener<ReqT>() {};
}
return next.startCall(call, headers);
}
private boolean isValidToken(String token) {
return token.startsWith("Bearer ") && validateJwtToken(token.substring(7));
}
}@ApplicationScoped
public class LoggingInterceptor implements ServerInterceptor {
private static final Logger logger = LoggerFactory.getLogger(LoggingInterceptor.class);
@Override
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
ServerCall<ReqT, RespT> call,
Metadata headers,
ServerCallHandler<ReqT, RespT> next) {
String methodName = call.getMethodDescriptor().getFullMethodName();
long startTime = System.currentTimeMillis();
logger.info("Starting gRPC call: {}", methodName);
return new ForwardingServerCallListener.SimpleForwardingServerCallListener<ReqT>(
next.startCall(call, headers)) {
@Override
public void onComplete() {
long duration = System.currentTimeMillis() - startTime;
logger.info("Completed gRPC call: {} in {}ms", methodName, duration);
super.onComplete();
}
@Override
public void onCancel() {
logger.warn("Cancelled gRPC call: {}", methodName);
super.onCancel();
}
};
}
}@ApplicationScoped
public class MetricsInterceptor implements ServerInterceptor {
@Inject
MeterRegistry meterRegistry;
@Override
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
ServerCall<ReqT, RespT> call,
Metadata headers,
ServerCallHandler<ReqT, RespT> next) {
String methodName = call.getMethodDescriptor().getFullMethodName();
Timer.Sample sample = Timer.start(meterRegistry);
Counter.builder("grpc.server.requests")
.tag("method", methodName)
.register(meterRegistry)
.increment();
return new ForwardingServerCallListener.SimpleForwardingServerCallListener<ReqT>(
next.startCall(call, headers)) {
@Override
public void onComplete() {
sample.stop(Timer.builder("grpc.server.duration")
.tag("method", methodName)
.tag("status", "completed")
.register(meterRegistry));
super.onComplete();
}
@Override
public void onCancel() {
sample.stop(Timer.builder("grpc.server.duration")
.tag("method", methodName)
.tag("status", "cancelled")
.register(meterRegistry));
super.onCancel();
}
};
}
}@ApplicationScoped
public class RetryInterceptor implements ClientInterceptor {
private final int maxRetries = 3;
private final long retryDelayMs = 1000;
@Override
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
MethodDescriptor<ReqT, RespT> method,
CallOptions callOptions,
Channel next) {
return new ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(
next.newCall(method, callOptions)) {
@Override
public void start(Listener<RespT> responseListener, Metadata headers) {
super.start(new RetryingListener<>(responseListener, method, callOptions, next),
headers);
}
};
}
private class RetryingListener<RespT> extends
ForwardingClientCallListener.SimpleForwardingClientCallListener<RespT> {
private int attemptCount = 0;
protected RetryingListener(Listener<RespT> delegate,
MethodDescriptor<?, RespT> method,
CallOptions callOptions,
Channel channel) {
super(delegate);
// Store for retry logic
}
@Override
public void onClose(Status status, Metadata trailers) {
if (status.isOk() || attemptCount >= maxRetries) {
super.onClose(status, trailers);
} else {
attemptCount++;
// Implement retry logic
scheduleRetry();
}
}
}
}Install with Tessl CLI
npx tessl i tessl/maven-io-quarkus--quarkus-grpc