Master Non‑Blocking & Streaming gRPC Clients in Java for High‑Performance Testing

Learn how to create and use non‑blocking (asynchronous) and streaming gRPC clients in Java, covering stub creation, request handling with ListenableFuture, blocking vs listener approaches, and stream observer implementation, enabling high‑concurrency performance testing and real‑time data processing scenarios.

FunTester
FunTester
FunTester
Master Non‑Blocking & Streaming gRPC Clients in Java for High‑Performance Testing

Non‑Blocking Client

Non‑blocking (asynchronous) clients are more complex than blocking ones but offer superior high‑concurrency performance, making them ideal for simulating many user requests. The main differences are the way service stubs are created and how responses are handled.

First, create an asynchronous stub ( FutureStub) to invoke methods, e.g.:

// Create asynchronous stub for FunTester service's queryUser method
UserServiceGrpc.UserServiceFutureStub userServiceFutureStub = UserServiceGrpc.newFutureStub(managedChannel);

Then send a request using the previously defined QueryRequest. The call returns a ListenableFuture object:

// Send asynchronous request and obtain Future object
ListenableFuture<QueryResponse> queryResponseFuture = userServiceFutureStub.queryUser(queryRequest);
ListenableFuture

provides two ways to handle the response:

Blocking get : call get() to wait for the QueryResponse. Suitable for simple scenarios but may block the main thread under heavy load.

Register listener : attach a callback to the ListenableFuture using an Executor. This non‑blocking approach requires familiarity with Java concurrency and is recommended for advanced use cases.

In practice, a common pattern is to fire many asynchronous requests, store the resulting ListenableFuture objects in a thread‑safe collection, and later iterate over them calling get() to retrieve results:

// Example: handling multiple asynchronous requests
List<ListenableFuture<QueryResponse>> futures = new ArrayList<>();
for (int i = 0; i < 100; i++) {
    QueryRequest request = QueryRequest.newBuilder().setId(800 + i).build();
    futures.add(userServiceFutureStub.queryUser(request)); // batch send
}
for (ListenableFuture<QueryResponse> future : futures) {
    try {
        QueryResponse response = future.get(); // blocking get
        System.out.println("User name: " + response.getName());
    } catch (Exception e) {
        System.err.println("Request failed: " + e.getMessage());
    }
}

This approach combines the high‑throughput advantage of asynchronous clients with deterministic business logic.

Streaming Client

Streaming clients are also asynchronous and are designed for real‑time data transfer or bidirectional communication, allowing concurrent sending and receiving of data streams. They differ from blocking clients in stub creation, request sending, and response handling, and are commonly used for video streaming, chat systems, and similar scenarios.

First, create a streaming stub ( Stub):

// Create streaming stub for FunTester service
UserServiceGrpc.UserServiceStub userServiceStub = UserServiceGrpc.newStub(managedChannel);

Requests are sent via a StreamObserver implementation. The server responses are delivered to the client through another StreamObserver with three callback methods:

// Build request object
QueryRequest queryRequest = QueryRequest.newBuilder().setId(888).build();

// Define response observer
StreamObserver<QueryResponse> streamObserver = new StreamObserver<QueryResponse>() {
    @Override
    public void onNext(QueryResponse response) {
        System.out.println("User name: " + response.getName());
    }
    @Override
    public void onError(Throwable throwable) {
        System.err.println("Request failed: " + throwable.getMessage());
    }
    @Override
    public void onCompleted() {
        System.out.println("Data stream processing completed");
    }
};

// Send streaming request
userServiceStub.queryUser(queryRequest, streamObserver);

The core of a streaming client is the StreamObserver implementation, where onNext processes each piece of data, onError handles failures, and onCompleted signals the end of the stream. This pattern is useful for testing real‑time log systems or instant‑messaging services under sustained high‑frequency data exchange.

Compared with asynchronous clients, streaming clients require deeper knowledge of the API and concurrency, so beginners should start with blocking or simple asynchronous clients before tackling streaming scenarios.

Mastering both non‑blocking and streaming gRPC clients equips test engineers to handle complex performance testing situations and ensure system stability under load.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaStreamingPerformance TestingAsynchronousgRPC
FunTester
Written by

FunTester

10k followers, 1k articles | completely useless

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.