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
- 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)
- Avoid passing hex through numeric types (BigInteger.toString() drops leading zeros) — keep it as a String end-to-end
- 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
- Never route hex through numeric types that strip leading zeros (BigInteger.toString)
- Validate length parity before decoding and left-pad with '0'
- Use fixed-length hex rendering (String.format("%02x", b)) when producing digests
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.