{"record":{"id":"7fb434eacf94987c","repo":"spring-projects/spring-security","slug":"hex-encoded-string-must-have-an-even-number-of-cha","errorCode":null,"errorMessage":"Hex-encoded string must have an even number of characters","messagePattern":"Hex-encoded string must have an even number of characters","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"crypto/src/main/java/org/springframework/security/crypto/codec/Hex.java","lineNumber":51,"sourceCode":"\t}\n\n\tpublic static char[] encode(byte[] bytes) {\n\t\tfinal int nBytes = bytes.length;\n\t\tchar[] result = new char[2 * nBytes];\n\t\tint j = 0;\n\t\tfor (byte aByte : bytes) {\n\t\t\t// Char for top 4 bits\n\t\t\tresult[j++] = HEX[(0xF0 & aByte) >>> 4];\n\t\t\t// Bottom 4\n\t\t\tresult[j++] = HEX[(0x0F & aByte)];\n\t\t}\n\t\treturn result;\n\t}\n\n\tpublic static byte[] decode(CharSequence s) {\n\t\tint nChars = s.length();\n\t\tif (nChars % 2 != 0) {\n\t\t\tthrow new IllegalArgumentException(\"Hex-encoded string must have an even number of characters\");\n\t\t}\n\t\tbyte[] result = new byte[nChars / 2];\n\t\tfor (int i = 0; i < nChars; i += 2) {\n\t\t\tint msb = Character.digit(s.charAt(i), 16);\n\t\t\tint lsb = Character.digit(s.charAt(i + 1), 16);\n\t\t\tif (msb < 0 || lsb < 0) {\n\t\t\t\tthrow new IllegalArgumentException(\n\t\t\t\t\t\t\"Detected a Non-hex character at \" + (i + 1) + \" or \" + (i + 2) + \" position\");\n\t\t\t}\n\t\t\tresult[i / 2] = (byte) ((msb << 4) | lsb);\n\t\t}\n\t\treturn result;\n\t}\n\n}\n","sourceCodeStart":33,"sourceCodeEnd":67,"githubUrl":"https://github.com/spring-projects/spring-security/blob/96852e8860138a482cb13d1479573f24ff6443c6/crypto/src/main/java/org/springframework/security/crypto/codec/Hex.java#L33-L67","documentation":"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.","triggerScenarios":"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.","commonSituations":"Salt or key material pasted from logs missing a character; string concatenation dropping a char; decoding output of a broken encoder; hand-trimmed hashes.","solutions":["Fix the source so the hex string has an even number of characters.","Check s.length() % 2 == 0 before calling and handle the odd case explicitly.","If the odd string is a lone high nibble, normalize it by prepending '0' (only when the data semantics allow)."],"exampleFix":"// before\nbyte[] bytes = Hex.decode(hexStr); // \"abc\" -> throws\n// after\nif (hexStr.length() % 2 != 0) hexStr = \"0\" + hexStr;\nbyte[] bytes = Hex.decode(hexStr);","handlingStrategy":"validation","validationCode":"if (s == null || s.length() % 2 != 0 || !s.chars().allMatch(c -> Character.digit(c, 16) >= 0)) throw new IllegalArgumentException(\"invalid hex input\");","typeGuard":null,"tryCatchPattern":"try { bytes = Hex.decode(s); } catch (IllegalArgumentException e) { /* log and reject input */ }","preventionTips":["Validate hex strings at the input boundary with a [0-9a-fA-F]+ regex and even-length check.","Strip whitespace/newlines introduced by logs or editors before decoding.","Keep hex encoding and decoding in one shared utility to avoid asymmetries."],"tags":["hex","encoding","spring-security","invalid-length"],"backgroundTag":"invalid-argument-format","analyzedSha":"96852e8860138a482cb13d1479573f24ff6443c6","analyzedAt":"2026-09-10T23:25:23.477Z","contentChangedAt":"2026-09-10T23:25:23.477Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}