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

  1. Validate the two characters following '%' are hex digits before decoding (or catch the NumberFormatException).
  2. Catch NumberFormatException at the decode layer and return HTTP 400 for the malformed URI.
  3. Percent-encode strings properly on the client side (java.net.URLEncoder / encodeURIComponent) so only valid escapes are produced.
  4. 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

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)