Mastering gRPC Java Clients: Blocking, Async, and Future Stubs Explained
This guide walks through three ways to create gRPC Java clients—newBlockingStub, newStub, and newFutureStub—showing their code, usage patterns, and performance characteristics, while also providing a simple server implementation for testing.
Server implementation
The gRPC service extends HelloServiceGrpc.HelloServiceImplBase and overrides executeHi. The method builds a HelloResponse whose msg field concatenates a greeting, the requester's name, and the current date obtained from Time.getDate(). It then sleeps for one second ( SourceCode.sleep(1.0)) to simulate processing latency, logs the incoming user name, and sends the response via responseObserver.onNext() followed by responseObserver.onCompleted().
package com.funtester.grpc;
import com.funtester.frame.SourceCode;
import com.funtester.fungrpc.HelloRequest;
import com.funtester.fungrpc.HelloResponse;
import com.funtester.fungrpc.HelloServiceGrpc;
import com.funtester.utils.Time;
import io.grpc.stub.StreamObserver;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class HelloServiceImpl extends HelloServiceGrpc.HelloServiceImplBase {
private static final Logger logger = LogManager.getLogger(HelloServiceImpl.class);
@Override
public void executeHi(HelloRequest request, StreamObserver<HelloResponse> responseObserver) {
HelloResponse response = HelloResponse.newBuilder()
.setMsg("你好 " + request.getName() + Time.getDate())
.build();
SourceCode.sleep(1.0); // simulate delay
logger.info("用户{}来了", request.getName());
responseObserver.onNext(response);
responseObserver.onCompleted();
}
}Synchronous (blocking) client – newBlockingStub
This stub provides a traditional request‑response flow. The client creates a ManagedChannel to localhost:8080, builds a HelloServiceBlockingStub with optional gzip compression, and repeatedly calls executeHi. Each call blocks until the server returns a HelloResponse, after which the client prints the msg field.
package com.funtest.grpc;
import com.funtester.frame.SourceCode;
import com.funtester.fungrpc.HelloRequest;
import com.funtester.fungrpc.HelloResponse;
import com.funtester.fungrpc.HelloServiceGrpc;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
public class BlockClient extends SourceCode {
public static void main(String[] args) {
ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 8080)
.usePlaintext()
.build();
HelloServiceGrpc.HelloServiceBlockingStub stub =
HelloServiceGrpc.newBlockingStub(channel).withCompression("gzip");
HelloRequest request = HelloRequest.newBuilder()
.setName("FunTester")
.build();
for (int i = 0; i < 5; i++) {
HelloResponse resp = stub.executeHi(request);
output("收到响应: " + resp.getMsg());
}
channel.shutdown();
}
}Pure asynchronous client – newStub
The asynchronous stub returns immediately; results are delivered through a StreamObserver. The client creates the stub, defines an observer that handles onNext (printing the message), onError (printing the error), and onCompleted (no action), then issues several requests. Because the calls are non‑blocking, they are processed concurrently on gRPC’s default executor threads.
package com.funtest.grpc;
import com.funtester.frame.SourceCode;
import com.funtester.fungrpc.HelloRequest;
import com.funtester.fungrpc.HelloResponse;
import com.funtester.fungrpc.HelloServiceGrpc;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.stub.StreamObserver;
public class AsyncClient extends SourceCode {
public static void main(String[] args) {
ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 8080)
.usePlaintext()
.build();
HelloServiceGrpc.HelloServiceStub stub =
HelloServiceGrpc.newStub(channel).withCompression("gzip");
HelloRequest request = HelloRequest.newBuilder()
.setName("FunTester")
.build();
StreamObserver<HelloResponse> responseObserver = new StreamObserver<HelloResponse>() {
@Override
public void onNext(HelloResponse value) {
output(value.getMsg());
}
@Override
public void onError(Throwable t) {
output(t.getMessage());
}
@Override
public void onCompleted() {}
};
for (int i = 0; i < 5; i++) {
stub.executeHi(request, responseObserver);
}
// Give async calls time to finish before shutting down
sleep(2000);
channel.shutdown();
}
}Future‑based client – newFutureStub
The future stub returns a ListenableFuture<HelloResponse>. It can be used synchronously by invoking get() on each future, or asynchronously by collecting futures and processing them later. The example demonstrates both patterns.
package com.funtest.grpc;
import com.funtester.frame.SourceCode;
import com.funtester.fungrpc.HelloRequest;
import com.funtester.fungrpc.HelloResponse;
import com.funtester.fungrpc.HelloServiceGrpc;
import com.google.common.util.concurrent.ListenableFuture;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
public class FutureClient extends SourceCode {
public static void main(String[] args) throws Exception {
ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 8080)
.usePlaintext()
.build();
HelloServiceGrpc.HelloServiceFutureStub stub =
HelloServiceGrpc.newFutureStub(channel).withCompression("gzip");
HelloRequest request = HelloRequest.newBuilder()
.setName("FunTester")
.build();
// Synchronous style: block on each future
for (int i = 0; i < 5; i++) {
ListenableFuture<HelloResponse> future = stub.executeHi(request);
HelloResponse resp = future.get();
output(resp.getMsg());
}
// Asynchronous style: collect futures then retrieve results
java.util.List<ListenableFuture<HelloResponse>> futures = new java.util.ArrayList<>();
for (int i = 0; i < 5; i++) {
futures.add(stub.executeHi(request));
}
for (ListenableFuture<HelloResponse> f : futures) {
output(f.get().getMsg());
}
channel.shutdown();
}
}Behavior comparison
Running the three clients against the server yields the following observations:
Blocking stub : Calls are processed sequentially; each request waits for the previous one to complete (approximately one‑second interval due to the server‑side sleep).
Asynchronous stub : All requests are dispatched immediately; responses arrive concurrently on separate executor threads, showing identical timestamps for the server‑side processing.
Future stub : Can be used either way. When get() is called immediately, the pattern behaves like the blocking stub. When futures are collected first and resolved later, the calls are effectively parallel, similar to the async stub.
These three stub types give developers flexibility to choose between simple synchronous code, fully asynchronous callbacks, or a hybrid future‑based approach when integrating gRPC services in Java applications.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
