Implementing Dubbo Asynchronous Calls with CompletableFuture: Practices and Performance Gains

By refactoring Dubbo RPC interfaces to return CompletableFuture, applying thenApply/thenCombine/thenCompose patterns, isolating a custom business thread pool, and handling errors and tracing, the team achieved up to 50% latency reduction, 25% response‑time improvement, a one‑third server cut and CPU utilization rise, demonstrating substantial performance and cost benefits.

DeWu Technology
DeWu Technology
DeWu Technology
Implementing Dubbo Asynchronous Calls with CompletableFuture: Practices and Performance Gains

Background

Since Dubbo 2.7.0, all asynchronous programming interfaces are based on CompletableFuture. Asynchronous Dubbo can greatly improve interface performance and reduce blocking between dependent calls. In a cost‑reduction project, the customer‑service robot team applied Dubbo async to several high‑QPS services and observed a 50% performance boost and a one‑third reduction in application servers.

Dubbo Async Implementation

By moving complex business logic from the default Dubbo thread pool (size 200) to a custom business thread pool, the request handling capacity of the Dubbo pool is increased while resource utilization improves.

2.1 Interface Refactor

The original synchronous method

Result<RecommendAtConnectRes> getRecommendContent(RecommendAtConnectReq request)

is kept for compatibility, and a new asynchronous method returning CompletableFuture<Result<RecommendAtConnectRes>> is added.

public interface RecommendAtConnectApi {
    Result<RecommendAtConnectRes> getRecommendContent(RecommendAtConnectReq request);
    CompletableFuture<Result<RecommendAtConnectRes>> asyncGetRecommendContent(RecommendAtConnectReq request);
}

2.2 Future Usage Patterns

Common patterns include:

Result transformation with thenApply Combining multiple futures with thenCombine (or allOf for more than two)

Sequential dependency with thenCompose Example of thenApply:

CompletableFuture<String> cFuture = cAsyncService.asyncSayHello(name);
CompletableFuture<DataDTO> finalFuture = cFuture.thenApply(c -> new DataDTO());
return finalFuture;

Example of thenCombine:

CompletableFuture<String> cFuture = cAsyncService.asyncSayHello(name);
CompletableFuture<String> dFuture = dAsyncService.asyncSayHello(name);
CompletableFuture<DataDTO> allFuture = cFuture.thenCombine(dFuture, (c, d) -> new DataDTO());
return allFuture;

Example of thenCompose (with optional handling):

CompletableFuture<Optional<RecommendAtConnectDto>> taskEngineFuture = pushGsTaskEngineHandler.asyncPushHandler(connectRequest);
CompletableFuture<Optional<RecommendAtConnectDto>> refundFuture = getNextFuture(taskEngineFuture, connectRequest, unused -> pushLogisticsRefundHandler.asyncPushHandler(connectRequest));
return refundFuture;

2.3 CompletableFuture Internals

The core fields are result (the computed value) and stack (a Treiber stack of dependent actions). Methods such as thenApply, thenCombine, and thenCompose create specific Completion objects, push them onto the stack via CAS, and fire them when the result becomes available.

Key source snippets:

// thenApply implementation
private <V> CompletableFuture<V> uniApplyStage(Executor e, Function<? super T, ? extends V> f) {
    if (f == null) throw new NullPointerException();
    CompletableFuture<V> d = new CompletableFuture<>();
    if (e != null || !d.uniApply(this, f, null)) {
        UniApply<T,V> c = new UniApply<>(e, d, this, f);
        push(c);
        c.tryFire(SYNC);
    }
    return d;
}

3.1 Practical Experience – Scenario Selection

Three robot scenarios were chosen: order detail page, chat “you may ask” page, and input suggestion. They have high QPS, are IO‑intensive, and do not affect core dialogue interfaces, making them ideal for async conversion.

3.2 Best Practices

3.2.1 Dependency Mapping

Before refactoring, draw a dependency graph to identify parallel and serial relationships. Parallel CF nodes can be executed concurrently, while dependent nodes must be chained.

3.2.2 Code Example

public CompletableFuture<CFResponse> getResult(){
    // Parallel three chains
    CompletableFuture<CF1Response> cf1 = cf1Service.getResult();
    CompletableFuture<CF2CombineResponse> cf2Combine = getCf2Combine();
    CompletableFuture<CF3CombineResponse> cf3Combine = getCf3Combine();
    // Combine and transform
    CompletableFuture<Void> finalFuture = CompletableFuture.allOf(cf1, cf2Combine, cf3Combine);
    return finalFuture.thenApply((unused) ->
        new CFResponse(cf1.get().getCf1Value() + cf2Combine.get().getCf2CombineValue() + cf3Combine.get().getCf3CombineValue()));
}

private CompletableFuture<CF2CombineResponse> getCf2Combine() {
    CompletableFuture<CF2Response> cf2 = cf2Service.getResult();
    return cf2.thenCompose(cf2Response -> {
        CompletableFuture<CF4Response> cf4 = cf4Service.getResult(cf2Response.getCf2Value());
        return cf4.thenApply(cf4Response -> new CF2CombineResponse(cf4Response.getCf4Value()));
    });
}

private CompletableFuture<CF3CombineResponse> getCf3Combine() {
    CompletableFuture<CF3Response> cf3 = cf3Service.getResult();
    return cf3.thenCompose(cf3Response -> {
        CompletableFuture<CF5Response> cf5 = cf5Service.getResult(cf3Response.getCf3Value());
        CompletableFuture<CF6Response> cf6 = cf6Service.getResult(cf3Response.getCf3Value());
        return CompletableFuture.allOf(cf5, cf6)
            .thenCompose(unused -> cf7Service.getResult(cf5.get().getCf5Value(), cf6.get().getCf6Value()));
    });
}

3.2.3 Thread‑Pool Isolation

Define a custom business thread pool to avoid blocking Dubbo’s internal pool. Example Spring bean:

@Bean(name = "dubboAsyncBizExecutor")
public ThreadPoolTaskExecutor dubboAsyncBizExecutor(){
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(200);
    executor.setMaxPoolSize(200);
    executor.setQueueCapacity(50);
    executor.setThreadNamePrefix("dubboAsyncBizExecutor-");
    executor.setRejectedExecutionHandler((r, e) -> log.error("dubbo async biz task exceed limit"));
    return executor;
}

Use it with CompletableFuture.supplyAsync(..., dubboAsyncBizExecutor).

3.2.4 Exception Handling

Handle errors with exceptionally and unwrap CompletionException via a utility method.

CompletableFuture<RecommendAtConnectDto> asyncPushContent(RecommendAtConnectRequest request) {
    CompletableFuture<String> future = orderSourcePredictHandlerChain.asyncHandleOfPredict(request);
    return future.thenApply(body -> {
        if (StrUtil.isBlank(body)) return null;
        return RecommendAtConnectDtoUtil.getDto(body, ...);
    }).exceptionally(err -> {
        log.error("async error", ExceptionUtils.extractRealException(err));
        return null;
    });
}

public class ExceptionUtils {
    public static Throwable extractRealException(Throwable t){
        if (t instanceof CompletionException || t instanceof ExecutionException) {
            if (t.getCause() != null) return t.getCause();
        }
        return t;
    }
}

3.2.5 Stability Guarantees

Keep original synchronous methods unchanged.

Introduce async methods alongside them.

Control traffic via AB testing to switch between sync and async.

Provide a one‑click rollback to the original logic.

3.3 Issues Encountered

Trace‑ID loss in async callbacks, requiring monitoring support.

Thread‑pool isolation only available from Dubbo 3.2.0. thenCompose cannot return null; wrap with Optional.

Logging must be placed inside callbacks to capture real results.

Monitoring platforms may not include callback latency, making performance diagnosis harder.

Asynchronous Benefits

Load test shows 50% interface latency reduction.

Production RT decreased by ~25% (e.g., input‑suggestion from 173 ms to 119 ms).

Server count reduced by one‑third, CPU utilization increased from ~18% to ~50%.

Conclusion

The practice demonstrates that converting Dubbo RPC to asynchronous CompletableFuture calls removes blocking between services, expands thread‑pool capacity, and improves CPU utilization, enabling higher throughput with fewer hardware resources. This contributes to cost‑reduction and efficiency gains for the organization.

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.

JavaperformanceDubboThreadPoolCompletableFuture
DeWu Technology
Written by

DeWu Technology

A platform for sharing and discussing tech knowledge, guiding you toward the cloud of technology.

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.