perwendel/spark · error · java.lang.NumberFormatException
!hex
Error message
!hex ${c} What it means
TypeUtil.convertHexDigit(char) maps an ASCII hex character (0-9, a-f, A-F) to its 0-15 value; when the bit arithmetic yields a value outside 0..15 the character was not a hex digit and a NumberFormatException prefixed with '!hex ' is thrown.
Solutions
- Validate the two characters following '%' are hex digits before decoding (or catch the NumberFormatException).
- Catch NumberFormatException at the decode layer and return HTTP 400 for the malformed URI.
- Percent-encode strings properly on the client side (java.net.URLEncoder / encodeURIComponent) so only valid escapes are produced.
- Log the offending character to help identify the misbehaving client.
Example fix
// before
int hi = TypeUtil.convertHexDigit(s.charAt(i + 1));
// after
char c1 = s.charAt(i + 1);
if (!isHexDigit(c1)) throw new IllegalArgumentException("Bad % escape at " + i);
int hi = TypeUtil.convertHexDigit(c1); Defensive patterns
Strategy: validation
Validate before calling
boolean isHexDigit(char c) {
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
} Type guard
boolean isHexChar(char c) { return Character.digit(c, 16) >= 0; } Try / catch
try {
int hi = TypeUtil.convertHexDigit(s.charAt(i + 1));
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Malformed %XX escape in URI");
} Prevention
- Check both characters after '%' are hex digits before decoding.
- Encode URIs client-side with URLEncoder/encodeURIComponent.
- Return HTTP 400 for malformed escapes instead of letting the exception escape.
- Log offending characters for client debugging.
When it happens
Trigger: Passing a char that is not an ASCII hex digit, e.g. 'g', '%', or any non-ASCII character, into convertHexDigit(char) — usually from a malformed %XX escape sequence in a URL.
Common situations: Users hitting endpoints with hand-written percent-escapes like '%zz'; truncated escape sequences ('%2' followed by a letter); probes with random bytes in paths.
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 perwendel/spark@1973e402f5 (2026-09-10).
Data as JSON: /api/errors/047d215c76a6c6b2.
Report an issue: GitHub.
Appendix: source
Thrown at src/main/java/spark/utils/urldecoding/TypeUtil.java:209
c = '0' + bi % base;
if (c > '9') {
c = 'a' + (c - '0' - 10);
}
buf.append((char) c);
}
return buf.toString();
}
/* ------------------------------------------------------------ */
/**
* @param c An ASCII encoded character 0-9 a-f A-F
* @return The byte value of the character 0-16.
*/
public static int convertHexDigit(char c) {
int d = ((c & 0x1f) + ((c >> 6) * 0x19) - 0x10);
if (d < 0 || d > 15) {
throw new NumberFormatException("!hex " + c);
}
return d;
}
/* ------------------------------------------------------------ */
/**
* @param c An ASCII encoded character 0-9 a-f A-F
* @return The byte value of the character 0-16.
*/
public static int convertHexDigit(int c) {
int d = ((c & 0x1f) + ((c >> 6) * 0x19) - 0x10);
if (d < 0 || d > 15) {
throw new NumberFormatException("!hex " + c);
}
return d;
}
View on GitHub (pinned to 1973e402f5)