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
- What Are the 3 gRPC Stub Types?
- Setting Up Stub Dependencies in Spring Boot
- Creating a Blocking Stub Client
- Creating an Async Stub with Callback
- Using ListenableFuture or CompletableFuture
- Injecting Stubs as Spring Beans
- Exception Handling for Failed Calls
- Logging Outgoing Requests
- Using the Client Inside a REST Controller
- End-to-End Call Tracing Example
- FAQs
- Summary
What Are the 3 gRPC Stub Types?
gRPC offers three types of client stubs, enabling flexible implementations suited to various application needs:
- Blocking (Synchronous) Stub
- Executes calls synchronously, blocking until the response is received.
- Ideal for simple, time-sensitive use cases.
- Async Stub
- Executes calls asynchronously, returning responses via callbacks.
- Suitable for non-blocking operations or streaming data.
- Future (Non-blocking) Stub
- Returns a
ListenableFuture
orCompletableFuture
that resolves when the call completes. - Ideal for combining calls using Java’s
Future
orCompletionStage
.
- Returns a
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.
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!