dromara/Sa-Token · error · IllegalArgumentException

Invalid hex character at position ${i} or ${i+1}

Error message

Invalid hex character at position ${i} or ${i+1}

What it means

The second validation inside SaHexUtil.hexToBytes: after the even-length check passes, each character pair is converted with Character.digit(c, 16); a return of -1 means the char is not a hex digit, and the method throws IllegalArgumentException naming the offending position(s). Any non-[0-9a-fA-F] character — whitespace, '0x' prefix, 'g'-'z', punctuation — triggers it.

Source

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

     * 将十六进制字符串转换为字节数组(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. Strip non-hex characters before decoding: hex = hex.replaceAll("[^0-9a-fA-F]", "")
  2. Remove '0x' prefixes and trim whitespace/newlines at the source
  3. Validate first with a regex check: hex.matches("([0-9a-fA-F]{2})*")

Example fix

// before
SaHexUtil.hexToBytes(raw); // raw = "0x1a2b" -> throws

// after
String hex = raw.startsWith("0x") ? raw.substring(2) : raw;
SaHexUtil.hexToBytes(hex.trim());
Defensive patterns

Strategy: type-guard

Validate before calling

String cleaned = hex.trim();
if (cleaned.startsWith("0x") || cleaned.startsWith("0X")) {
    cleaned = cleaned.substring(2);
}
cleaned = cleaned.replaceAll("[^0-9a-fA-F]", "");

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) {
    // e.getMessage() names the offending position; fix the producer of the string
}

Prevention

When it happens

Trigger: hexToBytes("0x1A2B") (contains 'x'), hexToBytes("1a 2b") (space), hexToBytes("hello") — any string with even length but non-hex characters.

Common situations: Digests copied with a '0x' prefix or embedded whitespace/newline (terminal line-wrap); hex read from a file that includes a trailing '\n'; case/charset confusion after passing through a non-hex-safe transformation.

Related errors


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