This is an automated email from the ASF dual-hosted git repository.

jimin pushed a commit to branch 2.x
in repository https://gitbox.apache.org/repos/asf/incubator-seata.git


The following commit(s) were added to refs/heads/2.x by this push:
     new 18a220d481 optimize : support multi-version codec & fix not returning 
client registration failure msg (#7000)
18a220d481 is described below

commit 18a220d481265ad7b95d425406bc1ba9f7722d88
Author: justabug <[email protected]>
AuthorDate: Sat Feb 7 23:49:23 2026 +0800

    optimize : support multi-version codec & fix not returning client 
registration failure msg (#7000)
---
 changes/en-us/2.x.md                               |   1 +
 changes/zh-cn/2.x.md                               |   1 +
 .../resources/static/console-fe/package-lock.json  |  25 +-
 .../seata/core/protocol/ProtocolConstants.java     |   9 +-
 .../seata/core/protocol/RegisterRMResponse.java    |   1 +
 .../seata/core/protocol/RegisterTMResponse.java    |   1 +
 .../org/apache/seata/core/protocol/Version.java    |   3 +
 .../seata/core/rpc/netty/MultiProtocolDecoder.java |  71 +++-
 .../seata/core/rpc/netty/NettyClientBootstrap.java |  14 +-
 .../seata/core/rpc/netty/ProtocolDecoder.java      |   2 +
 .../seata/core/rpc/netty/ProtocolEncoder.java      |   2 +
 .../seata/core/rpc/netty/v0/ProtocolDecoderV0.java |   7 +-
 .../seata/core/rpc/netty/v0/ProtocolEncoderV0.java |   8 +-
 .../seata/core/rpc/netty/v1/ProtocolDecoderV1.java |   7 +-
 .../seata/core/rpc/netty/v1/ProtocolEncoderV1.java |   9 +-
 .../ProtocolDecoderV2.java}                        |  16 +-
 .../ProtocolEncoderV2.java}                        |  17 +-
 .../core/rpc/processor/server/RegRmProcessor.java  |   1 +
 .../core/rpc/processor/server/RegTmProcessor.java  |   1 +
 .../seata/core/protocol/ProtocolConstantsTest.java |   2 +-
 .../processor/MockRegisterProcessor.java           |  46 ++-
 .../package-lock.json                              |  16 +-
 .../serializer/seata/MessageCodecFactory.java      |  24 +-
 .../seata/serializer/seata/MultiVersionCodec.java  |  47 +--
 .../seata/serializer/seata/SeataSerializer.java    |   5 +-
 .../v2/AbstractIdentifyResponseCodecV2.java        |  69 ++++
 .../protocol/v2/RegisterRMResponseCodecV2.java     |  17 +-
 .../protocol/v2/RegisterTMResponseCodecV2.java     |  17 +-
 .../seata/serializer/SeataSerializerV1.java        |  89 +++++
 .../seata/serializer/SeataSerializerV2.java        |  41 +--
 .../serializer/seata/MultiVersionCodecTest.java    |  39 +-
 .../protocol/v2/RegisterResponseCodecV2Test.java   |  89 +++++
 .../seata/serializer/SeataSerializerV1Test.java    |  82 +++++
 .../core/rpc/netty/CodecTestCheckAuthHandler.java  |  21 +-
 .../core/rpc/netty/MultiProtocolDecoderTest.java   | 148 ++++++++
 .../seata/core/rpc/netty/TestClientHandler.java    |  73 ++++
 .../seata/core/rpc/netty/TestServerHandler.java    | 138 +++++++
 .../MultiVersionCompatibilityTest.java             | 405 +++++++++++++++++++++
 .../netty/multiversion/ServerV1ToClientV1Test.java |  70 ++++
 .../netty/multiversion/ServerV1ToClientV2Test.java |  71 ++++
 .../netty/multiversion/ServerV2ToClientV1Test.java |  68 ++++
 .../netty/multiversion/ServerV2ToClientV2Test.java |  72 ++++
 ....apache.seata.core.rpc.RegisterCheckAuthHandler |  17 +
 43 files changed, 1670 insertions(+), 192 deletions(-)

diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md
index 6dc97d36a1..0cfbd82a5f 100644
--- a/changes/en-us/2.x.md
+++ b/changes/en-us/2.x.md
@@ -21,6 +21,7 @@ Add changes here for all PR submitted to the 2.x branch.
 ### feature:
 - [[#7882](https://github.com/apache/incubator-seata/pull/7882)] add metrics 
for NamingServer
 - [[#7760](https://github.com/apache/incubator-seata/pull/7760)] unify 
Jackson/fastjson serialization
+- [[#7000](https://github.com/apache/incubator-seata/pull/7000)] support 
multi-version codec & fix not returning client registration failure msg.
 - [[#7865](https://github.com/apache/incubator-seata/pull/7865)] add Benchmark 
CLI tool
 
 
diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md
index d988bdf052..7c00403684 100644
--- a/changes/zh-cn/2.x.md
+++ b/changes/zh-cn/2.x.md
@@ -22,6 +22,7 @@
 
 - [[#7882](https://github.com/apache/incubator-seata/pull/7882)] 
为NamingServer增加Metrics监控
 - [[#7760](https://github.com/apache/incubator-seata/pull/7760)] 
统一Jackson/fastjson序列化器
+- [[#7000](https://github.com/apache/incubator-seata/pull/7000)] 
支持多版本codec,修复不返回客户端注册失败消息的问题
 - [[#7865](https://github.com/apache/incubator-seata/pull/7865)] 新增 Benchmark 
命令行工具
 
 ### bugfix:
diff --git a/console/src/main/resources/static/console-fe/package-lock.json 
b/console/src/main/resources/static/console-fe/package-lock.json
index 426f76b835..359fba8672 100644
--- a/console/src/main/resources/static/console-fe/package-lock.json
+++ b/console/src/main/resources/static/console-fe/package-lock.json
@@ -7706,16 +7706,6 @@
         "node": ">= 0.8"
       }
     },
-    "node_modules/encoding": {
-      "version": "0.1.13",
-      "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz";,
-      "integrity": 
"sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
-      "license": "MIT",
-      "optional": true,
-      "dependencies": {
-        "iconv-lite": "^0.6.2"
-      }
-    },
     "node_modules/enhanced-resolve": {
       "version": "5.18.3",
       "resolved": 
"https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz";,
@@ -10240,19 +10230,6 @@
         "node": ">=10.18"
       }
     },
-    "node_modules/iconv-lite": {
-      "version": "0.6.3",
-      "resolved": 
"https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz";,
-      "integrity": 
"sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
-      "license": "MIT",
-      "optional": true,
-      "dependencies": {
-        "safer-buffer": ">= 2.1.2 < 3.0.0"
-      },
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
     "node_modules/icss-utils": {
       "version": "5.1.0",
       "resolved": 
"https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz";,
@@ -14998,7 +14975,7 @@
       "version": "2.1.2",
       "resolved": 
"https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz";,
       "integrity": 
"sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
-      "devOptional": true,
+      "dev": true,
       "license": "MIT"
     },
     "node_modules/sass-graph": {
diff --git 
a/core/src/main/java/org/apache/seata/core/protocol/ProtocolConstants.java 
b/core/src/main/java/org/apache/seata/core/protocol/ProtocolConstants.java
index c51cbb936b..1d527e0e8e 100644
--- a/core/src/main/java/org/apache/seata/core/protocol/ProtocolConstants.java
+++ b/core/src/main/java/org/apache/seata/core/protocol/ProtocolConstants.java
@@ -38,14 +38,19 @@ public interface ProtocolConstants {
     byte VERSION_0 = 0;
 
     /**
-     * Protocol version
+     * Protocol version 1
      */
     byte VERSION_1 = 1;
 
+    /**
+     * Protocol version 2
+     */
+    byte VERSION_2 = 2;
+
     /**
      * Protocol version
      */
-    byte VERSION = VERSION_1;
+    byte VERSION = VERSION_2;
 
     /**
      * Max frame length
diff --git 
a/core/src/main/java/org/apache/seata/core/protocol/RegisterRMResponse.java 
b/core/src/main/java/org/apache/seata/core/protocol/RegisterRMResponse.java
index 7dbce406cd..c068c38835 100644
--- a/core/src/main/java/org/apache/seata/core/protocol/RegisterRMResponse.java
+++ b/core/src/main/java/org/apache/seata/core/protocol/RegisterRMResponse.java
@@ -39,6 +39,7 @@ public class RegisterRMResponse extends 
AbstractIdentifyResponse implements Seri
     public RegisterRMResponse(boolean result) {
         super();
         setIdentified(result);
+        setResultCode(result ? ResultCode.Success : ResultCode.Failed);
     }
 
     @Override
diff --git 
a/core/src/main/java/org/apache/seata/core/protocol/RegisterTMResponse.java 
b/core/src/main/java/org/apache/seata/core/protocol/RegisterTMResponse.java
index 0158a9b83e..c5ae0f1060 100644
--- a/core/src/main/java/org/apache/seata/core/protocol/RegisterTMResponse.java
+++ b/core/src/main/java/org/apache/seata/core/protocol/RegisterTMResponse.java
@@ -39,6 +39,7 @@ public class RegisterTMResponse extends 
AbstractIdentifyResponse implements Seri
     public RegisterTMResponse(boolean result) {
         super();
         setIdentified(result);
+        setResultCode(result ? ResultCode.Success : ResultCode.Failed);
     }
 
     @Override
diff --git a/core/src/main/java/org/apache/seata/core/protocol/Version.java 
b/core/src/main/java/org/apache/seata/core/protocol/Version.java
index 3c7e77376c..219e6b8f6a 100644
--- a/core/src/main/java/org/apache/seata/core/protocol/Version.java
+++ b/core/src/main/java/org/apache/seata/core/protocol/Version.java
@@ -41,6 +41,9 @@ public class Version {
     private static final String VERSION_0_7_1 = "0.7.1";
     private static final String VERSION_1_5_0 = "1.5.0";
     private static final String VERSION_2_3_0 = "2.3.0";
+
+    public static final String VERSION_0_7_0 = "0.7.0";
+
     private static final int MAX_VERSION_DOT = 3;
 
     /**
diff --git 
a/core/src/main/java/org/apache/seata/core/rpc/netty/MultiProtocolDecoder.java 
b/core/src/main/java/org/apache/seata/core/rpc/netty/MultiProtocolDecoder.java
index feef1e6f51..8d19e29c1a 100644
--- 
a/core/src/main/java/org/apache/seata/core/rpc/netty/MultiProtocolDecoder.java
+++ 
b/core/src/main/java/org/apache/seata/core/rpc/netty/MultiProtocolDecoder.java
@@ -27,9 +27,13 @@ import org.apache.seata.core.rpc.netty.v0.ProtocolDecoderV0;
 import org.apache.seata.core.rpc.netty.v0.ProtocolEncoderV0;
 import org.apache.seata.core.rpc.netty.v1.ProtocolDecoderV1;
 import org.apache.seata.core.rpc.netty.v1.ProtocolEncoderV1;
+import org.apache.seata.core.rpc.netty.v2.ProtocolDecoderV2;
+import org.apache.seata.core.rpc.netty.v2.ProtocolEncoderV2;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -64,17 +68,27 @@ public class MultiProtocolDecoder extends 
LengthFieldBasedFrameDecoder {
 
     private final ChannelHandler[] channelHandlers;
 
+    private final byte maxCurrentVersion; // For testing purposes
+
     public MultiProtocolDecoder(ChannelHandler... channelHandlers) {
         // default is 8M
         this(ProtocolConstants.MAX_FRAME_LENGTH, channelHandlers);
     }
 
-    public MultiProtocolDecoder() {
-        // default is 8M
-        this(ProtocolConstants.MAX_FRAME_LENGTH, null);
+    /**
+     * Constructor for testing purposes to force a specific protocol version
+     * @param maxCurrentVersion the protocol version to force
+     * @param channelHandlers additional channel handlers
+     */
+    MultiProtocolDecoder(byte maxCurrentVersion, ChannelHandler... 
channelHandlers) {
+        this(ProtocolConstants.MAX_FRAME_LENGTH, maxCurrentVersion, 
channelHandlers);
     }
 
     public MultiProtocolDecoder(int maxFrameLength, ChannelHandler[] 
channelHandlers) {
+        this(maxFrameLength, ProtocolConstants.VERSION, channelHandlers);
+    }
+
+    MultiProtocolDecoder(int maxFrameLength, byte maxCurrentVersion, 
ChannelHandler[] channelHandlers) {
         /*
         int maxFrameLength,
         int lengthFieldOffset,  magic code is 2B, and version is 1B, and then 
FullLength. so value is 3
@@ -83,13 +97,16 @@ public class MultiProtocolDecoder extends 
LengthFieldBasedFrameDecoder {
         int initialBytesToStrip we will check magic code and version self, so 
do not strip any bytes. so values is 0
         */
         super(maxFrameLength, 3, 4, -7, 0);
+        this.maxCurrentVersion = maxCurrentVersion;
         this.protocolDecoderMap = ImmutableMap.<Byte, ProtocolDecoder>builder()
                 .put(ProtocolConstants.VERSION_0, new ProtocolDecoderV0())
                 .put(ProtocolConstants.VERSION_1, new ProtocolDecoderV1())
+                .put(ProtocolConstants.VERSION_2, new ProtocolDecoderV2())
                 .build();
         this.protocolEncoderMap = ImmutableMap.<Byte, ProtocolEncoder>builder()
                 .put(ProtocolConstants.VERSION_0, new ProtocolEncoderV0())
                 .put(ProtocolConstants.VERSION_1, new ProtocolEncoderV1())
+                .put(ProtocolConstants.VERSION_2, new ProtocolEncoderV2())
                 .build();
         this.channelHandlers = channelHandlers;
     }
@@ -110,22 +127,17 @@ public class MultiProtocolDecoder extends 
LengthFieldBasedFrameDecoder {
 
             if (decoded instanceof ByteBuf) {
                 frame = (ByteBuf) decoded;
-                ProtocolDecoder decoder = protocolDecoderMap.get(version);
-                if (decoder == null) {
+                // Ensure version is within supported range
+                if (version > maxCurrentVersion) {
+                    version = maxCurrentVersion;
                     LOGGER.error(
-                            "Decoder not found, version={}, use current 
version({})",
+                            "Detected version {} is greater than max supported 
version {}, using max supported version.",
                             version,
-                            ProtocolConstants.VERSION);
-                    decoder = 
protocolDecoderMap.get(ProtocolConstants.VERSION);
+                            maxCurrentVersion);
                 }
+                ProtocolDecoder decoder = protocolDecoderMap.get(version);
                 ProtocolEncoder encoder = protocolEncoderMap.get(version);
-                if (encoder == null) {
-                    LOGGER.error(
-                            "Encoder not found, version: {}, use current 
version({})",
-                            version,
-                            ProtocolConstants.VERSION);
-                    encoder = 
protocolEncoderMap.get(ProtocolConstants.VERSION);
-                }
+
                 try {
                     if (decoder == null || encoder == null) {
                         throw new UnsupportedOperationException("Unsupported 
version: " + version);
@@ -135,6 +147,8 @@ public class MultiProtocolDecoder extends 
LengthFieldBasedFrameDecoder {
                     if (version != ProtocolConstants.VERSION_0) {
                         frame.release();
                     }
+                    // Remove existing encoder if it exists (for client-side 
compatibility)
+                    removeExistingEncoder(ctx, encoder);
                     ctx.pipeline().addLast((ChannelHandler) decoder);
                     ctx.pipeline().addLast((ChannelHandler) encoder);
                     if (channelHandlers != null) {
@@ -186,4 +200,31 @@ public class MultiProtocolDecoder extends 
LengthFieldBasedFrameDecoder {
     protected boolean isV0(byte version) {
         return version == ProtocolConstants.VERSION_0;
     }
+
+    /**
+     * Remove existing encoder from pipeline to avoid conflicts when adding 
new encoder.
+     * This is particularly important for client-side where an encoder may 
already exist.
+     */
+    private void removeExistingEncoder(ChannelHandlerContext ctx, 
ProtocolEncoder newEncoder) {
+        // Create a list to collect handlers to remove (avoid 
ConcurrentModificationException)
+        List<String> handlersToRemove = new ArrayList<>();
+
+        ctx.pipeline().toMap().forEach((name, handler) -> {
+            // Remove if it's a ProtocolEncoder but not the same instance 
we're about to add
+            // and not a ProtocolDecoder (which might also implement 
ProtocolEncoder)
+            if (handler instanceof ProtocolEncoder && !(handler instanceof 
ProtocolDecoder) && handler != newEncoder) {
+                handlersToRemove.add(name);
+            }
+        });
+
+        // Remove the handlers
+        handlersToRemove.forEach(name -> {
+            try {
+                ctx.pipeline().remove(name);
+                LOGGER.debug("Removed existing encoder: {}", name);
+            } catch (Exception e) {
+                LOGGER.warn("Failed to remove existing encoder {}: {}", name, 
e.getMessage());
+            }
+        });
+    }
 }
diff --git 
a/core/src/main/java/org/apache/seata/core/rpc/netty/NettyClientBootstrap.java 
b/core/src/main/java/org/apache/seata/core/rpc/netty/NettyClientBootstrap.java
index dd732eaa6a..b1bae64c4d 100644
--- 
a/core/src/main/java/org/apache/seata/core/rpc/netty/NettyClientBootstrap.java
+++ 
b/core/src/main/java/org/apache/seata/core/rpc/netty/NettyClientBootstrap.java
@@ -44,8 +44,7 @@ import org.apache.seata.core.protocol.Protocol;
 import org.apache.seata.core.rpc.RemotingBootstrap;
 import org.apache.seata.core.rpc.netty.grpc.GrpcDecoder;
 import org.apache.seata.core.rpc.netty.grpc.GrpcEncoder;
-import org.apache.seata.core.rpc.netty.v1.ProtocolDecoderV1;
-import org.apache.seata.core.rpc.netty.v1.ProtocolEncoderV1;
+import org.apache.seata.core.rpc.netty.v2.ProtocolEncoderV2;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -97,7 +96,7 @@ public class NettyClientBootstrap implements 
RemotingBootstrap {
      *
      * @param handlers the handlers
      */
-    protected void setChannelHandlers(final ChannelHandler... handlers) {
+    public void setChannelHandlers(final ChannelHandler... handlers) {
         if (handlers != null) {
             channelHandlers = handlers;
         }
@@ -146,10 +145,11 @@ public class NettyClientBootstrap implements 
RemotingBootstrap {
                             nettyClientConfig.getChannelMaxReadIdleSeconds(),
                             nettyClientConfig.getChannelMaxWriteIdleSeconds(),
                             nettyClientConfig.getChannelMaxAllIdleSeconds()));
-                    pipeline.addLast(new ProtocolDecoderV1()).addLast(new 
ProtocolEncoderV1());
-                    if (channelHandlers != null) {
-                        addChannelPipelineLast(ch, channelHandlers);
-                    }
+                    // Use ProtocolEncoderV2 for sending requests (client 
always sends latest version)
+                    pipeline.addLast(new ProtocolEncoderV2());
+                    // Use MultiProtocolDecoder for receiving responses 
(supports V0/V1/V2)
+                    // channelHandlers will be added by MultiProtocolDecoder 
after first message
+                    pipeline.addLast(new 
MultiProtocolDecoder(channelHandlers));
                 }
             }
         });
diff --git 
a/core/src/main/java/org/apache/seata/core/rpc/netty/ProtocolDecoder.java 
b/core/src/main/java/org/apache/seata/core/rpc/netty/ProtocolDecoder.java
index 509be29a19..9d2cb82857 100644
--- a/core/src/main/java/org/apache/seata/core/rpc/netty/ProtocolDecoder.java
+++ b/core/src/main/java/org/apache/seata/core/rpc/netty/ProtocolDecoder.java
@@ -26,4 +26,6 @@ import org.apache.seata.core.protocol.RpcMessage;
 public interface ProtocolDecoder {
 
     RpcMessage decodeFrame(ByteBuf in);
+
+    byte protocolVersion();
 }
diff --git 
a/core/src/main/java/org/apache/seata/core/rpc/netty/ProtocolEncoder.java 
b/core/src/main/java/org/apache/seata/core/rpc/netty/ProtocolEncoder.java
index 6c91164fff..09363cc880 100644
--- a/core/src/main/java/org/apache/seata/core/rpc/netty/ProtocolEncoder.java
+++ b/core/src/main/java/org/apache/seata/core/rpc/netty/ProtocolEncoder.java
@@ -25,4 +25,6 @@ import org.apache.seata.core.protocol.RpcMessage;
  **/
 public interface ProtocolEncoder {
     void encode(RpcMessage rpcMessage, ByteBuf out);
+
+    byte protocolVersion();
 }
diff --git 
a/core/src/main/java/org/apache/seata/core/rpc/netty/v0/ProtocolDecoderV0.java 
b/core/src/main/java/org/apache/seata/core/rpc/netty/v0/ProtocolDecoderV0.java
index 287898d8f1..1606033695 100644
--- 
a/core/src/main/java/org/apache/seata/core/rpc/netty/v0/ProtocolDecoderV0.java
+++ 
b/core/src/main/java/org/apache/seata/core/rpc/netty/v0/ProtocolDecoderV0.java
@@ -130,7 +130,7 @@ public class ProtocolDecoderV0 extends 
LengthFieldBasedFrameDecoder implements P
             System.arraycopy(bs, 0, bs2, 2, length);
             byte codecType = isSeataCodec ? SerializerType.SEATA.getCode() : 
SerializerType.HESSIAN.getCode();
             Serializer serializer =
-                    
SerializerServiceLoader.load(SerializerType.getByCode(codecType), 
ProtocolConstants.VERSION_0);
+                    
SerializerServiceLoader.load(SerializerType.getByCode(codecType), 
protocolVersion());
             rpcMessage.setBody(serializer.deserialize(bs2));
         } catch (Exception e) {
             LOGGER.error("decode error", e);
@@ -151,4 +151,9 @@ public class ProtocolDecoderV0 extends 
LengthFieldBasedFrameDecoder implements P
             throw new DecodeException(exx);
         }
     }
+
+    @Override
+    public byte protocolVersion() {
+        return ProtocolConstants.VERSION_0;
+    }
 }
diff --git 
a/core/src/main/java/org/apache/seata/core/rpc/netty/v0/ProtocolEncoderV0.java 
b/core/src/main/java/org/apache/seata/core/rpc/netty/v0/ProtocolEncoderV0.java
index 9c30a9dbf5..074ff78e9c 100644
--- 
a/core/src/main/java/org/apache/seata/core/rpc/netty/v0/ProtocolEncoderV0.java
+++ 
b/core/src/main/java/org/apache/seata/core/rpc/netty/v0/ProtocolEncoderV0.java
@@ -82,8 +82,7 @@ public class ProtocolEncoderV0 extends MessageToByteEncoder 
implements ProtocolE
             }
 
             byte[] bodyBytes = null;
-            Serializer serializer =
-                    
SerializerServiceLoader.load(SerializerType.getByCode(codec), 
ProtocolConstants.VERSION_0);
+            Serializer serializer = 
SerializerServiceLoader.load(SerializerType.getByCode(codec), 
protocolVersion());
             bodyBytes = serializer.serialize(msg.getBody());
 
             if (msg.isSeataCodec()) {
@@ -119,4 +118,9 @@ public class ProtocolEncoderV0 extends MessageToByteEncoder 
implements ProtocolE
             LOGGER.error("Encode request error!", e);
         }
     }
+
+    @Override
+    public byte protocolVersion() {
+        return ProtocolConstants.VERSION_0;
+    }
 }
diff --git 
a/core/src/main/java/org/apache/seata/core/rpc/netty/v1/ProtocolDecoderV1.java 
b/core/src/main/java/org/apache/seata/core/rpc/netty/v1/ProtocolDecoderV1.java
index 4bfa09c982..3f2059d098 100644
--- 
a/core/src/main/java/org/apache/seata/core/rpc/netty/v1/ProtocolDecoderV1.java
+++ 
b/core/src/main/java/org/apache/seata/core/rpc/netty/v1/ProtocolDecoderV1.java
@@ -126,7 +126,7 @@ public class ProtocolDecoderV1 extends 
LengthFieldBasedFrameDecoder implements P
                 bs = compressor.decompress(bs);
                 SerializerType protocolType = 
SerializerType.getByCode(rpcMessage.getCodec());
                 if (this.supportDeSerializerTypes.contains(protocolType)) {
-                    Serializer serializer = 
SerializerServiceLoader.load(protocolType, ProtocolConstants.VERSION_1);
+                    Serializer serializer = 
SerializerServiceLoader.load(protocolType, protocolVersion());
                     rpcMessage.setBody(serializer.deserialize(bs));
                 } else {
                     throw new IllegalArgumentException("SerializerType not 
match");
@@ -156,4 +156,9 @@ public class ProtocolDecoderV1 extends 
LengthFieldBasedFrameDecoder implements P
         }
         return decoded;
     }
+
+    @Override
+    public byte protocolVersion() {
+        return ProtocolConstants.VERSION_1;
+    }
 }
diff --git 
a/core/src/main/java/org/apache/seata/core/rpc/netty/v1/ProtocolEncoderV1.java 
b/core/src/main/java/org/apache/seata/core/rpc/netty/v1/ProtocolEncoderV1.java
index 7ac12c6ac4..ffcb92a623 100644
--- 
a/core/src/main/java/org/apache/seata/core/rpc/netty/v1/ProtocolEncoderV1.java
+++ 
b/core/src/main/java/org/apache/seata/core/rpc/netty/v1/ProtocolEncoderV1.java
@@ -72,7 +72,7 @@ public class ProtocolEncoderV1 extends MessageToByteEncoder 
implements ProtocolE
 
             byte messageType = rpcMessage.getMessageType();
             out.writeBytes(ProtocolConstants.MAGIC_CODE_BYTES);
-            out.writeByte(ProtocolConstants.VERSION_1);
+            out.writeByte(protocolVersion());
             // full Length(4B) and head length(2B) will fix in the end.
             out.writerIndex(out.writerIndex() + 6);
             out.writeByte(messageType);
@@ -93,7 +93,7 @@ public class ProtocolEncoderV1 extends MessageToByteEncoder 
implements ProtocolE
                     && messageType != 
ProtocolConstants.MSGTYPE_HEARTBEAT_RESPONSE) {
                 // heartbeat has no body
                 Serializer serializer = SerializerServiceLoader.load(
-                        SerializerType.getByCode(rpcMessage.getCodec()), 
ProtocolConstants.VERSION_1);
+                        SerializerType.getByCode(rpcMessage.getCodec()), 
protocolVersion());
                 bodyBytes = serializer.serialize(rpcMessage.getBody());
                 Compressor compressor = 
CompressorFactory.getCompressor(rpcMessage.getCompressor());
                 bodyBytes = compressor.compress(bodyBytes);
@@ -131,4 +131,9 @@ public class ProtocolEncoderV1 extends MessageToByteEncoder 
implements ProtocolE
             LOGGER.error("Encode request error!", e);
         }
     }
+
+    @Override
+    public byte protocolVersion() {
+        return ProtocolConstants.VERSION_1;
+    }
 }
diff --git 
a/core/src/main/java/org/apache/seata/core/rpc/netty/ProtocolDecoder.java 
b/core/src/main/java/org/apache/seata/core/rpc/netty/v2/ProtocolDecoderV2.java
similarity index 70%
copy from 
core/src/main/java/org/apache/seata/core/rpc/netty/ProtocolDecoder.java
copy to 
core/src/main/java/org/apache/seata/core/rpc/netty/v2/ProtocolDecoderV2.java
index 509be29a19..5735be7446 100644
--- a/core/src/main/java/org/apache/seata/core/rpc/netty/ProtocolDecoder.java
+++ 
b/core/src/main/java/org/apache/seata/core/rpc/netty/v2/ProtocolDecoderV2.java
@@ -14,16 +14,18 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.seata.core.rpc.netty;
+package org.apache.seata.core.rpc.netty.v2;
 
-import io.netty.buffer.ByteBuf;
-import org.apache.seata.core.protocol.RpcMessage;
+import org.apache.seata.core.protocol.ProtocolConstants;
+import org.apache.seata.core.rpc.netty.v1.ProtocolDecoderV1;
 
 /**
- * the protocol decoder
- *
+ * Decoder of protocol-v2
  **/
-public interface ProtocolDecoder {
+public class ProtocolDecoderV2 extends ProtocolDecoderV1 {
 
-    RpcMessage decodeFrame(ByteBuf in);
+    @Override
+    public byte protocolVersion() {
+        return ProtocolConstants.VERSION_2;
+    }
 }
diff --git 
a/core/src/main/java/org/apache/seata/core/rpc/netty/ProtocolDecoder.java 
b/core/src/main/java/org/apache/seata/core/rpc/netty/v2/ProtocolEncoderV2.java
similarity index 70%
copy from 
core/src/main/java/org/apache/seata/core/rpc/netty/ProtocolDecoder.java
copy to 
core/src/main/java/org/apache/seata/core/rpc/netty/v2/ProtocolEncoderV2.java
index 509be29a19..f8149778a0 100644
--- a/core/src/main/java/org/apache/seata/core/rpc/netty/ProtocolDecoder.java
+++ 
b/core/src/main/java/org/apache/seata/core/rpc/netty/v2/ProtocolEncoderV2.java
@@ -14,16 +14,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.seata.core.rpc.netty;
+package org.apache.seata.core.rpc.netty.v2;
 
-import io.netty.buffer.ByteBuf;
-import org.apache.seata.core.protocol.RpcMessage;
+import org.apache.seata.core.protocol.ProtocolConstants;
+import org.apache.seata.core.rpc.netty.v1.ProtocolEncoderV1;
 
 /**
- * the protocol decoder
- *
+ * Encoder of protocol-v2
  **/
-public interface ProtocolDecoder {
-
-    RpcMessage decodeFrame(ByteBuf in);
+public class ProtocolEncoderV2 extends ProtocolEncoderV1 {
+    @Override
+    public byte protocolVersion() {
+        return ProtocolConstants.VERSION_2;
+    }
 }
diff --git 
a/core/src/main/java/org/apache/seata/core/rpc/processor/server/RegRmProcessor.java
 
b/core/src/main/java/org/apache/seata/core/rpc/processor/server/RegRmProcessor.java
index 10a89ae9c2..88578f446a 100644
--- 
a/core/src/main/java/org/apache/seata/core/rpc/processor/server/RegRmProcessor.java
+++ 
b/core/src/main/java/org/apache/seata/core/rpc/processor/server/RegRmProcessor.java
@@ -82,6 +82,7 @@ public class RegRmProcessor implements RemotingProcessor {
                             message.getTransactionServiceGroup(),
                             message.getApplicationId());
                 }
+                errorInfo = "RM checkAuth fail";
             }
         } catch (Exception exx) {
             isSuccess = false;
diff --git 
a/core/src/main/java/org/apache/seata/core/rpc/processor/server/RegTmProcessor.java
 
b/core/src/main/java/org/apache/seata/core/rpc/processor/server/RegTmProcessor.java
index 2cc5d4ed57..eba5102577 100644
--- 
a/core/src/main/java/org/apache/seata/core/rpc/processor/server/RegTmProcessor.java
+++ 
b/core/src/main/java/org/apache/seata/core/rpc/processor/server/RegTmProcessor.java
@@ -83,6 +83,7 @@ public class RegTmProcessor implements RemotingProcessor {
                             message.getTransactionServiceGroup(),
                             message.getApplicationId());
                 }
+                errorInfo = "TM checkAuth fail";
             }
         } catch (Exception exx) {
             isSuccess = false;
diff --git 
a/core/src/test/java/org/apache/seata/core/protocol/ProtocolConstantsTest.java 
b/core/src/test/java/org/apache/seata/core/protocol/ProtocolConstantsTest.java
index 9942e6a505..e46d8b6be6 100644
--- 
a/core/src/test/java/org/apache/seata/core/protocol/ProtocolConstantsTest.java
+++ 
b/core/src/test/java/org/apache/seata/core/protocol/ProtocolConstantsTest.java
@@ -37,7 +37,7 @@ public class ProtocolConstantsTest {
     public void testVersionConstants() {
         assertEquals(0, ProtocolConstants.VERSION_0);
         assertEquals(1, ProtocolConstants.VERSION_1);
-        assertEquals(ProtocolConstants.VERSION_1, ProtocolConstants.VERSION);
+        assertEquals(ProtocolConstants.VERSION_2, ProtocolConstants.VERSION);
     }
 
     @Test
diff --git 
a/mock-server/src/main/java/org/apache/seata/mockserver/processor/MockRegisterProcessor.java
 
b/mock-server/src/main/java/org/apache/seata/mockserver/processor/MockRegisterProcessor.java
index 2651960217..d20da5b7d8 100644
--- 
a/mock-server/src/main/java/org/apache/seata/mockserver/processor/MockRegisterProcessor.java
+++ 
b/mock-server/src/main/java/org/apache/seata/mockserver/processor/MockRegisterProcessor.java
@@ -17,6 +17,8 @@
 package org.apache.seata.mockserver.processor;
 
 import io.netty.channel.ChannelHandlerContext;
+import org.apache.seata.common.util.StringUtils;
+import org.apache.seata.core.protocol.AbstractResultMessage;
 import org.apache.seata.core.protocol.RegisterRMRequest;
 import org.apache.seata.core.protocol.RegisterRMResponse;
 import org.apache.seata.core.protocol.RegisterTMRequest;
@@ -46,27 +48,31 @@ public class MockRegisterProcessor implements 
RemotingProcessor {
 
     @Override
     public void process(ChannelHandlerContext ctx, RpcMessage rpcMessage) 
throws Exception {
-        if (role == Role.TM) {
-            RegisterTMRequest message = (RegisterTMRequest) 
rpcMessage.getBody();
-            LOGGER.info("message = " + message);
-
-            ChannelManager.registerTMChannel(message, ctx.channel());
-            Version.putChannelVersion(ctx.channel(), message.getVersion());
-
-            RegisterTMResponse resp = new RegisterTMResponse();
-            remotingServer.sendAsyncResponse(rpcMessage, ctx.channel(), resp);
-            LOGGER.info("sendAsyncResponse: {}", resp);
-        } else if (role == Role.RM) {
-            RegisterRMRequest message = (RegisterRMRequest) 
rpcMessage.getBody();
-            LOGGER.info("message = " + message);
-
-            ChannelManager.registerRMChannel(message, ctx.channel());
-            Version.putChannelVersion(ctx.channel(), message.getVersion());
-
-            RegisterRMResponse resp = new RegisterRMResponse();
-            remotingServer.sendAsyncResponse(rpcMessage, ctx.channel(), resp);
-            LOGGER.info("sendAsyncResponse: {}", resp);
+        String errorInfo = StringUtils.EMPTY;
+        AbstractResultMessage response = null;
+        try {
+            if (role == Role.TM) {
+                RegisterTMRequest message = (RegisterTMRequest) 
rpcMessage.getBody();
+                LOGGER.info("reg message = " + message);
+                ChannelManager.registerTMChannel(message, ctx.channel());
+                Version.putChannelVersion(ctx.channel(), message.getVersion());
+                response = new RegisterTMResponse();
+            } else if (role == Role.RM) {
+                RegisterRMRequest message = (RegisterRMRequest) 
rpcMessage.getBody();
+                LOGGER.info("reg message = " + message);
+                ChannelManager.registerRMChannel(message, ctx.channel());
+                Version.putChannelVersion(ctx.channel(), message.getVersion());
+                response = new RegisterRMResponse();
+            }
+        } catch (Exception e) {
+            errorInfo = e.getMessage();
+            LOGGER.error(role + " register fail, error message:{}", errorInfo);
+        }
+        if (StringUtils.isNotEmpty(errorInfo)) {
+            response.setMsg(errorInfo);
         }
+        remotingServer.sendAsyncResponse(rpcMessage, ctx.channel(), response);
+        LOGGER.info("sendAsyncResponse: {}", response);
     }
 
     public enum Role {
diff --git a/saga/seata-saga-statemachine-designer/package-lock.json 
b/saga/seata-saga-statemachine-designer/package-lock.json
index 731a4be522..df02a49409 100644
--- a/saga/seata-saga-statemachine-designer/package-lock.json
+++ b/saga/seata-saga-statemachine-designer/package-lock.json
@@ -97,6 +97,7 @@
       "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.7.tgz";,
       "integrity": 
"sha512-+UpDgowcmqe36d4NwqvKsyPMlOLNGMsfMmQ5WGCu+siCe3t3dfe9njrzGfdN4qq+bcNUt0+Vw6haRxBOycs4dw==",
       "dev": true,
+      "peer": true,
       "dependencies": {
         "@ampproject/remapping": "^2.2.0",
         "@babel/code-frame": "^7.23.5",
@@ -2850,6 +2851,7 @@
       "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz";,
       "integrity": 
"sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
       "dev": true,
+      "peer": true,
       "bin": {
         "acorn": "bin/acorn"
       },
@@ -2880,6 +2882,7 @@
       "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz";,
       "integrity": 
"sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
       "dev": true,
+      "peer": true,
       "dependencies": {
         "fast-deep-equal": "^3.1.1",
         "json-schema-traverse": "^1.0.0",
@@ -3367,6 +3370,7 @@
           "url": "https://github.com/sponsors/ai";
         }
       ],
+      "peer": true,
       "dependencies": {
         "caniuse-lite": "^1.0.30001565",
         "electron-to-chromium": "^1.4.601",
@@ -4470,6 +4474,7 @@
       "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz";,
       "integrity": 
"sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==",
       "dev": true,
+      "peer": true,
       "dependencies": {
         "@eslint-community/eslint-utils": "^4.2.0",
         "@eslint-community/regexpp": "^4.6.1",
@@ -4611,6 +4616,7 @@
       "resolved": 
"https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz";,
       "integrity": 
"sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==",
       "dev": true,
+      "peer": true,
       "dependencies": {
         "array-includes": "^3.1.7",
         "array.prototype.findlastindex": "^1.2.3",
@@ -4685,6 +4691,7 @@
       "resolved": 
"https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.8.0.tgz";,
       "integrity": 
"sha512-Hdh937BS3KdwwbBaKd5+PLCOmYY6U4f2h9Z2ktwtNKvIdIEu137rjYbcb9ApSbVJfWxANNuiKTD/9tOKjK9qOA==",
       "dev": true,
+      "peer": true,
       "dependencies": {
         "@babel/runtime": "^7.23.2",
         "aria-query": "^5.3.0",
@@ -4749,6 +4756,7 @@
       "resolved": 
"https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.33.2.tgz";,
       "integrity": 
"sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==",
       "dev": true,
+      "peer": true,
       "dependencies": {
         "array-includes": "^3.1.6",
         "array.prototype.flatmap": "^1.3.1",
@@ -8062,6 +8070,7 @@
           "url": "https://github.com/sponsors/ai";
         }
       ],
+      "peer": true,
       "dependencies": {
         "nanoid": "^3.3.7",
         "picocolors": "^1.0.0",
@@ -9468,6 +9477,7 @@
       "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz";,
       "integrity": 
"sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
       "dev": true,
+      "peer": true,
       "dependencies": {
         "fast-deep-equal": "^3.1.1",
         "fast-json-stable-stringify": "^2.0.0",
@@ -9618,7 +9628,8 @@
       "version": "2.6.2",
       "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz";,
       "integrity": 
"sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==",
-      "dev": true
+      "dev": true,
+      "peer": true
     },
     "node_modules/type-check": {
       "version": "0.4.0",
@@ -9912,6 +9923,7 @@
       "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.95.0.tgz";,
       "integrity": 
"sha512-2t3XstrKULz41MNMBF+cJ97TyHdyQ8HCt//pqErqDvNjU9YQBnZxIHa11VXsi7F3mb5/aO2tuDxdeTPdU7xu9Q==",
       "dev": true,
+      "peer": true,
       "dependencies": {
         "@types/estree": "^1.0.5",
         "@webassemblyjs/ast": "^1.12.1",
@@ -9958,6 +9970,7 @@
       "resolved": 
"https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz";,
       "integrity": 
"sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==",
       "dev": true,
+      "peer": true,
       "dependencies": {
         "@discoveryjs/json-ext": "^0.5.0",
         "@webpack-cli/configtest": "^1.2.0",
@@ -10148,6 +10161,7 @@
       "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz";,
       "integrity": 
"sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
       "dev": true,
+      "peer": true,
       "dependencies": {
         "fast-deep-equal": "^3.1.1",
         "fast-json-stable-stringify": "^2.0.0",
diff --git 
a/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/MessageCodecFactory.java
 
b/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/MessageCodecFactory.java
index 07d52825d7..758c3155d4 100644
--- 
a/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/MessageCodecFactory.java
+++ 
b/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/MessageCodecFactory.java
@@ -16,15 +16,7 @@
  */
 package org.apache.seata.serializer.seata;
 
-import org.apache.seata.core.protocol.AbstractMessage;
-import org.apache.seata.core.protocol.BatchResultMessage;
-import org.apache.seata.core.protocol.MergeResultMessage;
-import org.apache.seata.core.protocol.MergedWarpMessage;
-import org.apache.seata.core.protocol.MessageType;
-import org.apache.seata.core.protocol.RegisterRMRequest;
-import org.apache.seata.core.protocol.RegisterRMResponse;
-import org.apache.seata.core.protocol.RegisterTMRequest;
-import org.apache.seata.core.protocol.RegisterTMResponse;
+import org.apache.seata.core.protocol.*;
 import org.apache.seata.core.protocol.transaction.BranchCommitRequest;
 import org.apache.seata.core.protocol.transaction.BranchCommitResponse;
 import org.apache.seata.core.protocol.transaction.BranchRegisterRequest;
@@ -74,6 +66,8 @@ import 
org.apache.seata.serializer.seata.protocol.transaction.GlobalRollbackResp
 import 
org.apache.seata.serializer.seata.protocol.transaction.GlobalStatusRequestCodec;
 import 
org.apache.seata.serializer.seata.protocol.transaction.GlobalStatusResponseCodec;
 import 
org.apache.seata.serializer.seata.protocol.transaction.UndoLogDeleteRequestCodec;
+import org.apache.seata.serializer.seata.protocol.v2.RegisterRMResponseCodecV2;
+import org.apache.seata.serializer.seata.protocol.v2.RegisterTMResponseCodecV2;
 
 import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
@@ -117,13 +111,21 @@ public class MessageCodecFactory {
                 msgCodec = new RegisterTMRequestCodec();
                 break;
             case MessageType.TYPE_REG_CLT_RESULT:
-                msgCodec = new RegisterTMResponseCodec();
+                if (version == ProtocolConstants.VERSION_2) {
+                    msgCodec = new RegisterTMResponseCodecV2();
+                } else {
+                    msgCodec = new RegisterTMResponseCodec();
+                }
                 break;
             case MessageType.TYPE_REG_RM:
                 msgCodec = new RegisterRMRequestCodec();
                 break;
             case MessageType.TYPE_REG_RM_RESULT:
-                msgCodec = new RegisterRMResponseCodec();
+                if (version == ProtocolConstants.VERSION_2) {
+                    msgCodec = new RegisterRMResponseCodecV2();
+                } else {
+                    msgCodec = new RegisterRMResponseCodec();
+                }
                 break;
             case MessageType.TYPE_BRANCH_COMMIT:
                 msgCodec = new BranchCommitRequestCodec();
diff --git 
a/core/src/main/java/org/apache/seata/core/protocol/RegisterRMResponse.java 
b/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/MultiVersionCodec.java
similarity index 54%
copy from 
core/src/main/java/org/apache/seata/core/protocol/RegisterRMResponse.java
copy to 
serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/MultiVersionCodec.java
index 7dbce406cd..c8f0950f51 100644
--- a/core/src/main/java/org/apache/seata/core/protocol/RegisterRMResponse.java
+++ 
b/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/MultiVersionCodec.java
@@ -14,35 +14,40 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.seata.core.protocol;
+package org.apache.seata.serializer.seata;
 
-import java.io.Serializable;
+import java.util.Map;
 
 /**
- * The type Register rm response.
- *
+ * interface MultiVersionCodec
  */
-public class RegisterRMResponse extends AbstractIdentifyResponse implements 
Serializable {
+public interface MultiVersionCodec {
 
-    /**
-     * Instantiates a new Register rm response.
-     */
-    public RegisterRMResponse() {
-        this(true);
-    }
+    Map<VersionRange, MessageSeataCodec> oldVersionCodec();
 
     /**
-     * Instantiates a new Register rm response.
-     *
-     * @param result the result
+     * version range (begin, end]
      */
-    public RegisterRMResponse(boolean result) {
-        super();
-        setIdentified(result);
-    }
+    class VersionRange {
+        private String begin;
+        private String end;
+
+        public VersionRange(String begin, String end) {
+            this.begin = begin;
+            this.end = end;
+        }
+
+        public VersionRange(String end) {
+            this.begin = "0";
+            this.end = end;
+        }
+
+        public String getBegin() {
+            return begin;
+        }
 
-    @Override
-    public short getTypeCode() {
-        return MessageType.TYPE_REG_RM_RESULT;
+        public String getEnd() {
+            return end;
+        }
     }
 }
diff --git 
a/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/SeataSerializer.java
 
b/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/SeataSerializer.java
index c52469eec9..851b3cab03 100644
--- 
a/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/SeataSerializer.java
+++ 
b/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/SeataSerializer.java
@@ -24,6 +24,7 @@ import org.apache.seata.common.util.BufferUtils;
 import org.apache.seata.core.protocol.AbstractMessage;
 import org.apache.seata.core.protocol.ProtocolConstants;
 import org.apache.seata.core.serializer.Serializer;
+import org.apache.seata.serializer.seata.serializer.SeataSerializerV2;
 
 import java.nio.ByteBuffer;
 
@@ -39,6 +40,8 @@ public class SeataSerializer implements Serializer {
             versionSeataSerializer = SeataSerializerV0.getInstance();
         } else if (version == ProtocolConstants.VERSION_1) {
             versionSeataSerializer = SeataSerializerV1.getInstance();
+        } else if (version == ProtocolConstants.VERSION_2) {
+            versionSeataSerializer = SeataSerializerV2.getInstance();
         }
         if (versionSeataSerializer == null) {
             throw new UnsupportedOperationException("version is not 
supported");
@@ -159,7 +162,7 @@ public class SeataSerializer implements Serializer {
         }
     }
 
-    private static <T> T deserializeByVersion(byte[] bytes, byte version) {
+    public static <T> T deserializeByVersion(byte[] bytes, byte version) {
         if (bytes == null || bytes.length == 0) {
             throw new IllegalArgumentException("Nothing to decode.");
         }
diff --git 
a/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/protocol/v2/AbstractIdentifyResponseCodecV2.java
 
b/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/protocol/v2/AbstractIdentifyResponseCodecV2.java
new file mode 100644
index 0000000000..fb1a559815
--- /dev/null
+++ 
b/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/protocol/v2/AbstractIdentifyResponseCodecV2.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.seata.serializer.seata.protocol.v2;
+
+import io.netty.buffer.ByteBuf;
+import org.apache.seata.core.protocol.AbstractIdentifyResponse;
+import org.apache.seata.serializer.seata.protocol.AbstractResultMessageCodec;
+
+import java.nio.ByteBuffer;
+
+/**
+ *  The type Abstract identify request codec.(v2)
+ **/
+public class AbstractIdentifyResponseCodecV2 extends 
AbstractResultMessageCodec {
+    @Override
+    public Class<?> getMessageClassType() {
+        return AbstractIdentifyResponse.class;
+    }
+
+    @Override
+    public <T> void encode(T t, ByteBuf out) {
+        super.encode(t, out);
+        AbstractIdentifyResponse abstractIdentifyResponse = 
(AbstractIdentifyResponse) t;
+        boolean identified = abstractIdentifyResponse.isIdentified();
+        String version = abstractIdentifyResponse.getVersion();
+
+        out.writeByte(identified ? (byte) 1 : (byte) 0);
+        if (version != null) {
+            byte[] bs = version.getBytes(UTF8);
+            out.writeShort((short) bs.length);
+            if (bs.length > 0) {
+                out.writeBytes(bs);
+            }
+        } else {
+            out.writeShort((short) 0);
+        }
+    }
+
+    @Override
+    public <T> void decode(T t, ByteBuffer in) {
+        AbstractIdentifyResponse abstractIdentifyResponse = 
(AbstractIdentifyResponse) t;
+        super.decode(t, in);
+        abstractIdentifyResponse.setIdentified(in.get() == 1);
+        short len = in.getShort();
+        if (len <= 0) {
+            return;
+        }
+        if (in.remaining() < len) {
+            return;
+        }
+        byte[] bs = new byte[len];
+        in.get(bs);
+        abstractIdentifyResponse.setVersion(new String(bs, UTF8));
+    }
+}
diff --git 
a/core/src/main/java/org/apache/seata/core/rpc/netty/ProtocolDecoder.java 
b/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/protocol/v2/RegisterRMResponseCodecV2.java
similarity index 70%
copy from 
core/src/main/java/org/apache/seata/core/rpc/netty/ProtocolDecoder.java
copy to 
serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/protocol/v2/RegisterRMResponseCodecV2.java
index 509be29a19..a0e9ba08b9 100644
--- a/core/src/main/java/org/apache/seata/core/rpc/netty/ProtocolDecoder.java
+++ 
b/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/protocol/v2/RegisterRMResponseCodecV2.java
@@ -14,16 +14,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.seata.core.rpc.netty;
+package org.apache.seata.serializer.seata.protocol.v2;
 
-import io.netty.buffer.ByteBuf;
-import org.apache.seata.core.protocol.RpcMessage;
+import org.apache.seata.core.protocol.RegisterRMResponse;
 
 /**
- * the protocol decoder
- *
- **/
-public interface ProtocolDecoder {
+ * The type Register rm response codec.(v2)
+ */
+public class RegisterRMResponseCodecV2 extends AbstractIdentifyResponseCodecV2 
{
 
-    RpcMessage decodeFrame(ByteBuf in);
+    @Override
+    public Class<?> getMessageClassType() {
+        return RegisterRMResponse.class;
+    }
 }
diff --git 
a/core/src/main/java/org/apache/seata/core/rpc/netty/ProtocolDecoder.java 
b/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/protocol/v2/RegisterTMResponseCodecV2.java
similarity index 70%
copy from 
core/src/main/java/org/apache/seata/core/rpc/netty/ProtocolDecoder.java
copy to 
serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/protocol/v2/RegisterTMResponseCodecV2.java
index 509be29a19..9e0e4bc786 100644
--- a/core/src/main/java/org/apache/seata/core/rpc/netty/ProtocolDecoder.java
+++ 
b/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/protocol/v2/RegisterTMResponseCodecV2.java
@@ -14,16 +14,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.seata.core.rpc.netty;
+package org.apache.seata.serializer.seata.protocol.v2;
 
-import io.netty.buffer.ByteBuf;
-import org.apache.seata.core.protocol.RpcMessage;
+import org.apache.seata.core.protocol.RegisterTMResponse;
 
 /**
- * the protocol decoder
- *
- **/
-public interface ProtocolDecoder {
+ * The type Register tm response codec.(v2)
+ */
+public class RegisterTMResponseCodecV2 extends AbstractIdentifyResponseCodecV2 
{
 
-    RpcMessage decodeFrame(ByteBuf in);
+    @Override
+    public Class<?> getMessageClassType() {
+        return RegisterTMResponse.class;
+    }
 }
diff --git 
a/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/serializer/SeataSerializerV1.java
 
b/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/serializer/SeataSerializerV1.java
new file mode 100644
index 0000000000..c0c863ed73
--- /dev/null
+++ 
b/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/serializer/SeataSerializerV1.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.seata.serializer.seata.serializer;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import org.apache.seata.common.util.BufferUtils;
+import org.apache.seata.core.protocol.AbstractMessage;
+import org.apache.seata.core.protocol.ProtocolConstants;
+import org.apache.seata.core.serializer.Serializer;
+import org.apache.seata.serializer.seata.MessageCodecFactory;
+import org.apache.seata.serializer.seata.MessageSeataCodec;
+import org.apache.seata.serializer.seata.SeataSerializer;
+
+import java.nio.ByteBuffer;
+
+/**
+ * SeataSerializer of V1
+ **/
+public class SeataSerializerV1 implements Serializer {
+
+    private static volatile SeataSerializerV1 instance;
+
+    protected SeataSerializerV1() {}
+
+    public static SeataSerializerV1 getInstance() {
+        if (instance == null) {
+            synchronized (SeataSerializerV1.class) {
+                if (instance == null) {
+                    instance = new SeataSerializerV1();
+                }
+            }
+        }
+        return instance;
+    }
+
+    @Override
+    public <T> byte[] serialize(T t) {
+        if (!(t instanceof AbstractMessage)) {
+            throw new IllegalArgumentException("AbstractMessage isn't 
available.");
+        }
+        AbstractMessage abstractMessage = (AbstractMessage) t;
+        // type code
+        short typecode = abstractMessage.getTypeCode();
+        // msg codec
+        MessageSeataCodec messageCodec = 
MessageCodecFactory.getMessageCodec(typecode, protocolVersion());
+        // get empty ByteBuffer
+        ByteBuf out = Unpooled.buffer(1024);
+        // msg encode
+        messageCodec.encode(t, out);
+        byte[] body = new byte[out.readableBytes()];
+        out.readBytes(body);
+
+        ByteBuffer byteBuffer;
+
+        // typecode + body
+        byteBuffer = ByteBuffer.allocate(2 + body.length);
+        byteBuffer.putShort(typecode);
+        byteBuffer.put(body);
+
+        BufferUtils.flip(byteBuffer);
+        byte[] content = new byte[byteBuffer.limit()];
+        byteBuffer.get(content);
+        return content;
+    }
+
+    @Override
+    public <T> T deserialize(byte[] bytes) {
+        return SeataSerializer.deserializeByVersion(bytes, protocolVersion());
+    }
+
+    public byte protocolVersion() {
+        return ProtocolConstants.VERSION_1;
+    }
+}
diff --git 
a/core/src/main/java/org/apache/seata/core/protocol/RegisterRMResponse.java 
b/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/serializer/SeataSerializerV2.java
similarity index 54%
copy from 
core/src/main/java/org/apache/seata/core/protocol/RegisterRMResponse.java
copy to 
serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/serializer/SeataSerializerV2.java
index 7dbce406cd..6e0c322afc 100644
--- a/core/src/main/java/org/apache/seata/core/protocol/RegisterRMResponse.java
+++ 
b/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/serializer/SeataSerializerV2.java
@@ -14,35 +14,30 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.seata.core.protocol;
+package org.apache.seata.serializer.seata.serializer;
 
-import java.io.Serializable;
+import org.apache.seata.core.protocol.ProtocolConstants;
 
 /**
- * The type Register rm response.
- *
- */
-public class RegisterRMResponse extends AbstractIdentifyResponse implements 
Serializable {
+ * the type SeataSerializer(V2)
+ **/
+public class SeataSerializerV2 extends SeataSerializerV1 {
+    private static volatile SeataSerializerV2 instance;
 
-    /**
-     * Instantiates a new Register rm response.
-     */
-    public RegisterRMResponse() {
-        this(true);
-    }
+    protected SeataSerializerV2() {}
 
-    /**
-     * Instantiates a new Register rm response.
-     *
-     * @param result the result
-     */
-    public RegisterRMResponse(boolean result) {
-        super();
-        setIdentified(result);
+    public static SeataSerializerV2 getInstance() {
+        if (instance == null) {
+            synchronized (SeataSerializerV2.class) {
+                if (instance == null) {
+                    instance = new SeataSerializerV2();
+                }
+            }
+        }
+        return instance;
     }
 
-    @Override
-    public short getTypeCode() {
-        return MessageType.TYPE_REG_RM_RESULT;
+    public byte protocolVersion() {
+        return ProtocolConstants.VERSION_2;
     }
 }
diff --git 
a/core/src/main/java/org/apache/seata/core/protocol/RegisterRMResponse.java 
b/serializer/seata-serializer-seata/src/test/java/org/apache/seata/serializer/seata/MultiVersionCodecTest.java
similarity index 52%
copy from 
core/src/main/java/org/apache/seata/core/protocol/RegisterRMResponse.java
copy to 
serializer/seata-serializer-seata/src/test/java/org/apache/seata/serializer/seata/MultiVersionCodecTest.java
index 7dbce406cd..ae24252c4c 100644
--- a/core/src/main/java/org/apache/seata/core/protocol/RegisterRMResponse.java
+++ 
b/serializer/seata-serializer-seata/src/test/java/org/apache/seata/serializer/seata/MultiVersionCodecTest.java
@@ -14,35 +14,28 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.seata.core.protocol;
+package org.apache.seata.serializer.seata;
 
-import java.io.Serializable;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
 
 /**
- * The type Register rm response.
- *
+ * Test for MultiVersionCodec.VersionRange
  */
-public class RegisterRMResponse extends AbstractIdentifyResponse implements 
Serializable {
-
-    /**
-     * Instantiates a new Register rm response.
-     */
-    public RegisterRMResponse() {
-        this(true);
-    }
+public class MultiVersionCodecTest {
 
-    /**
-     * Instantiates a new Register rm response.
-     *
-     * @param result the result
-     */
-    public RegisterRMResponse(boolean result) {
-        super();
-        setIdentified(result);
+    @Test
+    public void testVersionRangeWithBeginAndEnd() {
+        MultiVersionCodec.VersionRange range = new 
MultiVersionCodec.VersionRange("1.0", "2.0");
+        assertThat(range.getBegin()).isEqualTo("1.0");
+        assertThat(range.getEnd()).isEqualTo("2.0");
     }
 
-    @Override
-    public short getTypeCode() {
-        return MessageType.TYPE_REG_RM_RESULT;
+    @Test
+    public void testVersionRangeWithOnlyEnd() {
+        MultiVersionCodec.VersionRange range = new 
MultiVersionCodec.VersionRange("2.0");
+        assertThat(range.getBegin()).isEqualTo("0");
+        assertThat(range.getEnd()).isEqualTo("2.0");
     }
 }
diff --git 
a/serializer/seata-serializer-seata/src/test/java/org/apache/seata/serializer/seata/protocol/v2/RegisterResponseCodecV2Test.java
 
b/serializer/seata-serializer-seata/src/test/java/org/apache/seata/serializer/seata/protocol/v2/RegisterResponseCodecV2Test.java
new file mode 100644
index 0000000000..adcb3095ac
--- /dev/null
+++ 
b/serializer/seata-serializer-seata/src/test/java/org/apache/seata/serializer/seata/protocol/v2/RegisterResponseCodecV2Test.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.seata.serializer.seata.protocol.v2;
+
+import org.apache.seata.core.protocol.ProtocolConstants;
+import org.apache.seata.core.protocol.RegisterRMResponse;
+import org.apache.seata.core.protocol.RegisterTMResponse;
+import org.apache.seata.core.protocol.ResultCode;
+import org.apache.seata.serializer.seata.SeataSerializer;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Test for RegisterTMResponseCodecV2 and RegisterRMResponseCodecV2
+ */
+public class RegisterResponseCodecV2Test {
+
+    private final SeataSerializer seataSerializer = new 
SeataSerializer(ProtocolConstants.VERSION_2);
+
+    @Test
+    public void testRegisterTMResponseCodecV2() {
+        RegisterTMResponse response = new RegisterTMResponse();
+        response.setIdentified(true);
+        response.setVersion("2.0.0");
+        response.setResultCode(ResultCode.Success);
+        response.setMsg("success");
+
+        byte[] bytes = seataSerializer.serialize(response);
+        assertThat(bytes).isNotNull();
+
+        RegisterTMResponse deserialized = seataSerializer.deserialize(bytes);
+        assertThat(deserialized.isIdentified()).isTrue();
+        assertThat(deserialized.getVersion()).isEqualTo("2.0.0");
+    }
+
+    @Test
+    public void testRegisterTMResponseCodecV2WithNullVersion() {
+        RegisterTMResponse response = new RegisterTMResponse();
+        response.setIdentified(false);
+        response.setVersion(null);
+        response.setResultCode(ResultCode.Failed);
+
+        byte[] bytes = seataSerializer.serialize(response);
+        RegisterTMResponse deserialized = seataSerializer.deserialize(bytes);
+        assertThat(deserialized.isIdentified()).isFalse();
+    }
+
+    @Test
+    public void testRegisterRMResponseCodecV2() {
+        RegisterRMResponse response = new RegisterRMResponse();
+        response.setIdentified(true);
+        response.setVersion("2.0.0");
+        response.setResultCode(ResultCode.Success);
+
+        byte[] bytes = seataSerializer.serialize(response);
+        assertThat(bytes).isNotNull();
+
+        RegisterRMResponse deserialized = seataSerializer.deserialize(bytes);
+        assertThat(deserialized.isIdentified()).isTrue();
+        assertThat(deserialized.getVersion()).isEqualTo("2.0.0");
+    }
+
+    @Test
+    public void testRegisterTMResponseCodecV2GetMessageClassType() {
+        RegisterTMResponseCodecV2 codec = new RegisterTMResponseCodecV2();
+        
assertThat(codec.getMessageClassType()).isEqualTo(RegisterTMResponse.class);
+    }
+
+    @Test
+    public void testRegisterRMResponseCodecV2GetMessageClassType() {
+        RegisterRMResponseCodecV2 codec = new RegisterRMResponseCodecV2();
+        
assertThat(codec.getMessageClassType()).isEqualTo(RegisterRMResponse.class);
+    }
+}
diff --git 
a/serializer/seata-serializer-seata/src/test/java/org/apache/seata/serializer/seata/serializer/SeataSerializerV1Test.java
 
b/serializer/seata-serializer-seata/src/test/java/org/apache/seata/serializer/seata/serializer/SeataSerializerV1Test.java
new file mode 100644
index 0000000000..ea4b7017e5
--- /dev/null
+++ 
b/serializer/seata-serializer-seata/src/test/java/org/apache/seata/serializer/seata/serializer/SeataSerializerV1Test.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.seata.serializer.seata.serializer;
+
+import org.apache.seata.core.protocol.ProtocolConstants;
+import org.apache.seata.core.protocol.RegisterTMRequest;
+import org.apache.seata.core.protocol.RegisterTMResponse;
+import org.apache.seata.core.protocol.ResultCode;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Test for SeataSerializerV1
+ */
+public class SeataSerializerV1Test {
+
+    private final SeataSerializerV1 serializer = 
SeataSerializerV1.getInstance();
+
+    @Test
+    public void testGetInstance() {
+        SeataSerializerV1 instance1 = SeataSerializerV1.getInstance();
+        SeataSerializerV1 instance2 = SeataSerializerV1.getInstance();
+        assertThat(instance1).isSameAs(instance2);
+    }
+
+    @Test
+    public void testProtocolVersion() {
+        
assertThat(serializer.protocolVersion()).isEqualTo(ProtocolConstants.VERSION_1);
+    }
+
+    @Test
+    public void testSerializeAndDeserializeRegisterTMRequest() {
+        RegisterTMRequest request = new RegisterTMRequest("testApp", 
"testGroup");
+        request.setExtraData("extra");
+
+        byte[] bytes = serializer.serialize(request);
+        assertThat(bytes).isNotNull();
+        assertThat(bytes.length).isGreaterThan(0);
+
+        RegisterTMRequest deserialized = serializer.deserialize(bytes);
+        assertThat(deserialized.getApplicationId()).isEqualTo("testApp");
+        
assertThat(deserialized.getTransactionServiceGroup()).isEqualTo("testGroup");
+    }
+
+    @Test
+    public void testSerializeAndDeserializeRegisterTMResponse() {
+        RegisterTMResponse response = new RegisterTMResponse();
+        response.setIdentified(true);
+        response.setVersion("1.0.0");
+        response.setResultCode(ResultCode.Success);
+
+        byte[] bytes = serializer.serialize(response);
+        assertThat(bytes).isNotNull();
+
+        RegisterTMResponse deserialized = serializer.deserialize(bytes);
+        assertThat(deserialized.isIdentified()).isTrue();
+        assertThat(deserialized.getVersion()).isEqualTo("1.0.0");
+    }
+
+    @Test
+    public void testSerializeNonAbstractMessageThrows() {
+        assertThrows(IllegalArgumentException.class, () -> {
+            serializer.serialize("not an AbstractMessage");
+        });
+    }
+}
diff --git 
a/core/src/main/java/org/apache/seata/core/rpc/netty/ProtocolDecoder.java 
b/test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/CodecTestCheckAuthHandler.java
similarity index 55%
copy from 
core/src/main/java/org/apache/seata/core/rpc/netty/ProtocolDecoder.java
copy to 
test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/CodecTestCheckAuthHandler.java
index 509be29a19..11e2f02184 100644
--- a/core/src/main/java/org/apache/seata/core/rpc/netty/ProtocolDecoder.java
+++ 
b/test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/CodecTestCheckAuthHandler.java
@@ -16,14 +16,23 @@
  */
 package org.apache.seata.core.rpc.netty;
 
-import io.netty.buffer.ByteBuf;
-import org.apache.seata.core.protocol.RpcMessage;
+import org.apache.seata.common.loader.LoadLevel;
+import org.apache.seata.core.protocol.RegisterTMRequest;
+import org.apache.seata.server.auth.DefaultCheckAuthHandler;
 
 /**
- * the protocol decoder
- *
+ * the type CodecTestCheckAuthHandler
  **/
-public interface ProtocolDecoder {
+@LoadLevel(name = "codecTestCheckAuthHandler", order = 101)
+public class CodecTestCheckAuthHandler extends DefaultCheckAuthHandler {
+
+    public static String CODEC_TEST_REG_ERROR = "codec_test_reg_error";
 
-    RpcMessage decodeFrame(ByteBuf in);
+    @Override
+    public boolean regTransactionManagerCheckAuth(RegisterTMRequest request) {
+        if (CODEC_TEST_REG_ERROR.equals(request.getExtraData())) {
+            return false;
+        }
+        return super.regTransactionManagerCheckAuth(request);
+    }
 }
diff --git 
a/test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/MultiProtocolDecoderTest.java
 
b/test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/MultiProtocolDecoderTest.java
new file mode 100644
index 0000000000..c1881cd37a
--- /dev/null
+++ 
b/test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/MultiProtocolDecoderTest.java
@@ -0,0 +1,148 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.seata.core.rpc.netty;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelPipeline;
+import io.netty.channel.embedded.EmbeddedChannel;
+import org.apache.seata.core.protocol.ProtocolConstants;
+import org.apache.seata.core.rpc.netty.v1.ProtocolDecoderV1;
+import org.apache.seata.core.rpc.netty.v1.ProtocolEncoderV1;
+import org.apache.seata.core.rpc.netty.v2.ProtocolEncoderV2;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Test for MultiProtocolDecoder encoder replacement functionality
+ */
+public class MultiProtocolDecoderTest {
+
+    /**
+     * Test that MultiProtocolDecoder correctly removes existing encoder when 
processing messages
+     */
+    @Test
+    public void testEncoderReplacement() throws Exception {
+        // Create an embedded channel with V2 encoder and MultiProtocolDecoder
+        EmbeddedChannel channel = new EmbeddedChannel();
+
+        // Add V2 encoder first (simulating client-side setup)
+        channel.pipeline().addLast("encoder", new ProtocolEncoderV2());
+
+        // Add MultiProtocolDecoder
+        channel.pipeline().addLast("multiDecoder", 
createMultiProtocolDecoder(ProtocolConstants.VERSION_1));
+
+        // Create a V1 message (magic + version + length + data)
+        ByteBuf testMessage = createV1TestMessage();
+
+        // Process the message through MultiProtocolDecoder
+        channel.writeInbound(testMessage);
+
+        // Verify that the original V2 encoder was removed and V1 encoder was 
added
+        ChannelPipeline pipeline = channel.pipeline();
+
+        // The MultiProtocolDecoder should have been removed after processing
+        assertNull(pipeline.get("multiDecoder"), "MultiProtocolDecoder should 
be removed after processing");
+
+        // Should not have the old V2 encoder anymore
+        assertNull(pipeline.get("encoder"), "Original V2 encoder should be 
removed");
+
+        // Should have V1 encoder and decoder
+        assertNotNull(pipeline.get(ProtocolDecoderV1.class), "Should have V1 
decoder");
+        assertNotNull(pipeline.get(ProtocolEncoderV1.class), "Should have V1 
encoder");
+
+        channel.close();
+    }
+
+    /**
+     * Test that no encoder duplication occurs
+     */
+    @Test
+    public void testNoEncoderDuplication() throws Exception {
+        EmbeddedChannel channel = new EmbeddedChannel();
+
+        // Add V1 encoder first
+        channel.pipeline().addLast("v1encoder", new ProtocolEncoderV1());
+
+        // Add MultiProtocolDecoder
+        channel.pipeline().addLast("multiDecoder", new MultiProtocolDecoder());
+
+        // Create a V1 message
+        ByteBuf testMessage = createV1TestMessage();
+
+        // Process the message
+        channel.writeInbound(testMessage);
+
+        // Count encoders in the pipeline
+        long encoderCount = channel.pipeline().toMap().values().stream()
+                .filter(handler -> handler instanceof ProtocolEncoder && 
!(handler instanceof ProtocolDecoder))
+                .count();
+
+        // Should have exactly one encoder
+        assertEquals(1, encoderCount, "Should have exactly one encoder after 
processing");
+
+        channel.close();
+    }
+
+    /**
+     * Create a test V1 message
+     */
+    private ByteBuf createV1TestMessage() {
+        ByteBuf buffer = Unpooled.buffer();
+
+        // Magic code (0xdada)
+        buffer.writeBytes(ProtocolConstants.MAGIC_CODE_BYTES);
+
+        // Version (V1)
+        buffer.writeByte(ProtocolConstants.VERSION_1);
+
+        // Full length (4 bytes) - we'll update this
+        int lengthIndex = buffer.writerIndex();
+        buffer.writeInt(0); // placeholder
+
+        // Head length (2 bytes)
+        buffer.writeShort(16);
+
+        // Message type
+        buffer.writeByte(ProtocolConstants.MSGTYPE_RESQUEST_SYNC);
+
+        // Serializer
+        buffer.writeByte(1);
+
+        // Compressor
+        buffer.writeByte(0);
+
+        // Request ID (4 bytes)
+        buffer.writeInt(1);
+
+        // Update full length
+        int fullLength = buffer.readableBytes();
+        buffer.setInt(lengthIndex, fullLength);
+
+        return buffer;
+    }
+
+    public MultiProtocolDecoder createMultiProtocolDecoder(byte 
currentVersion) {
+        return new MultiProtocolDecoder(currentVersion, new 
TestServerHandler());
+    }
+
+    public MultiProtocolDecoder createMultiProtocolDecoder(byte 
currentVersion, ChannelHandler... handlers) {
+        return new MultiProtocolDecoder(currentVersion, handlers);
+    }
+}
diff --git 
a/test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/TestClientHandler.java
 
b/test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/TestClientHandler.java
new file mode 100644
index 0000000000..5d6dd1ffdd
--- /dev/null
+++ 
b/test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/TestClientHandler.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.seata.core.rpc.netty;
+
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import org.apache.seata.core.protocol.RpcMessage;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Test client handler that captures server responses.
+ * Supports multiple request/response cycles using resetLatch().
+ */
+public class TestClientHandler extends ChannelInboundHandlerAdapter {
+
+    private final AtomicReference<Object> responseRef;
+    private volatile CountDownLatch responseLatch;
+
+    public TestClientHandler(AtomicReference<Object> responseRef, 
CountDownLatch responseLatch) {
+        this.responseRef = responseRef;
+        this.responseLatch = responseLatch;
+    }
+
+    public TestClientHandler() {
+        this.responseRef = new AtomicReference<>();
+        this.responseLatch = new CountDownLatch(1);
+    }
+
+    /**
+     * Reset the latch for a new request/response cycle
+     */
+    public void resetLatch(CountDownLatch newLatch) {
+        this.responseLatch = newLatch;
+    }
+
+    @Override
+    public void channelRead(ChannelHandlerContext ctx, Object msg) throws 
Exception {
+        // Handle RpcMessage wrapped responses
+        if (msg instanceof RpcMessage) {
+            RpcMessage rpcMessage = (RpcMessage) msg;
+            Object body = rpcMessage.getBody();
+            responseRef.set(body);
+        } else {
+            // Handle direct responses (for backward compatibility)
+            responseRef.set(msg);
+        }
+        if (responseLatch != null) {
+            responseLatch.countDown();
+        }
+    }
+
+    @Override
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) 
throws Exception {
+        cause.printStackTrace();
+        ctx.close();
+    }
+}
diff --git 
a/test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/TestServerHandler.java
 
b/test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/TestServerHandler.java
new file mode 100644
index 0000000000..bb0299078f
--- /dev/null
+++ 
b/test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/TestServerHandler.java
@@ -0,0 +1,138 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.seata.core.rpc.netty;
+
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import org.apache.seata.core.protocol.HeartbeatMessage;
+import org.apache.seata.core.protocol.ProtocolConstants;
+import org.apache.seata.core.protocol.RegisterTMRequest;
+import org.apache.seata.core.protocol.RegisterTMResponse;
+import org.apache.seata.core.protocol.RpcMessage;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
+
+/**
+ * Test server handler that simulates seata server behavior
+ */
+public class TestServerHandler extends ChannelInboundHandlerAdapter {
+
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(TestServerHandler.class);
+
+    private final AtomicReference<Object> requestRef;
+    private final Consumer<Object> responseCallback;
+
+    public TestServerHandler(AtomicReference<Object> requestRef, 
Consumer<Object> responseCallback) {
+        this.requestRef = requestRef;
+        this.responseCallback = responseCallback;
+    }
+
+    public TestServerHandler() {
+        this.requestRef = new AtomicReference<>();
+        this.responseCallback = null;
+    }
+
+    @Override
+    public void channelRead(ChannelHandlerContext ctx, Object msg) throws 
Exception {
+        requestRef.set(msg);
+
+        // Handle RpcMessage wrapped requests
+        if (msg instanceof RpcMessage) {
+            RpcMessage rpcMessage = (RpcMessage) msg;
+            Object body = rpcMessage.getBody();
+
+            if (body instanceof RegisterTMRequest) {
+                handleRegisterTMRequest(ctx, rpcMessage, (RegisterTMRequest) 
body);
+            } else if (body instanceof HeartbeatMessage) {
+                handleHeartbeatMessage(ctx, rpcMessage, (HeartbeatMessage) 
body);
+            }
+        } else if (msg instanceof RegisterTMRequest) {
+            // Handle direct requests (for backward compatibility)
+            handleDirectRegisterTMRequest(ctx, (RegisterTMRequest) msg);
+        } else if (msg instanceof HeartbeatMessage) {
+            // Handle direct heartbeat (for backward compatibility)
+            handleDirectHeartbeatMessage(ctx, (HeartbeatMessage) msg);
+        }
+    }
+
+    private void handleRegisterTMRequest(ChannelHandlerContext ctx, RpcMessage 
rpcMessage, RegisterTMRequest request) {
+        boolean identified = true;
+        String respMsg = "";
+
+        // Check for auth error flag (mimic real server auth logic)
+        if 
(CodecTestCheckAuthHandler.CODEC_TEST_REG_ERROR.equals(request.getExtraData())) 
{
+            identified = false;
+            respMsg = "Auth Failed";
+        }
+
+        RegisterTMResponse response = new RegisterTMResponse(identified);
+        response.setVersion(request.getVersion());
+        response.setMsg(respMsg);
+
+        // Wrap response in RpcMessage
+        RpcMessage responseMsg = new RpcMessage();
+        responseMsg.setId(rpcMessage.getId());
+        responseMsg.setMessageType(ProtocolConstants.MSGTYPE_RESPONSE);
+        responseMsg.setCodec(rpcMessage.getCodec());
+        responseMsg.setCompressor(rpcMessage.getCompressor());
+        responseMsg.setBody(response);
+
+        ctx.writeAndFlush(responseMsg);
+    }
+
+    private void handleHeartbeatMessage(ChannelHandlerContext ctx, RpcMessage 
rpcMessage, HeartbeatMessage heartbeat) {
+        if (heartbeat.isPing()) {
+            LOGGER.debug("Received PING, sending PONG");
+            // Respond with PONG
+            RpcMessage responseMsg = new RpcMessage();
+            responseMsg.setId(rpcMessage.getId());
+            
responseMsg.setMessageType(ProtocolConstants.MSGTYPE_HEARTBEAT_RESPONSE);
+            responseMsg.setCodec(rpcMessage.getCodec());
+            responseMsg.setCompressor(rpcMessage.getCompressor());
+            responseMsg.setBody(HeartbeatMessage.PONG);
+
+            ctx.writeAndFlush(responseMsg);
+        }
+    }
+
+    private void handleDirectRegisterTMRequest(ChannelHandlerContext ctx, 
RegisterTMRequest request) {
+        boolean identified = true;
+
+        if 
(CodecTestCheckAuthHandler.CODEC_TEST_REG_ERROR.equals(request.getExtraData())) 
{
+            identified = false;
+        }
+
+        RegisterTMResponse response = new RegisterTMResponse(identified);
+        response.setVersion(request.getVersion());
+        ctx.writeAndFlush(response);
+    }
+
+    private void handleDirectHeartbeatMessage(ChannelHandlerContext ctx, 
HeartbeatMessage heartbeat) {
+        if (heartbeat.isPing()) {
+            ctx.writeAndFlush(HeartbeatMessage.PONG);
+        }
+    }
+
+    @Override
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) 
throws Exception {
+        cause.printStackTrace();
+        ctx.close();
+    }
+}
diff --git 
a/test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/multiversion/MultiVersionCompatibilityTest.java
 
b/test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/multiversion/MultiVersionCompatibilityTest.java
new file mode 100644
index 0000000000..5c806385d4
--- /dev/null
+++ 
b/test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/multiversion/MultiVersionCompatibilityTest.java
@@ -0,0 +1,405 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.seata.core.rpc.netty.multiversion;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import io.netty.bootstrap.Bootstrap;
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.ChannelPipeline;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.codec.MessageToByteEncoder;
+import io.netty.handler.timeout.IdleStateHandler;
+import org.apache.seata.common.XID;
+import org.apache.seata.common.metadata.Instance;
+import org.apache.seata.common.metadata.Node;
+import org.apache.seata.common.thread.NamedThreadFactory;
+import org.apache.seata.common.util.NetUtil;
+import org.apache.seata.common.util.StringUtils;
+import org.apache.seata.common.util.UUIDGenerator;
+import org.apache.seata.core.protocol.HeartbeatMessage;
+import org.apache.seata.core.protocol.ProtocolConstants;
+import org.apache.seata.core.protocol.RegisterTMRequest;
+import org.apache.seata.core.protocol.RegisterTMResponse;
+import org.apache.seata.core.protocol.RpcMessage;
+import org.apache.seata.core.rpc.netty.MultiProtocolDecoderTest;
+import org.apache.seata.core.rpc.netty.NettyClientBootstrap;
+import org.apache.seata.core.rpc.netty.NettyClientConfig;
+import org.apache.seata.core.rpc.netty.NettyPoolKey;
+import org.apache.seata.core.rpc.netty.NettyServerConfig;
+import org.apache.seata.core.rpc.netty.TestClientHandler;
+import org.apache.seata.core.rpc.netty.TestServerHandler;
+import org.apache.seata.core.rpc.netty.v1.ProtocolEncoderV1;
+import org.apache.seata.mockserver.MockCoordinator;
+import org.apache.seata.mockserver.MockNettyRemotingServer;
+import org.jetbrains.annotations.NotNull;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.InetSocketAddress;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * This provides common utilities for testing multi-version protocol 
compatibility.
+ * Supports testing all 2x2 combinations:
+ * - V1 Server + V1 Client (manual construction - simulates legacy)
+ * - V1 Server + V2 Client (manual construction - simulates legacy)
+ * - V2 Server + V1 Client (MockNettyRemotingServer + manual client)
+ * - V2 Server + V2 Client (MockNettyRemotingServer + NettyClientBootstrap - 
production-like)
+ */
+public abstract class MultiVersionCompatibilityTest {
+
+    // LOG instance
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(MultiVersionCompatibilityTest.class);
+
+    // JSON ObjectMapper for pretty printing
+    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper()
+            .enable(SerializationFeature.INDENT_OUTPUT)
+            .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
+
+    /**
+     * Convert object to pretty JSON format for logging
+     */
+    public static String toPrettyJson(Object obj) {
+        if (obj == null) {
+            return "null";
+        }
+        try {
+            return OBJECT_MAPPER.writeValueAsString(obj);
+        } catch (JsonProcessingException e) {
+            LOGGER.warn("Failed to convert object to JSON, using toString(): 
{}", e.getMessage());
+            return obj.toString();
+        }
+    }
+
+    // ========== V1 Server (manual construction for legacy simulation) 
==========
+    protected EventLoopGroup bossGroup;
+    protected EventLoopGroup workerGroup;
+    protected Channel serverChannel;
+
+    // ========== V2 Server (using production MockNettyRemotingServer) 
==========
+    protected MockNettyRemotingServer mockRemotingServer;
+    protected ThreadPoolExecutor serverWorkingThreads;
+
+    // ========== V1 Client (manual construction for legacy simulation) 
==========
+    protected EventLoopGroup clientGroup;
+    protected Channel clientChannel;
+
+    // ========== V2 Client (using production NettyClientBootstrap) ==========
+    protected NettyClientBootstrap clientBootstrap;
+
+    // ========== Common ==========
+    protected TestClientHandler testClientHandler;
+    protected final AtomicReference<Object> requestRef = new 
AtomicReference<>();
+    protected final AtomicReference<Object> responseRef = new 
AtomicReference<>();
+    protected CountDownLatch responseLatch;
+
+    // Helper for creating MultiProtocolDecoder with specific version (for V1 
tests)
+    private final MultiProtocolDecoderTest decoderTestHelper = new 
MultiProtocolDecoderTest();
+
+    @BeforeEach
+    public void setUp() {
+        bossGroup = new NioEventLoopGroup(1);
+        workerGroup = new NioEventLoopGroup();
+        clientGroup = new NioEventLoopGroup();
+        requestRef.set(null);
+        responseRef.set(null);
+        responseLatch = new CountDownLatch(1);
+    }
+
+    @AfterEach
+    public void tearDown() throws InterruptedException {
+        // Shutdown client
+        if (clientChannel != null) {
+            clientChannel.close().sync();
+        }
+        if (clientBootstrap != null) {
+            clientBootstrap.shutdown();
+        }
+
+        // Shutdown V1 server
+        if (serverChannel != null) {
+            serverChannel.close().sync();
+        }
+
+        // Shutdown V2 server (MockNettyRemotingServer)
+        if (mockRemotingServer != null) {
+            mockRemotingServer.destroy();
+        }
+        if (serverWorkingThreads != null) {
+            serverWorkingThreads.shutdown();
+        }
+
+        bossGroup.shutdownGracefully().sync();
+        workerGroup.shutdownGracefully().sync();
+        clientGroup.shutdownGracefully().sync();
+    }
+
+    // ==================== V1 Server Methods (manual, for legacy simulation) 
====================
+
+    /**
+     * Start a V1 protocol server (manual construction to simulate legacy 
server)
+     */
+    protected void startV1Server(int port) throws InterruptedException {
+        startServerByVersion(ProtocolConstants.VERSION_1, port);
+    }
+
+    private void startServerByVersion(byte version, int port) throws 
InterruptedException {
+        ServerBootstrap serverBootstrap = new ServerBootstrap();
+        serverBootstrap
+                .group(bossGroup, workerGroup)
+                .channel(NioServerSocketChannel.class)
+                .childHandler(new ChannelInitializer<SocketChannel>() {
+                    @Override
+                    protected void initChannel(SocketChannel ch) throws 
Exception {
+                        ChannelPipeline pipeline = ch.pipeline();
+                        pipeline.addLast(new IdleStateHandler(0, 0, 30));
+                        pipeline.addLast(
+                                
decoderTestHelper.createMultiProtocolDecoder(version, 
createTestServerHandler()));
+                    }
+                });
+
+        ChannelFuture future = serverBootstrap.bind(port).sync();
+        serverChannel = future.channel();
+        LOGGER.info("V1 Server started on port {} (manual construction)", 
port);
+    }
+
+    // ==================== V2 Server Methods (using MockNettyRemotingServer) 
====================
+
+    /**
+     * Start a V2 protocol server using production MockNettyRemotingServer.
+     * This uses the real server bootstrap with all production handlers:
+     * - ProtocolDetectHandler -> SeataDetector -> MultiProtocolDecoder
+     * - MockRegisterProcessor (handles TM/RM registration)
+     * - MockHeartbeatProcessor (handles heartbeat)
+     */
+    protected void startV2Server(int port) {
+        serverWorkingThreads = new ThreadPoolExecutor(
+                10,
+                10,
+                500,
+                TimeUnit.SECONDS,
+                new LinkedBlockingQueue<>(2000),
+                new NamedThreadFactory("MockServerThread", 10),
+                new ThreadPoolExecutor.CallerRunsPolicy());
+
+        NettyServerConfig config = new NettyServerConfig();
+        config.setServerListenPort(port);
+        mockRemotingServer = new MockNettyRemotingServer(serverWorkingThreads, 
config);
+
+        // Initialize XID for the server
+        XID.setIpAddress(NetUtil.getLocalIp());
+        XID.setPort(port);
+        Instance.getInstance().setTransaction(new 
Node.Endpoint(XID.getIpAddress(), XID.getPort(), "netty"));
+        UUIDGenerator.init(1L);
+
+        MockCoordinator coordinator = MockCoordinator.getInstance();
+        coordinator.setRemotingServer(mockRemotingServer);
+        mockRemotingServer.setHandler(coordinator);
+        mockRemotingServer.init();
+
+        LOGGER.info("V2 Server started on port {} (using 
MockNettyRemotingServer)", port);
+    }
+
+    // ==================== V1 Client Methods (manual, for legacy simulation) 
====================
+
+    /**
+     * Connect V1 client (manual construction to simulate legacy client)
+     */
+    protected void connectV1Client(String host, int port, int connectTimeout) {
+        connectClientByVersion(new ProtocolEncoderV1(), 
ProtocolConstants.VERSION_1, host, port, connectTimeout);
+    }
+
+    private void connectClientByVersion(
+            MessageToByteEncoder encoder, byte version, String host, int port, 
int connectTimeout) {
+        testClientHandler = createTestClientHandler();
+
+        Bootstrap bootstrap = new Bootstrap();
+        bootstrap.group(clientGroup).channel(NioSocketChannel.class);
+        bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
+        bootstrap.option(ChannelOption.ALLOCATOR, ByteBufAllocator.DEFAULT);
+        bootstrap.handler(new ChannelInitializer<Channel>() {
+            @Override
+            protected void initChannel(Channel channel) throws Exception {
+                ChannelPipeline pipeline = channel.pipeline();
+                pipeline.addLast(new IdleStateHandler(0, 0, 15));
+                pipeline.addLast(encoder);
+                
pipeline.addLast(decoderTestHelper.createMultiProtocolDecoder(version, 
testClientHandler));
+            }
+        });
+
+        ChannelFuture channelFuture = bootstrap.connect(host, port);
+        channelFuture.awaitUninterruptibly(connectTimeout, 
TimeUnit.MILLISECONDS);
+        if (channelFuture.isSuccess()) {
+            clientChannel = channelFuture.channel();
+            LOGGER.info("V1 Client connected to {}:{} (manual construction)", 
host, port);
+        }
+    }
+
+    // ==================== V2 Client Methods (using NettyClientBootstrap) 
====================
+
+    /**
+     * Connect V2 client using production NettyClientBootstrap.
+     * This uses the real client bootstrap with:
+     * - ProtocolEncoderV2
+     * - MultiProtocolDecoder
+     */
+    protected void connectV2Client(String host, int port, int connectTimeout) {
+        testClientHandler = createTestClientHandler();
+
+        NettyClientConfig config = new NettyClientConfig();
+        config.setConnectTimeoutMillis(connectTimeout);
+
+        clientBootstrap = new NettyClientBootstrap(config, 
NettyPoolKey.TransactionRole.TMROLE);
+        clientBootstrap.setChannelHandlers(testClientHandler);
+        clientBootstrap.start();
+
+        clientChannel = clientBootstrap.getNewChannel(new 
InetSocketAddress(host, port));
+        LOGGER.info("V2 Client connected to {}:{} (using 
NettyClientBootstrap)", host, port);
+    }
+
+    // ==================== Request/Response Methods ====================
+
+    /**
+     * Send request through client channel
+     */
+    protected void sendRequest(Object request) {
+        if (clientChannel != null && clientChannel.isActive()) {
+            RpcMessage rpcMessage = buildRequestMessage(request);
+            clientChannel.writeAndFlush(rpcMessage);
+        }
+    }
+
+    /**
+     * Send heartbeat PING through client channel
+     */
+    protected void sendHeartbeatPing() {
+        if (clientChannel != null && clientChannel.isActive()) {
+            RpcMessage rpcMessage = new RpcMessage();
+            rpcMessage.setId(getNextMessageId());
+            
rpcMessage.setMessageType(ProtocolConstants.MSGTYPE_HEARTBEAT_REQUEST);
+            rpcMessage.setCodec(ProtocolConstants.CONFIGURED_CODEC);
+            rpcMessage.setCompressor(ProtocolConstants.CONFIGURED_COMPRESSOR);
+            rpcMessage.setBody(HeartbeatMessage.PING);
+            clientChannel.writeAndFlush(rpcMessage);
+        }
+    }
+
+    private RpcMessage buildRequestMessage(Object msg) {
+        RpcMessage rpcMessage = new RpcMessage();
+        rpcMessage.setId(getNextMessageId());
+        rpcMessage.setMessageType(ProtocolConstants.MSGTYPE_RESQUEST_SYNC);
+        rpcMessage.setCodec(ProtocolConstants.CONFIGURED_CODEC);
+        rpcMessage.setCompressor(ProtocolConstants.CONFIGURED_COMPRESSOR);
+        rpcMessage.setBody(msg);
+        return rpcMessage;
+    }
+
+    private static final AtomicInteger MESSAGE_ID_GENERATOR = new 
AtomicInteger(0);
+
+    private int getNextMessageId() {
+        return MESSAGE_ID_GENERATOR.incrementAndGet();
+    }
+
+    // ==================== Handler Factory Methods ====================
+
+    /**
+     * Create test server handler (for V1 server manual construction)
+     */
+    protected TestServerHandler createTestServerHandler() {
+        return new TestServerHandler(requestRef, null);
+    }
+
+    /**
+     * Create test client handler (needed to capture responses for 
verification)
+     */
+    protected TestClientHandler createTestClientHandler() {
+        return new TestClientHandler(responseRef, responseLatch);
+    }
+
+    /**
+     * Reset response latch for next request/response cycle
+     */
+    protected void resetResponseLatch() {
+        responseLatch = new CountDownLatch(1);
+        responseRef.set(null);
+        if (testClientHandler != null) {
+            testClientHandler.resetLatch(responseLatch);
+        }
+    }
+
+    // ==================== Test Helper Methods ====================
+
+    @NotNull
+    protected RegisterTMResponse doSendRegister(String extraData) throws 
InterruptedException {
+        RegisterTMRequest request = new RegisterTMRequest("testApp", 
"testGroup");
+        if (StringUtils.isNotBlank(extraData)) {
+            request.setExtraData(extraData);
+        }
+        LOGGER.info("Sending RegisterTMRequest:\n{}", toPrettyJson(request));
+        sendRequest(request);
+
+        boolean received = responseLatch.await(30, TimeUnit.SECONDS);
+        Assertions.assertTrue(received, "Should receive response within 
timeout");
+
+        Object response = responseRef.get();
+        Assertions.assertNotNull(response, "Should receive response from 
server");
+        LOGGER.info("Received RegisterTMResponse:\n{}", 
toPrettyJson(response));
+
+        RegisterTMResponse tmResponse = (RegisterTMResponse) response;
+        return tmResponse;
+    }
+
+    /**
+     * Send heartbeat PING and verify PONG response.
+     */
+    protected void doSendHeartbeatAndVerify() throws InterruptedException {
+        resetResponseLatch();
+
+        LOGGER.info("Sending HeartbeatMessage: PING");
+        sendHeartbeatPing();
+
+        boolean received = responseLatch.await(30, TimeUnit.SECONDS);
+        Assertions.assertTrue(received, "Should receive heartbeat response 
within timeout");
+
+        Object response = responseRef.get();
+        Assertions.assertNotNull(response, "Should receive heartbeat response 
from server");
+        LOGGER.info("Received HeartbeatMessage: {}", response);
+
+        Assertions.assertInstanceOf(HeartbeatMessage.class, response, 
"Response should be HeartbeatMessage");
+        HeartbeatMessage heartbeatResponse = (HeartbeatMessage) response;
+        Assertions.assertFalse(heartbeatResponse.isPing(), "Response should be 
PONG (isPing=false)");
+    }
+}
diff --git 
a/test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/multiversion/ServerV1ToClientV1Test.java
 
b/test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/multiversion/ServerV1ToClientV1Test.java
new file mode 100644
index 0000000000..b57e297718
--- /dev/null
+++ 
b/test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/multiversion/ServerV1ToClientV1Test.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.seata.core.rpc.netty.multiversion;
+
+import org.apache.seata.common.util.StringUtils;
+import org.apache.seata.core.protocol.RegisterTMResponse;
+import org.apache.seata.core.rpc.netty.CodecTestCheckAuthHandler;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Test V1 Server with V1 Client compatibility.
+ * Both server and client use V1 protocol (manual construction to simulate 
legacy systems).
+ * - Server: Manual V1 construction with TestServerHandler
+ * - Client: Manual V1 construction with ProtocolEncoderV1
+ */
+public class ServerV1ToClientV1Test extends MultiVersionCompatibilityTest {
+
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(ServerV1ToClientV1Test.class);
+
+    /**
+     * Test V1 client to V1 server - success scenario.
+     * Both sides use manual construction to simulate legacy V1 systems.
+     */
+    @Test
+    public void testServerV1ToClientV1Success() throws Exception {
+        // Manual V1 server construction (simulates legacy server)
+        startV1Server(8111);
+        // Manual V1 client construction (simulates legacy client)
+        connectV1Client("127.0.0.1", 8111, 3000);
+
+        // Step 1: Register TM
+        RegisterTMResponse tmResponse = doSendRegister(null);
+        Assertions.assertTrue(tmResponse.isIdentified(), "Should be 
successfully identified");
+
+        // Step 2: Send heartbeat to verify bidirectional encode/decode
+        doSendHeartbeatAndVerify();
+        LOGGER.info("Heartbeat PING/PONG verified - bidirectional 
communication works (V1 Server + V1 Client)");
+    }
+
+    /**
+     * Test V1 client to V1 server - failure scenario (auth error).
+     * Note: V1 protocol does not include error message in response.
+     */
+    @Test
+    public void testServerV1ToClientV1AuthFailure() throws Exception {
+        startV1Server(8112);
+        connectV1Client("127.0.0.1", 8112, 3000);
+
+        RegisterTMResponse tmResponse = 
doSendRegister(CodecTestCheckAuthHandler.CODEC_TEST_REG_ERROR);
+        Assertions.assertFalse(tmResponse.isIdentified(), "Should fail 
identification due to auth error");
+        Assertions.assertTrue(StringUtils.isBlank(tmResponse.getMsg()), "V1 
protocol should not include error message");
+    }
+}
diff --git 
a/test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/multiversion/ServerV1ToClientV2Test.java
 
b/test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/multiversion/ServerV1ToClientV2Test.java
new file mode 100644
index 0000000000..3a6f668bf4
--- /dev/null
+++ 
b/test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/multiversion/ServerV1ToClientV2Test.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.seata.core.rpc.netty.multiversion;
+
+import org.apache.seata.common.util.StringUtils;
+import org.apache.seata.core.protocol.RegisterTMResponse;
+import org.apache.seata.core.rpc.netty.CodecTestCheckAuthHandler;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Test V1 Server with V2 Client compatibility.
+ * - Server: Manual V1 construction with TestServerHandler (simulates legacy 
server)
+ * - Client: NettyClientBootstrap (production V2 client with 
MultiProtocolDecoder)
+ *
+ * This tests client auto-downgrade - V2 client connecting to V1 server.
+ */
+public class ServerV1ToClientV2Test extends MultiVersionCompatibilityTest {
+
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(ServerV1ToClientV2Test.class);
+
+    /**
+     * Test V2 client to V1 server - success scenario.
+     * V2 client should auto-detect V1 server response and handle it correctly.
+     */
+    @Test
+    public void testServerV1ToClientV2Success() throws Exception {
+        // Manual V1 server construction (simulates legacy server)
+        startV1Server(8121);
+        // Use production-like V2 client (NettyClientBootstrap)
+        connectV2Client("127.0.0.1", 8121, 3000);
+
+        // Step 1: Register TM
+        RegisterTMResponse tmResponse = doSendRegister(null);
+        Assertions.assertTrue(tmResponse.isIdentified(), "Should be 
successfully identified");
+
+        // Step 2: Send heartbeat to verify bidirectional encode/decode
+        doSendHeartbeatAndVerify();
+        LOGGER.info("Heartbeat PING/PONG verified - bidirectional 
communication works (V1 Server + V2 Client)");
+    }
+
+    /**
+     * Test V2 client to V1 server - failure scenario (auth error).
+     * Note: V1 server does not include error message in response.
+     */
+    @Test
+    public void testServerV1ToClientV2AuthFailure() throws Exception {
+        startV1Server(8122);
+        connectV2Client("127.0.0.1", 8122, 3000);
+
+        RegisterTMResponse tmResponse = 
doSendRegister(CodecTestCheckAuthHandler.CODEC_TEST_REG_ERROR);
+        Assertions.assertFalse(tmResponse.isIdentified(), "Should fail 
identification due to auth error");
+        Assertions.assertTrue(StringUtils.isBlank(tmResponse.getMsg()), "V1 
server should not include error message");
+    }
+}
diff --git 
a/test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/multiversion/ServerV2ToClientV1Test.java
 
b/test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/multiversion/ServerV2ToClientV1Test.java
new file mode 100644
index 0000000000..bdd8153e2c
--- /dev/null
+++ 
b/test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/multiversion/ServerV2ToClientV1Test.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.seata.core.rpc.netty.multiversion;
+
+import org.apache.seata.core.protocol.RegisterTMResponse;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Test V2 Server with V1 Client compatibility.
+ * - Server: MockNettyRemotingServer (production V2 server with 
MultiProtocolDecoder)
+ * - Client: Manual V1 construction (simulates legacy client with 
ProtocolEncoderV1)
+ *
+ * This tests server backward compatibility - V2 server handling V1 client.
+ */
+public class ServerV2ToClientV1Test extends MultiVersionCompatibilityTest {
+
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(ServerV2ToClientV1Test.class);
+
+    /**
+     * Test V1 client to V2 server - success scenario.
+     * V2 server should handle V1 client request and respond appropriately.
+     */
+    @Test
+    public void testServerV2ToClientV1Success() throws Exception {
+        // Use production-like V2 server (MockNettyRemotingServer)
+        startV2Server(8211);
+        // Use manual V1 client construction (simulates legacy client)
+        connectV1Client("127.0.0.1", 8211, 3000);
+
+        // Step 1: Register TM
+        RegisterTMResponse tmResponse = doSendRegister(null);
+        Assertions.assertTrue(tmResponse.isIdentified(), "Should be 
successfully identified");
+
+        // Step 2: Send heartbeat to verify bidirectional encode/decode
+        doSendHeartbeatAndVerify();
+        LOGGER.info("Heartbeat PING/PONG verified - bidirectional 
communication works (V2 Server + V1 Client)");
+    }
+
+    /**
+     * Test V1 client to V2 server - second success scenario.
+     * MockRegisterProcessor always returns success, so we test another 
success case.
+     */
+    @Test
+    public void testServerV2ToClientV1SuccessWithExtraData() throws Exception {
+        startV2Server(8212);
+        connectV1Client("127.0.0.1", 8212, 3000);
+
+        RegisterTMResponse tmResponse = doSendRegister("test-extra-data");
+        Assertions.assertTrue(tmResponse.isIdentified(), "Should be 
successfully identified with extra data");
+    }
+}
diff --git 
a/test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/multiversion/ServerV2ToClientV2Test.java
 
b/test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/multiversion/ServerV2ToClientV2Test.java
new file mode 100644
index 0000000000..f618ea5d48
--- /dev/null
+++ 
b/test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/multiversion/ServerV2ToClientV2Test.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.seata.core.rpc.netty.multiversion;
+
+import org.apache.seata.core.protocol.RegisterTMResponse;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Test V2 Server with V2 Client compatibility.
+ * Both server and client use V2 protocol with production-like bootstrap:
+ * - Server: MockNettyRemotingServer (uses ProtocolDetectHandler -> 
SeataDetector -> MultiProtocolDecoder)
+ * - Client: NettyClientBootstrap (uses ProtocolEncoderV2 + 
MultiProtocolDecoder)
+ *
+ * This is the most production-like test scenario.
+ */
+public class ServerV2ToClientV2Test extends MultiVersionCompatibilityTest {
+
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(ServerV2ToClientV2Test.class);
+
+    /**
+     * Test V2 client to V2 server - success scenario.
+     * Uses production MockNettyRemotingServer and NettyClientBootstrap.
+     */
+    @Test
+    public void testServerV2ToClientV2Success() throws Exception {
+        // Use production-like V2 server (MockNettyRemotingServer)
+        startV2Server(8221);
+        // Use production-like V2 client (NettyClientBootstrap)
+        connectV2Client("127.0.0.1", 8221, 3000);
+
+        // Step 1: Register TM
+        RegisterTMResponse tmResponse = doSendRegister(null);
+        Assertions.assertTrue(tmResponse.isIdentified(), "Should be 
successfully identified");
+
+        // Step 2: Send heartbeat to verify bidirectional encode/decode
+        doSendHeartbeatAndVerify();
+        LOGGER.info("Heartbeat PING/PONG verified - bidirectional 
communication works (V2 Server + V2 Client)");
+    }
+
+    /**
+     * Test V2 client to V2 server - failure scenario (auth error).
+     * Note: V2 protocol includes error message in response.
+     */
+    @Test
+    public void testServerV2ToClientV2AuthFailure() throws Exception {
+        startV2Server(8222);
+        connectV2Client("127.0.0.1", 8222, 3000);
+
+        // MockRegisterProcessor in MockNettyRemotingServer always returns 
success,
+        // so we only test the success path for V2 server.
+        // Auth failure testing is done with manual V1 server construction.
+        RegisterTMResponse tmResponse = doSendRegister(null);
+        Assertions.assertTrue(tmResponse.isIdentified(), "Should be 
successfully identified");
+    }
+}
diff --git 
a/test-suite/test-new-version/src/test/resources/META-INF/services/org.apache.seata.core.rpc.RegisterCheckAuthHandler
 
b/test-suite/test-new-version/src/test/resources/META-INF/services/org.apache.seata.core.rpc.RegisterCheckAuthHandler
new file mode 100644
index 0000000000..c5d514453e
--- /dev/null
+++ 
b/test-suite/test-new-version/src/test/resources/META-INF/services/org.apache.seata.core.rpc.RegisterCheckAuthHandler
@@ -0,0 +1,17 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+org.apache.seata.core.rpc.netty.CodecTestCheckAuthHandler
\ No newline at end of file


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to