Quarkus gRPC extension that enables implementing and consuming gRPC services with reactive and imperative programming models.
—
Client functionality for consuming gRPC services with reactive programming support and configuration management. Clients are injected as CDI beans and provide reactive method signatures using Mutiny types.
Qualifies an injected gRPC client. The annotation provides configuration name resolution and supports programmatic creation of annotation literals.
/**
* Qualifies an injected gRPC client.
*/
@Qualifier
@Target({ FIELD, PARAMETER })
@Retention(RUNTIME)
public @interface GrpcClient {
/**
* Constant value for {@link #value()} indicating that the annotated element's name should be used as-is.
*/
String ELEMENT_NAME = "<<element name>>";
/**
* The name is used to configure the gRPC client, e.g. the location, TLS/SSL, etc.
*
* @return the client name
*/
String value() default ELEMENT_NAME;
final class Literal extends AnnotationLiteral<GrpcClient> implements GrpcClient {
private static final long serialVersionUID = 1L;
private final String value;
/**
* Creates a new instance of {@link Literal}.
*
* @param value the name of the gRPC service, must not be {@code null}, must not be {@code blank}
* @return the literal instance.
*/
public static Literal of(String value);
/**
* @return the service name.
*/
public String value();
}
}Usage Examples:
import io.quarkus.grpc.GrpcClient;
import jakarta.inject.Inject;
public class GreetingClientService {
// Using field name as client name
@Inject
@GrpcClient
MutinyGreetingGrpc greeting;
// Using explicit client name
@Inject
@GrpcClient("user-service")
MutinyUserGrpc userService;
// Using method parameter injection
public void processGreeting(@GrpcClient("greeting") MutinyGreetingGrpc client) {
// Use client
}
}Represents a convenient Mutiny client generated for a gRPC service. Provides access to the underlying gRPC stub and supports creating new instances with custom stubs.
/**
* Represents a convenient Mutiny client generated for a gRPC service.
*/
public interface MutinyClient<T extends AbstractStub<T>> {
/**
*
* @return the underlying stub
*/
T getStub();
MutinyClient<T> newInstanceWithStub(T stub);
}Usage Examples:
import io.quarkus.grpc.GrpcClient;
import io.quarkus.grpc.MutinyClient;
import io.grpc.stub.AbstractStub;
import io.smallrye.mutiny.Uni;
public class ProductClientService {
@Inject
@GrpcClient("product-service")
MutinyClient<ProductServiceGrpc.ProductServiceStub> productClient;
public Uni<Product> getProduct(Long productId) {
ProductRequest request = ProductRequest.newBuilder()
.setId(productId)
.build();
return productClient.getStub().getProduct(request);
}
public MutinyClient<ProductServiceGrpc.ProductServiceStub> withDeadline(Duration timeout) {
var stubWithDeadline = productClient.getStub()
.withDeadlineAfter(timeout.toMillis(), TimeUnit.MILLISECONDS);
return productClient.newInstanceWithStub(stubWithDeadline);
}
}Clients are configured through application properties using the client name:
# Basic configuration
quarkus.grpc.clients.greeting.host=localhost
quarkus.grpc.clients.greeting.port=9000
# TLS configuration
quarkus.grpc.clients.user-service.host=secure-service.example.com
quarkus.grpc.clients.user-service.port=443
quarkus.grpc.clients.user-service.ssl.trust-store=path/to/truststore.p12
quarkus.grpc.clients.user-service.ssl.trust-store-password=secret
# Load balancing
quarkus.grpc.clients.payment-service.host=payment-service
quarkus.grpc.clients.payment-service.port=9000
quarkus.grpc.clients.payment-service.load-balancing-policy=round_robinUtility class for working with gRPC clients, including header attachment and proxy handling.
/**
* gRPC client utilities
*/
public class GrpcClientUtils {
/**
* Attach headers to a gRPC client.
*
* To make a call with headers, first invoke this method and then perform the intended call with the <b>returned</b> client
*
* @param client any kind of gRPC client
* @param extraHeaders headers to attach
* @param <T> type of the client
* @return a client with headers attached
*/
public static <T> T attachHeaders(T client, Metadata extraHeaders);
public static <T> T getProxiedObject(T client);
}Usage Examples:
import io.quarkus.grpc.GrpcClientUtils;
import io.grpc.Metadata;
import io.grpc.Metadata.Key;
public class AuthenticatedClientService {
@Inject
@GrpcClient("secure-service")
MutinySecureServiceGrpc secureClient;
public Uni<SecureResponse> callSecureService(String token, SecureRequest request) {
// Create headers
Metadata headers = new Metadata();
Key<String> authKey = Key.of("authorization", Metadata.ASCII_STRING_MARSHALLER);
headers.put(authKey, "Bearer " + token);
// Attach headers to client
var clientWithHeaders = GrpcClientUtils.attachHeaders(secureClient, headers);
// Make the call
return clientWithHeaders.secureCall(request);
}
public Uni<UserInfo> getUserInfo(String userId) {
// Get actual client instance (unwrap proxy if needed)
var actualClient = GrpcClientUtils.getProxiedObject(secureClient);
UserRequest request = UserRequest.newBuilder()
.setUserId(userId)
.build();
return actualClient.getUserInfo(request);
}
}Clients support all gRPC call patterns using Mutiny reactive types:
public class ComprehensiveClientExample {
@Inject
@GrpcClient("data-service")
MutinyDataServiceGrpc dataClient;
// Unary call: request -> Uni<response>
public Uni<DataResponse> getData(String id) {
DataRequest request = DataRequest.newBuilder().setId(id).build();
return dataClient.getData(request);
}
// Server streaming: request -> Multi<response>
public Multi<DataItem> streamData(String query) {
StreamRequest request = StreamRequest.newBuilder().setQuery(query).build();
return dataClient.streamData(request);
}
// Client streaming: Multi<request> -> Uni<response>
public Uni<UploadResponse> uploadData(Multi<DataChunk> chunks) {
return dataClient.uploadData(chunks);
}
// Bidirectional streaming: Multi<request> -> Multi<response>
public Multi<ProcessedData> processData(Multi<RawData> rawData) {
return dataClient.processData(rawData);
}
}Client errors are propagated as Mutiny failures and can be handled using standard reactive patterns:
public class ErrorHandlingExample {
@Inject
@GrpcClient("unreliable-service")
MutinyUnreliableServiceGrpc client;
public Uni<ServiceResponse> callWithRetry(ServiceRequest request) {
return client.callService(request)
.onFailure(StatusRuntimeException.class)
.retry().withBackOff(Duration.ofSeconds(1)).atMost(3)
.onFailure().transform(ex -> {
if (ex instanceof StatusRuntimeException) {
StatusRuntimeException sre = (StatusRuntimeException) ex;
return new ServiceException("Service call failed: " + sre.getStatus());
}
return ex;
});
}
}For dynamic client creation, use CDI programmatic lookup with GrpcClient.Literal:
import jakarta.enterprise.inject.Instance;
import io.quarkus.grpc.GrpcClient;
@ApplicationScoped
public class DynamicClientService {
@Inject
Instance<Object> instance;
public <T> T getClient(String serviceName, Class<T> clientType) {
return instance.select(clientType, GrpcClient.Literal.of(serviceName)).get();
}
public Uni<GreetingResponse> greetDynamically(String serviceName, String name) {
MutinyGreetingGrpc client = getClient(serviceName, MutinyGreetingGrpc.class);
GreetingRequest request = GreetingRequest.newBuilder()
.setName(name)
.build();
return client.greet(request);
}
}Install with Tessl CLI
npx tessl i tessl/maven-io-quarkus--quarkus-grpc