CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-io-quarkus--quarkus-grpc

Quarkus gRPC extension that enables implementing and consuming gRPC services with reactive and imperative programming models.

Pending
Overview
Eval results
Files

client-usage.mddocs/

Client Usage

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.

Capabilities

@GrpcClient Annotation

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
    }
}

MutinyClient Interface

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);
    }
}

Client Configuration

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_robin

Client Utilities

Utility 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);
    }
}

Reactive Call Patterns

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);
    }
}

Error Handling

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;
            });
    }
}

Programmatic Client Creation

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

docs

client-usage.md

configuration.md

exception-handling.md

index.md

interceptors.md

reactive-streaming.md

service-implementation.md

tile.json