{"record":{"id":"a68fcfbe76431dd3","repo":"quarkusio/quarkus","slug":"unterminated-escape-sequence-at-end-of-string-s","errorCode":null,"errorMessage":"unterminated escape sequence at end of string: ${s}","messagePattern":"unterminated escape sequence at end of string: (.+?)","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/util/URIDecoder.java","lineNumber":67,"sourceCode":"        boolean modified = false;\n        int i;\n        for (i = 0; i < size; i++) {\n            final char c = s.charAt(i);\n            if (c == '%' || (plus && c == '+')) {\n                modified = true;\n                break;\n            }\n        }\n        if (!modified) {\n            return s;\n        }\n        final byte[] buf = s.getBytes(StandardCharsets.UTF_8);\n        int pos = i; // position in `buf'.\n        for (; i < size; i++) {\n            char c = s.charAt(i);\n            if (c == '%') {\n                if (i == size - 1) {\n                    throw new IllegalArgumentException(\"unterminated escape\"\n                            + \" sequence at end of string: \" + s);\n                }\n                c = s.charAt(++i);\n                if (c == '%') {\n                    buf[pos++] = '%'; // \"%%\" -> \"%\"\n                    break;\n                }\n                if (i >= size - 1) {\n                    throw new IllegalArgumentException(\"partial escape\"\n                            + \" sequence at end of string: \" + s);\n                }\n                c = decodeHexNibble(c);\n                final char c2 = decodeHexNibble(s.charAt(++i));\n                if (c == Character.MAX_VALUE || c2 == Character.MAX_VALUE) {\n                    throw new IllegalArgumentException(\n                            \"invalid escape sequence `%\" + s.charAt(i - 1)\n                                    + s.charAt(i) + \"' at index \" + (i - 2)\n                                    + \" of: \" + s);","sourceCodeStart":49,"sourceCodeEnd":85,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/util/URIDecoder.java#L49-L85","documentation":"URIDecoder.decodeURIComponent() percent-decodes a URI component. A '%' must be followed by two hex digits ('%XX'); if the string ends immediately after a '%', there is no escape sequence to decode, so this IllegalArgumentException is thrown. It is a strict-input guard: the decoder refuses to silently drop a truncated escape.","triggerScenarios":"Calling decodeURIComponent (or the delegating decode overload) with a string whose last character is '%', e.g. \"100%\", \"/path%\", or a URL that was itself truncated or badly encoded.","commonSituations":"Decoding user-supplied query/path fragments that contain literal percent signs; truncation when storing/pasting URLs; double-encoding mistakes where '%' ended up unescaped in the input.","solutions":["Encode literal percent signs as %25 before decoding","Validate the input matches /^([^%]|%[0-9A-Fa-f]{2})*$/ before decoding","Trim or repair the truncated escape character from the input string","Catch IllegalArgumentException and return the input or a 400 response to the client"],"exampleFix":"// before\nString v = URIDecoder.decodeURIComponent(\"discount=100%\");\n// after\nString v = URIDecoder.decodeURIComponent(\"discount=100%25\");","handlingStrategy":"validation","validationCode":"private static final Pattern SAFE = Pattern.compile(\"^([^%]|%[0-9A-Fa-f]{2})*$\");\nstatic boolean isDecodable(String s) { return s != null && SAFE.matcher(s).matches(); }\n// call: if (!isDecoded(input)) reject 400;","typeGuard":"static boolean endsWithCompleteEscape(String s) {\n    return s != null && !s.endsWith(\"%\");\n}","tryCatchPattern":"try {\n    decoded = URIDecoder.decodeURIComponent(raw);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"unterminated escape\")) {\n    throw new BadRequestException(\"Malformed URI component: \" + raw);\n    } else throw e;\n}","preventionTips":["Always percent-encode literal '%' as %25 before decoding","Never decode strings you did not encode or receive from a trusted encoder","Validate user input with a percent-encoding regex before decoding","Sanitize/truncate points: check that slicing operations never cut mid-escape"],"tags":["uri","percent-encoding","decoding"],"backgroundTag":"malformed-percent-encoding","analyzedSha":"e1c734241f34c7919086ceb4c9262b4a58f6de44","analyzedAt":"2026-09-05T17:01:29.979Z","contentChangedAt":"2026-09-05T17:01:29.979Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}