How Dubbo 3.0 Enables Custom Exceptions with the Triple Protocol
This article explains how Dubbo 3.0’s Triple RPC protocol now supports custom exception serialization, detailing the original exception handling flow, the limitations of header‑based error transport, and the proposed TripleWrapper‑Protobuf solution for reliable client‑side error capture.
Background
In many business scenarios developers need to throw custom exceptions that carry specific error information. Traditionally this is done by re‑throwing the exception in a catch block or by using an ExceptionBuilder to return an exception object to the consumer.
public void deal() {
try {
// doSomething
...
} catch (IGreeterException e) {
// additional handling
throw e;
}
} provider.send(new ExceptionBuilders.IGreeterExceptionBuilder()
.setDescription('异常描述信息'));On the consumer side the exception can be caught with instanceof checks:
try {
greeterProxy.echo(REQUEST_MSG);
} catch (IGreeterException e) {
// handle specific error
...
}Dubbo 3.0 and the Triple Protocol
Dubbo 3.0 embraces cloud‑native principles and introduces the Triple protocol, which is built on HTTP/2, compatible with gRPC, and supports various streaming models. Triple also adds IDL‑based service definitions.
When Triple is used, the provider can generate custom exception information and the protocol guarantees that the client receives the exception message. However, Dubbo wraps all provider‑side exceptions into a generic RpcException inside AbstractInvoker.waitForResultIfSync, losing the original exception type and message:
try {
greeterProxy.echo(REQUEST_MSG);
} catch (RpcException e) {
e.printStackTrace();
}Because ExceptionBuilder is not widely adopted, Dubbo now adds explicit support for custom exceptions while still deprecating the builder approach.
Consumer‑Side Exception Flow
The consumer obtains a proxy bean from the Spring container. Calls are delegated to ProxyFactory (default JavassistProxyFactory), which creates an anonymous subclass of AbstractProxyInvoker. The overridden doInvoke forwards the request to a generated Wrapper class, which ultimately invokes InvocationUtil → AbstractInvoker → concrete invoker. The provider’s response is wrapped in an AppResponse. If the response contains an exception, AppResponse.recreate() throws it, allowing the consumer to catch the specific exception.
public Object recreate() throws Throwable {
if (exception != null) {
try {
Object stackTrace = exception.getStackTrace();
if (stackTrace == null) {
exception.setStackTrace(new StackTraceElement[0]);
}
} catch (Exception e) {
// ignore
}
throw exception;
}
return result;
}Provider‑Side ExceptionFilter
On the provider side, org.apache.dubbo.rpc.filter.ExceptionFilter is part of the filter chain and packages any thrown exception into an AppResponse. The following diagram (omitted here) shows the filter’s position.
Serialization Choices and Header‑Size Limitation
To transmit custom exception objects, they must be serialized. Common options include Hessian2 (Dubbo/HSF default), JSON, and protobuf (gRPC native). Triple adopts protobuf by default. Because protobuf cannot directly serialize an exception object, a wrapper (e.g., TripleExceptionWrapperUtils) is used to serialize the exception into the trailer.
However, HTTP/2 limits header size (typically 8 KB). Large serialized exceptions may exceed this limit, causing transmission failures and consuming valuable header space.
Proposed Solution: TripleWrapper + Protobuf in Body
The community discussed moving the serialized exception from the trailer to the message body, using a TripleWrapper combined with protobuf. This avoids header‑size constraints while preserving exception details. The revised flow serializes the exception into the body and returns a 200 status code, treating the payload as a business‑level error rather than a transport error.
Provider Invocation Logic with TripleWrapper
public void invoke() {
// ...
try {
// invoke service method
final Result response = invoker.invoke(invocation);
// async wait for result
response.whenCompleteWithContext((r, t) -> {
if (t != null) {
// method threw an exception
responseObserver.onError(t);
return;
}
if (response.hasException()) {
// business exception handling
onReturn(response.getException());
return;
}
// normal result
onReturn(r.getValue());
});
}
// ...
}The revised version only adds the serialized exception to the body when it is non‑null, then the consumer deserializes it using the same protobuf definition.
Conclusion
Adding custom exception support to Dubbo 3.0 required careful consideration of protocol compatibility, header size limits, and serialization strategy. By moving exception payloads to the body with a protobuf‑based wrapper, Dubbo preserves rich error information while staying aligned with cloud‑native and gRPC design principles.
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.
Alibaba Cloud Native
We publish cloud-native tech news, curate in-depth content, host regular events and live streams, and share Alibaba product and user case studies. Join us to explore and share the cloud-native insights you need.
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.
