apache/incubator-seata · error · IllegalArgumentException

Unknown TransactionExceptionCode[{ordinal}]

Error message

Unknown TransactionExceptionCode[{ordinal}]

What it means

TransactionExceptionCode.get(int) maps a numeric code from the wire protocol back to the TransactionExceptionCode enum (e.g. BeginFailed, LockKeyConflict, IoErro). It throws when the ordinal is outside the enum range — either negative or >= values().length — i.e. the integer in the message header cannot be a valid exception code on this build.

Source

Thrown at core/src/main/java/org/apache/seata/core/exception/TransactionExceptionCode.java:170

     * @param ordinal the ordinal
     * @return the transaction exception code
     */
    public static TransactionExceptionCode get(byte ordinal) {
        return get((int) ordinal);
    }

    /**
     * Get transaction exception code.
     *
     * @param ordinal the ordinal
     * @return the transaction exception code
     */
    public static TransactionExceptionCode get(int ordinal) {
        TransactionExceptionCode value = null;
        try {
            value = TransactionExceptionCode.values()[ordinal];
        } catch (Exception e) {
            throw new IllegalArgumentException("Unknown TransactionExceptionCode[" + ordinal + "]");
        }
        return value;
    }
}

View on GitHub (pinned to e01f97c6db)

Solutions

  1. Pin client and server to the same (or compatible) Seata version; upgrade the older side first during rolling upgrades.
  2. Verify only Seata nodes connect to the TC port; keep binary RPC traffic off HTTP proxies.
  3. Capture the frame (packet dump / debug logging of RpcMessage) to confirm whether the code field is genuinely newer or the payload is corrupt.

Example fix

# before
client seata 1.5 <-> server seata 2.x (new codes)
# after
client upgraded to match server major.minor
Defensive patterns

Strategy: type-guard

Validate before calling

static boolean validTxExceptionCode(int ordinal) {
    return ordinal >= 0 && ordinal < TransactionExceptionCode.values().length;
}

Type guard

static Optional<TransactionExceptionCode> safeGet(int o) {
    return (o >= 0 && o < TransactionExceptionCode.values().length)
        ? Optional.of(TransactionExceptionCode.values()[o]) : Optional.empty();
}

Try / catch

catch (IllegalArgumentException e) {
    if (e.getMessage().startsWith("Unknown TransactionExceptionCode")) {
        LOG.warn("peer may run newer seata; code={}", ordinal);
        // degrade: treat as generic TransactionException instead of killing the channel
    }
}

Prevention

When it happens

Trigger: Decoding a TransactionExceptionCode from an RPC message sent by a peer with a different (usually newer) enum that has more codes, or from a corrupted/garbage frame where random bytes land in the code field.

Common situations: Version skew during rolling upgrades (newer TC sends a code the old client enum lacks), non-Seata traffic hitting the TC service port, or a proxy/load balancer corrupting the binary protocol.

Related errors


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