{"record":{"id":"4b8fbb040a5f7d50","repo":"eclipse-vertx/vert.x","slug":"invalid-position-for-escape-character-start","errorCode":null,"errorMessage":"Invalid position for escape character: ${start}","messagePattern":"Invalid position for escape character: (.+?)","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"vertx-core/src/main/java/io/vertx/core/internal/net/RFC3986.java","lineNumber":321,"sourceCode":"        // ALPHA\n        (unescaped >= 0x41 && unescaped <= 0x5A) ||\n          (unescaped >= 0x61 && unescaped <= 0x7A) ||\n          // DIGIT\n          (unescaped >= 0x30 && unescaped <= 0x39) ||\n          // HYPHEN\n          (unescaped == 0x2D) ||\n          // PERIOD\n          (unescaped == 0x2E) ||\n          // UNDERSCORE\n          (unescaped == 0x5F) ||\n          // TILDE\n          (unescaped == 0x7E)) {\n\n        path.setCharAt(start, (char) unescaped);\n        path.delete(start + 1, start + 3);\n      }\n    } else {\n      throw new IllegalArgumentException(\"Invalid position for escape character: \" + start);\n    }\n  }\n}\n","sourceCodeStart":303,"sourceCodeEnd":325,"githubUrl":"https://github.com/eclipse-vertx/vert.x/blob/fb308bd8c3f12c79f4ae89bef67fadf6c80d036e/vertx-core/src/main/java/io/vertx/core/internal/net/RFC3986.java#L303-L325","documentation":"decodeUnreserved expects a '%' escape to start exactly where the caller says: path.charAt(start) must be '%'. If the character at the given position is not '%', the method throws this IllegalArgumentException. It indicates a caller/index bug or a string that was mutated between locating the escape and decoding it.","triggerScenarios":"Passing a start index that does not point at '%' — e.g. an index computed with indexOf('%') on a different string, an off-by-one when iterating (skipping 3 vs restarting at start+1), or decoding a string that was already modified so escapes shifted position.","commonSituations":"Custom loops that scan for '%' and call decode per match with a stale index after earlier in-place setCharAt/delete mutations; reusing cached indices after the buffer changed; calling the internal method directly with an offset from a substring operation.","solutions":["Recompute the '%' index immediately before each decode call on the current string","Iterate from the returned next position rather than assuming fixed 3-character advances when decoding in place","Prefer calling the public decodeUnreserved(String)/decodeUnreservedChars entry point, which scans escapes itself, instead of invoking the position-based method manually","Guard the call: only invoke when path.charAt(start) == '%'"],"exampleFix":"// before\npool.decodeUnreserved(path, idx); // idx from an earlier scan, may no longer point at '%'\n// after\nif (idx < path.length() && path.charAt(idx) == '%') {\n  pool.decodeUnreserved(path, idx);\n}","handlingStrategy":"validation","validationCode":"if (start >= 0 && start < path.length() && path.charAt(start) == '%') {\n  pool.decodeUnreserved(path, start);\n}","typeGuard":"boolean pointsAtEscape(StringBuilder path, int start) {\n  return start >= 0 && start < path.length() && path.charAt(start) == '%';\n}","tryCatchPattern":"try {\n  pool.decodeUnreserved(path, start);\n} catch (IllegalArgumentException e) {\n  if (e.getMessage() != null && e.getMessage().contains(\"Invalid position for escape character\")) {\n    start = path.indexOf(\"%\", start); // recompute index and retry once\n  } else throw e;\n}","preventionTips":["Recompute the '%' index on the current buffer right before decoding — never reuse stale indices","After in-place mutation (setCharAt/delete), rescan rather than advancing by a fixed stride","Prefer public decode entry points over the position-based internal method","Log the offending index and surrounding characters to catch off-by-one loops early"],"tags":["uri","percent-encoding","index","illegal-argument"],"backgroundTag":"invalid-url-format","analyzedSha":"fb308bd8c3f12c79f4ae89bef67fadf6c80d036e","analyzedAt":"2026-09-06T11:37:12.241Z","contentChangedAt":"2026-09-06T11:37:12.241Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}