Unveiling Dubbo’s Encoding & Decoding: A Deep Dive into RPC Serialization

This article walks through Dubbo’s RPC encoding and decoding mechanisms, explaining how Netty pipelines add codec handlers, detailing the message header format, exploring Hessian2 serialization, and answering common pitfalls such as class‑path changes and field mismatches.

vivo Internet Technology
vivo Internet Technology
vivo Internet Technology
Unveiling Dubbo’s Encoding & Decoding: A Deep Dive into RPC Serialization

Background

The author discovered a package‑path change in a shared component during maintenance and, out of curiosity, investigated Dubbo’s encoding and decoding process.

Dubbo RPC Overview

Dubbo is a Java RPC framework that abstracts remote calls so they look like local method invocations. While this simplifies usage, it introduces coupling between providers and consumers through shared interfaces and facades.

Netty Pipeline Integration

Both client and server use Netty. During NettyClient#doOpen and NettyServer#doOpen, codec handlers are added to the pipeline:

bootstrap.handler(new ChannelInitializer<SocketChannel>() {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ch.pipeline()
            .addLast("decoder", adapter.getDecoder())
            .addLast("encoder", adapter.getEncoder())
            .addLast("client-idle-handler", new IdleStateHandler(heartbeatInterval, 0, 0, MILLISECONDS))
            .addLast("handler", nettyClientHandler);
    }
});
bootstrap.group(bossGroup, workerGroup)
    .channel(NettyEventLoopFactory.serverSocketChannelClass())
    .option(ChannelOption.SO_REUSEADDR, Boolean.TRUE)
    .childOption(ChannelOption.TCP_NODELAY, Boolean.TRUE)
    .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
    .childHandler(new ChannelInitializer<SocketChannel>() {
        @Override
        protected void initChannel(SocketChannel ch) throws Exception {
            ch.pipeline()
                .addLast("decoder", adapter.getDecoder())
                .addLast("encoder", adapter.getEncoder())
                .addLast("server-idle-handler", new IdleStateHandler(0, 0, idleTimeout, MILLISECONDS))
                .addLast("handler", nettyServerHandler);
        }
    });

Encoding Flow (Consumer Side)

When a consumer sends a request, the flow is:

Channel → NettyCodecAdapter#getEncoder()NettyCodecAdapter$InternalEncoder#encodeDubboCountCodec#encodeDubboCodec#encodeExchangeCodec#encodeExchangeCodec#encodeRequest The actual serialization object is obtained from Serialization serialization = getSerialization(channel); which defaults to Hessian2.

Decoding Flow (Consumer Side)

Responses are decoded through two stages:

First stage builds a DecodeableRpcResult without fully deserializing the payload.

Second stage, executed in an async thread, invokes DecodeableRpcResult#decode to reconstruct the result object.

if (channel.getUrl().getParameter(DECODE_IN_IO_THREAD_KEY, DEFAULT_DECODE_IN_IO_THREAD)) {
    result = new DecodeableRpcResult(channel, res, is, (Invocation) getRequestData(id), proto);
    result.decode();
} else {
    result = new DecodeableRpcResult(channel, res, new UnsafeByteArrayInputStream(readMessageData(is)), (Invocation) getRequestData(id), proto);
}

Message Header Structure

Dubbo uses a fixed‑length header (2 bytes magic, 1 byte flag, 1 byte status, 8 bytes request ID, 4 bytes body length) followed by the serialized body.

Dubbo protocol header diagram
Dubbo protocol header diagram

Hessian2 Serialization Details

Dubbo’s default serializer is Hessian2. The request data is written by DubboCodec#encodeRequestData:

@Override
protected void encodeRequestData(Channel channel, ObjectOutput out, Object data, String version) throws IOException {
    RpcInvocation inv = (RpcInvocation) data;
    out.writeUTF(version);
    String serviceName = inv.getAttachment(INTERFACE_KEY);
    if (serviceName == null) serviceName = inv.getAttachment(PATH_KEY);
    out.writeUTF(serviceName);
    out.writeUTF(inv.getAttachment(VERSION_KEY));
    out.writeUTF(inv.getMethodName());
    out.writeUTF(inv.getParameterTypesDesc());
    Object[] args = inv.getArguments();
    if (args != null) {
        for (int i = 0; i < args.length; i++) {
            out.writeObject(encodeInvocationArgument(channel, inv, i));
        }
    }
    out.writeAttachments(inv.getObjectAttachments());
}

The corresponding deserializer is Hessian2ObjectInput, obtained via CodecSupport.getSerialization(url).deserialize(...).

Common Issues & Answers

Provider changes class path but consumer does not error – Deserialization catches ClassNotFoundException and falls back to the local type.

Why return value is not written in the request – Java method signatures do not include return types as part of the identifier.

Field order mismatch between parent and child classes – In Dubbo 2.7.x this is fixed by reversing the field list during serialization.

Conclusion

The encoding/decoding process in Dubbo is intricate, involving multiple layers of abstraction to hide low‑level details. Understanding the Netty pipeline, message header, and Hessian2 serialization helps developers troubleshoot compatibility issues and extend the framework safely.

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.

JavaRPCDubboserializationencodingNettydecodinghessian2
vivo Internet Technology
Written by

vivo Internet Technology

Sharing practical vivo Internet technology insights and salon events, plus the latest industry news and hot conferences.

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.