quarkusio/quarkus · error · IllegalArgumentException

partial escape sequence at end of string: ${s}

Error message

partial escape sequence at end of string: ${s}

What it means

URIDecoder.decodeURIComponent() requires two hex digits after '%'. When the first character after '%' is not '%' but there are fewer than two remaining characters (i >= size - 1), the escape sequence is incomplete and this IllegalArgumentException is thrown. It prevents decoding a half-specified byte value.

Source

Thrown at independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/util/URIDecoder.java:76

        if (!modified) {
            return s;
        }
        final byte[] buf = s.getBytes(StandardCharsets.UTF_8);
        int pos = i; // position in `buf'.
        for (; i < size; i++) {
            char c = s.charAt(i);
            if (c == '%') {
                if (i == size - 1) {
                    throw new IllegalArgumentException("unterminated escape"
                            + " sequence at end of string: " + s);
                }
                c = s.charAt(++i);
                if (c == '%') {
                    buf[pos++] = '%'; // "%%" -> "%"
                    break;
                }
                if (i >= size - 1) {
                    throw new IllegalArgumentException("partial escape"
                            + " sequence at end of string: " + s);
                }
                c = decodeHexNibble(c);
                final char c2 = decodeHexNibble(s.charAt(++i));
                if (c == Character.MAX_VALUE || c2 == Character.MAX_VALUE) {
                    throw new IllegalArgumentException(
                            "invalid escape sequence `%" + s.charAt(i - 1)
                                    + s.charAt(i) + "' at index " + (i - 2)
                                    + " of: " + s);
                }
                c = (char) (c * 16 + c2);
                // shouldn't check for plus since it would be a double decoding
                buf[pos++] = (byte) c;
            } else {
                buf[pos++] = (byte) (plus && c == '+' ? ' ' : c);
            }
        }
        return new String(buf, 0, pos, StandardCharsets.UTF_8);

View on GitHub (pinned to e1c734241f)

Solutions

  1. Provide both hex digits for every escape (e.g. %2F not %2)
  2. Validate input with a regex like /^([^%]|%[0-9A-Fa-f]{2})*$/ before decoding
  3. Re-encode the source string properly (encodeURIComponent / UriBuilder) instead of hand-assembling
  4. Catch IllegalArgumentException and reject the request with 400 Bad Request

Example fix

// before
URIDecoder.decodeURIComponent("/files/a%2");
// after
URIDecoder.decodeURIComponent("/files/a%2F");
Defensive patterns

Strategy: validation

Validate before calling

private static final Pattern PCT = Pattern.compile("^([^%]|%[0-9A-Fa-f]{2})*$");
static void requireValidPctEncoding(String s) {
    if (!PCT.matcher(s).matches()) throw new IllegalArgumentException("bad percent-encoding: " + s);
}

Type guard

static boolean hasCompleteTrailingEscape(String s) {
    if (s == null || !s.contains("%")) return true;
    int i = s.lastIndexOf('%');
    return s.length() - i > 2 && isHex(s.charAt(i + 1)) && isHex(s.charAt(i + 2));
}

Try / catch

try {
    decoded = URIDecoder.decodeURIComponent(raw);
} catch (IllegalArgumentException e) {
    if (e.getMessage() != null && e.getMessage().contains("partial escape")) {
    throw new BadRequestException("Incomplete percent-escape in: " + raw);
    } else throw e;
}

Prevention

When it happens

Trigger: Calling decodeURIComponent with input ending in '%X' (one hex digit), e.g. "/a%C" or "value=%2" — the percent escape is cut short at the end of the string.

Common situations: URLs truncated by length limits or copy/paste; hand-built query strings where a two-digit hex code was typed with only one digit; log lines or substrings cut mid-escape.

Related errors


AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05). Data as JSON: /api/errors/dab9f51a19d3467a. Report an issue: GitHub.