dromara/Sa-Token · error · IllegalArgumentException

Hex string must have even length

Error message

Hex string must have even length

What it means

SaHexUtil.hexToBytes converts a hexadecimal string to a byte array by consuming two hex chars per byte. An odd-length input cannot be split into byte pairs, so it fails fast with IllegalArgumentException('Hex string must have even length') before any parsing. This is a pure input-validation error on the caller's data.

Source

Thrown at sa-token-core/src/main/java/cn/dev33/satoken/util/SaHexUtil.java:55

        for (int i = 0; i < bytes.length; i++) {
            int v = bytes[i] & 0xFF;
            hexChars[i * 2] = HEX_ARRAY[v >>> 4];
            hexChars[i * 2 + 1] = HEX_ARRAY[v & 0x0F];
        }
        return new String(hexChars);
    }

    /**
     * 将十六进制字符串转换为字节数组(JDK8兼容)
     * @param hexString 有效的十六进制字符串(不区分大小写)
     * @return 对应的字节数组
     * @throws IllegalArgumentException 输入字符串格式错误时抛出异常
     */
    public static byte[] hexToBytes(String hexString) {
        if (hexString == null) return null;
        int len = hexString.length();
        if (len % 2 != 0) {
            throw new IllegalArgumentException("Hex string must have even length");
        }

        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            int high = Character.digit(hexString.charAt(i), 16);
            int low = Character.digit(hexString.charAt(i+1), 16);

            if (high == -1 || low == -1) {
                throw new IllegalArgumentException(
                        "Invalid hex character at position " + i + " or " + (i+1)
                );
            }

            data[i/2] = (byte) ((high << 4) + low);
        }
        return data;
    }

View on GitHub (pinned to ac2c7f6e94)

Solutions

  1. Check str.length() % 2 == 0 before calling, and pad with a leading '0' when needed (String.format("%0"+ (len+1) +"d"...) style fix or '0'+hex)
  2. Avoid passing hex through numeric types (BigInteger.toString() drops leading zeros) — keep it as a String end-to-end
  3. Log the offending string length to find the truncation bug upstream

Example fix

// before
byte[] b = SaHexUtil.hexToBytes(hex); // hex = "abc" -> throws

// after
if (hex.length() % 2 != 0) hex = "0" + hex;
byte[] b = SaHexUtil.hexToBytes(hex);
Defensive patterns

Strategy: validation

Validate before calling

public static boolean isEvenLengthHex(String s) {
    return s != null && s.length() % 2 == 0
        && s.matches("[0-9a-fA-F]*");
}
// if only length is the concern:
if (hex.length() % 2 != 0) hex = "0" + hex;

Type guard

public static boolean isValidHexString(String s) {
    return s != null && s.length() % 2 == 0 && s.matches("(?:[0-9a-fA-F]{2})+");
}

Try / catch

try {
    byte[] b = SaHexUtil.hexToBytes(hex);
} catch (IllegalArgumentException e) {
    // input bug upstream: log hex.length() and reject the value
}

Prevention

When it happens

Trigger: Calling SaHexUtil.hexToBytes("abc") (3 chars), "0" , or any hex string with odd length; commonly the result of truncating a digest by hand, off-by-one slicing, or losing a character during copy/paste.

Common situations: Copying MD5/SHA hex digests and dropping the last char; substring-based truncation logic (e.g. token.substring(0, 31)); concatenating hex fragments where one piece had a leading zero stripped by a numeric conversion.

Related errors


AI-assisted analysis of dromara/Sa-Token@ac2c7f6e94 (2026-08-14). Data as JSON: /api/errors/9bf6d5beeb4fcb26. Report an issue: GitHub.