Spring Boot gRPC Client Example: Blocking, Async, and Future Stubs

Efficient communication between microservices lies at the heart of distributed system design. gRPC, a high-performance RPC framework, offers a flexible and robust way to achieve this. Within gRPC’s ecosystem, client stubs are crucial for defining how calls are initiated and managed. Spring Boot, with its rich ecosystem and ease of use, provides seamless integration with gRPC.

This guide will walk you through building and implementing gRPC clients in Spring Boot using blocking, asynchronous, and future-based stubs. We’ll cover configuration, client creation methods, exception handling, logging, and even show you an example of call tracing.

Table of Contents

  1. What Are the 3 gRPC Stub Types?
  2. Setting Up Stub Dependencies in Spring Boot
  3. Creating a Blocking Stub Client
  4. Creating an Async Stub with Callback
  5. Using ListenableFuture or CompletableFuture
  6. Injecting Stubs as Spring Beans
  7. Exception Handling for Failed Calls
  8. Logging Outgoing Requests
  9. Using the Client Inside a REST Controller
  10. End-to-End Call Tracing Example
  11. FAQs
  12. Summary

What Are the 3 gRPC Stub Types?

gRPC offers three types of client stubs, enabling flexible implementations suited to various application needs:

  1. Blocking (Synchronous) Stub
    • Executes calls synchronously, blocking until the response is received.
    • Ideal for simple, time-sensitive use cases.
  1. Async Stub
    • Executes calls asynchronously, returning responses via callbacks.
    • Suitable for non-blocking operations or streaming data.
  1. Future (Non-blocking) Stub
    • Returns a ListenableFuture or CompletableFuture that resolves when the call completes.
    • Ideal for combining calls using Java’s Future or CompletionStage.

Understanding which stub to use depends on your use case. For example, blocking stubs are intuitive for quick synchronous operations, while async and future stubs shine in resource-heavy or asynchronous workflows.

Learn more about gRPC concepts


Setting Up Stub Dependencies in Spring Boot

Before implementing gRPC clients, ensure your project is configured with the necessary dependencies.

Maven Dependencies

Add the following to your pom.xml:

   <dependency>
       <groupId>io.grpc</groupId>
       <artifactId>grpc-netty</artifactId>
       <version>1.56.0</version>
   </dependency>
   <dependency>
       <groupId>io.grpc</groupId>
       <artifactId>grpc-protobuf</artifactId>
       <version>1.56.0</version>
   </dependency>
   <dependency>
       <groupId>io.grpc</groupId>
       <artifactId>grpc-stub</artifactId>
       <version>1.56.0</version>
   </dependency>
   <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter</artifactId>
   </dependency>

Official Maven gRPC Documentation

Run mvn clean compile to generate the required gRPC stubs.


Creating a Blocking Stub Client

A blocking stub executes requests synchronously, waiting for a response before proceeding.

Implementation

   @Component
   public class BlockingGreeterClient {

       private final GreeterGrpc.GreeterBlockingStub blockingStub;

       public BlockingGreeterClient(ManagedChannel channel) {
           this.blockingStub = GreeterGrpc.newBlockingStub(channel);
       }

       public String sayHello(String name) {
           HelloRequest request = HelloRequest.newBuilder()
               .setName(name)
               .build();
           HelloReply reply = blockingStub.sayHello(request);
           return reply.getMessage();
       }
   }

Blocking clients are suited for straightforward use cases where latency is not a major concern.


Creating an Async Stub with Callback

Async stubs execute calls non-blockingly, returning results via callbacks.

Implementation

   @Component
   public class AsyncGreeterClient {

       private final GreeterGrpc.GreeterStub asyncStub;

       public AsyncGreeterClient(ManagedChannel channel) {
           this.asyncStub = GreeterGrpc.newStub(channel);
       }

       public void sayHello(String name) {
           HelloRequest request = HelloRequest.newBuilder()
               .setName(name)
               .build();
           asyncStub.sayHello(request, new StreamObserver<HelloReply>() {
               @Override
               public void onNext(HelloReply reply) {
                   System.out.println("Received reply: " + reply.getMessage());
               }

               @Override
               public void onError(Throwable t) {
                   System.err.println("RPC failed: " + t);
               }

               @Override
               public void onCompleted() {
                   System.out.println("Call completed.");
               }
           });
       }
   }

Async stubs are ideal for streaming calls, where data is continuously exchanged in real-time.

Learn more about gRPC Streaming


Using ListenableFuture or CompletableFuture

Future stubs provide a powerful abstraction for handling non-blocking calls, allowing integration with concurrency frameworks.

Example with CompletableFuture:

   @Component
   public class FutureGreeterClient {

       private final GreeterGrpc.GreeterFutureStub futureStub;

       public FutureGreeterClient(ManagedChannel channel) {
           this.futureStub = GreeterGrpc.newFutureStub(channel);
       }

       public CompletableFuture<String> sayHello(String name) {
           HelloRequest request = HelloRequest.newBuilder().setName(name).build();
           return CompletableFuture.supplyAsync(() -> {
               try {
                   HelloReply reply = futureStub.sayHello(request).get();
                   return reply.getMessage();
               } catch (Exception e) {
                   throw new RuntimeException(e);
               }
           });
       }
   }

Futures shine in use cases involving complex workflows, such as chaining or aggregating multiple calls.


Injecting Stubs as Spring Beans

To simplify dependency injection in Spring Boot, configure stubs as beans in a configuration class:

   @Configuration
   public class GrpcClientConfig {

       @Bean
       public ManagedChannel channel() {
           return ManagedChannelBuilder.forAddress("localhost", 9090)
               .usePlaintext()
               .build();
       }

       @Bean
       public GreeterGrpc.GreeterBlockingStub blockingStub(ManagedChannel channel) {
           return GreeterGrpc.newBlockingStub(channel);
       }

       @Bean
       public GreeterGrpc.GreeterStub asyncStub(ManagedChannel channel) {
           return GreeterGrpc.newStub(channel);
       }
   }

Exception Handling for Failed Calls

Handle exceptions like StatusRuntimeException for failed gRPC calls.

   try {
       HelloReply reply = blockingStub.sayHello(request);
   } catch (StatusRuntimeException e) {
       log.error("RPC failed with status code {}, description {}", 
                 e.getStatus().getCode(), e.getStatus().getDescription());
   }

Logging Outgoing Requests

Add interceptors to log gRPC requests and responses.

   public class LoggingInterceptor implements ClientInterceptor {
       ...
   }

Learn About Logging and Interceptors


Using the Client Inside a REST Controller

Combine REST and gRPC by exposing a REST endpoint that calls your gRPC service:

   @RestController
   public class GreetingController {

       @Autowired
       private BlockingGreeterClient greeterClient;

       @GetMapping("/api/greet")
       public String greet(@RequestParam String name) {
           return greeterClient.sayHello(name);
       }
   }

This approach bridges external client requests with internal gRPC services.

Hybrid gRPC and REST Guide


End-to-End Call Tracing Example

Integrate gRPC with tools like Zipkin for distributed tracing:

   MDC.put("traceId", "request-trace-id");

Use gRPC interceptors to propagate traceIds in all outgoing requests for observability.

Distributed Tracing with Zipkin


FAQs

What are gRPC client stubs?

Client stubs abstract the RPC communication in gRPC, handling serialization, deserialization, and transport.

Can blocking and async stubs coexist?

Yes, they can run in parallel within the same application as long as proper channels are used.


Summary

Understanding how to work with blocking, async, and future stubs empowers you to build efficient and responsive communication between Spring Boot microservices. By managing configurations, adopting exception handling practices, and leveraging interceptors and tracing tools, you can ensure robust gRPC client integration across your systems. Experiment with the examples to create optimized, user-friendly APIs!

Similar Posts