apache/incubator-seata · error · FrameworkException

register TM failed. client version

Error message

register TM failed. client version: %s,server version: %s, errorMsg: %s, channel: %s

What it means

Thrown from TmNettyRemotingClient.onRegisterMsgFail: the Seata Server returned a failure RegisterTMResponse for the transaction-manager registration request. The message captures client/server versions, the server's errorMsg, and the channel, wrapped in FrameworkException. The server explicitly refused the TM registration rather than the channel failing.

Solutions

  1. Compare the txServiceGroup string on the client against the server's seata.service.vgroupMapping keys (exact match, no typo, same case).
  2. Add/fix the mapping on the server (service.vgroupMapping.my_tx_group=default) and ensure the 'default' cluster is in the registry.
  3. Align client and server Seata versions if the errorMsg hints at version rejection.
  4. Confirm registry namespace/cluster/group on the client matches the server's registration so the TM reaches a server that owns the group.

Example fix

# before
# client: TMClient.init("app", "order_tx_group")
# server application.yml:
seata:
  service:
    vgroupMapping:
      order-group: "default"   # name mismatch -> register TM failed

# after
seata:
  service:
    vgroupMapping:
      order_tx_group: "default"
Defensive patterns

Strategy: validation

Validate before calling

// fail fast before TMClient.init if the transaction group resolves to no server
String group = "my_tx_group";
List<String> servers = io.seata.discovery.registry.RegistryFactory.getInstance().lookup(group);
if (servers == null || servers.isEmpty()) {
    throw new IllegalStateException("txServiceGroup '" + group + "' unknown - add it to server service.vgroupMapping");
}
TMClient.init(applicationId, group);

Try / catch

try {
    TMClient.init(applicationId, txServiceGroup);
} catch (io.seata.common.exception.FrameworkException e) {
    if (e.getMessage() != null && e.getMessage().contains("register TM failed")) {
        // server rejected the TM: surface as config error, include server errorMsg from the message
        throw new IllegalStateException("TM registration rejected: " + e.getMessage(), e);
    }
    throw e;
}

Prevention

When it happens

Trigger: TMClient.init(applicationId, transactionServiceGroup) connects and sends RegisterTMRequest; the server responds with a non-success RegisterTMResponse — typically because the transaction service group is unknown to that server or a version/compatibility check failed.

Common situations: Client's txServiceGroup is not configured in server's service.vgroupMapping; client pointed at wrong registry namespace/cluster and reached a server that does not host the group; io.seata (1.x) client talking to an org.apache.seata (2.x) server; typo in the group name on either side.

Related errors


AI-assisted analysis of apache/incubator-seata@e01f97c6db (2026-08-14). Data as JSON: /api/errors/654af18a29f26464. Report an issue: GitHub.

Appendix: source

Thrown at core/src/main/java/org/apache/seata/core/rpc/netty/TmNettyRemotingClient.java:261

        if (LOGGER.isInfoEnabled()) {
            LOGGER.info(
                    "register TM success. client version:{}, server version:{},channel:{}",
                    registerTMRequest.getVersion(),
                    registerTMResponse.getVersion(),
                    channel);
        }
        getClientChannelManager().registerChannel(serverAddress, channel, registerTMRequest.getVersion());
    }

    @Override
    public void onRegisterMsgFail(
            String serverAddress, Channel channel, Object response, AbstractMessage requestMessage) {
        RegisterTMRequest registerTMRequest = (RegisterTMRequest) requestMessage;
        RegisterTMResponse registerTMResponse = (RegisterTMResponse) response;
        String errMsg = String.format(
                "register TM failed. client version: %s,server version: %s, errorMsg: %s, " + "channel: %s",
                registerTMRequest.getVersion(), registerTMResponse.getVersion(), registerTMResponse.getMsg(), channel);
        throw new FrameworkException(errMsg);
    }

    @Override
    public void destroy() {
        super.destroy();
        initialized.getAndSet(false);
        instance = null;
    }

    @Override
    protected Function<String, NettyPoolKey> getPoolKeyFunction() {
        return severAddress -> {
            RegisterTMRequest message = new RegisterTMRequest(applicationId, transactionServiceGroup, getExtraData());
            return new NettyPoolKey(NettyPoolKey.TransactionRole.TMROLE, severAddress, message);
        };
    }

    private void registerProcessor() {

View on GitHub (pinned to e01f97c6db)