{"record":{"id":"3b6478d3e47d4ccd","repo":"spring-projects/spring-security","slug":"detected-a-non-hex-character-at-n-or-n-1-position","errorCode":null,"errorMessage":"Detected a Non-hex character at N or N+1 position","messagePattern":"Detected a Non-hex character at N or N\\+1 position","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"crypto/src/main/java/org/springframework/security/crypto/codec/Hex.java","lineNumber":58,"sourceCode":"\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":40,"sourceCodeEnd":67,"githubUrl":"https://github.com/spring-projects/spring-security/blob/96852e8860138a482cb13d1479573f24ff6443c6/crypto/src/main/java/org/springframework/security/crypto/codec/Hex.java#L40-L67","documentation":"Hex.decode validates each character pair with Character.digit(c, 16). If either character of a pair is not a hex digit (0-9, a-f, A-F), the library throws IllegalArgumentException identifying the 1-based positions (i+1 or i+2) where the bad character may be. Note both positions of the offending pair are reported since it doesn't say which one failed.","triggerScenarios":"Calling Hex.decode(\"zz12\") or any string containing 'g'-'z', punctuation, whitespace (e.g. \"ab cd\"), or unicode characters — even length but non-hex content.","commonSituations":"Hashes copied from logs with embedded spaces or line breaks; uppercase prefix like \"0X\"; Base64 data mistakenly treated as hex; locale-dependent case mangling.","solutions":["Inspect the reported positions and correct the characters to valid hex digits.","Sanitize input first: strip whitespace and validate with hexStr.matches(\"[0-9a-fA-F]+\") before decoding.","Fix the producer to emit proper hex (e.g. String.format(\"%02x\", b)) instead of Base64 or raw chars."],"exampleFix":"// before\nbyte[] key = Hex.decode(raw.trim()); // may contain 'g' or spaces\n// after\nString clean = raw.replaceAll(\"\\\\s\", \"\");\nif (!clean.matches(\"[0-9a-fA-F]+\")) throw new IllegalArgumentException(\"not hex: \" + clean);\nbyte[] key = Hex.decode(clean);","handlingStrategy":"validation","validationCode":"boolean isHex(CharSequence s) { return s != null && s.length() % 2 == 0 && s.chars().allMatch(c -> Character.digit(c, 16) >= 0); }","typeGuard":null,"tryCatchPattern":"try { bytes = Hex.decode(s); } catch (IllegalArgumentException e) { throw new IllegalArgumentException(\"Bad hex at/near: \" + e.getMessage(), e); }","preventionTips":["Run a strict hex regex ([0-9a-fA-F]+) over input before decode.","Reject or trim input containing whitespace, 0x prefixes, or punctuation.","Generate hex with a canonical encoder so round-trips are guaranteed valid."],"tags":["hex","encoding","spring-security","invalid-characters"],"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"}