spring-projects/spring-security · error · IllegalArgumentException

Hex-encoded string must have an even number of characters

Error message

Hex-encoded string must have an even number of characters

What it means

Hex.decode converts a hexadecimal CharSequence into bytes and requires each byte to be represented by exactly two hex characters. An odd-length string cannot be split into byte pairs, so the library throws IllegalArgumentException immediately instead of guessing a trailing nibble.

Source

Thrown at crypto/src/main/java/org/springframework/security/crypto/codec/Hex.java:51

	}

	public static char[] encode(byte[] bytes) {
		final int nBytes = bytes.length;
		char[] result = new char[2 * nBytes];
		int j = 0;
		for (byte aByte : bytes) {
			// Char for top 4 bits
			result[j++] = HEX[(0xF0 & aByte) >>> 4];
			// Bottom 4
			result[j++] = HEX[(0x0F & aByte)];
		}
		return result;
	}

	public static byte[] decode(CharSequence s) {
		int nChars = s.length();
		if (nChars % 2 != 0) {
			throw new IllegalArgumentException("Hex-encoded string must have an even number of characters");
		}
		byte[] result = new byte[nChars / 2];
		for (int i = 0; i < nChars; i += 2) {
			int msb = Character.digit(s.charAt(i), 16);
			int lsb = Character.digit(s.charAt(i + 1), 16);
			if (msb < 0 || lsb < 0) {
				throw new IllegalArgumentException(
						"Detected a Non-hex character at " + (i + 1) + " or " + (i + 2) + " position");
			}
			result[i / 2] = (byte) ((msb << 4) | lsb);
		}
		return result;
	}

}

View on GitHub (pinned to 96852e8860)

Solutions

  1. Fix the source so the hex string has an even number of characters.
  2. Check s.length() % 2 == 0 before calling and handle the odd case explicitly.
  3. If the odd string is a lone high nibble, normalize it by prepending '0' (only when the data semantics allow).

Example fix

// before
byte[] bytes = Hex.decode(hexStr); // "abc" -> throws
// after
if (hexStr.length() % 2 != 0) hexStr = "0" + hexStr;
byte[] bytes = Hex.decode(hexStr);
Defensive patterns

Strategy: validation

Validate before calling

if (s == null || s.length() % 2 != 0 || !s.chars().allMatch(c -> Character.digit(c, 16) >= 0)) throw new IllegalArgumentException("invalid hex input");

Try / catch

try { bytes = Hex.decode(s); } catch (IllegalArgumentException e) { /* log and reject input */ }

Prevention

When it happens

Trigger: Calling Hex.decode with a string of odd length, e.g. Hex.decode("abc") or Hex.decode("f"), often from truncated Base16 data or manually built hex strings.

Common situations: Salt or key material pasted from logs missing a character; string concatenation dropping a char; decoding output of a broken encoder; hand-trimmed hashes.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10). Data as JSON: /api/errors/7fb434eacf94987c. Report an issue: GitHub.