{"record":{"id":"f2fd31c191b6198b","repo":"quarkusio/quarkus","slug":"purl-type-contains-invalid-character","errorCode":null,"errorMessage":"PURL type contains invalid character '","messagePattern":"PURL type contains invalid character '","errorType":"exception","errorClass":"java.lang.IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"independent-projects/bootstrap/app-model/src/main/java/io/quarkus/sbom/Purl.java","lineNumber":601,"sourceCode":"        for (char c = '0'; c <= '9'; c++) {\n            UNRESERVED[c] = true;\n        }\n        UNRESERVED['-'] = true;\n        UNRESERVED['.'] = true;\n        UNRESERVED['_'] = true;\n        UNRESERVED['~'] = true;\n    }\n\n    private static void validateType(String type) {\n        char first = type.charAt(0);\n        if (!((first >= 'a' && first <= 'z') || (first >= 'A' && first <= 'Z'))) {\n            throw new IllegalArgumentException(\"PURL type must start with a letter: \" + type);\n        }\n        for (int i = 1; i < type.length(); i++) {\n            char c = type.charAt(i);\n            if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')\n                    || (c >= '0' && c <= '9') || c == '.' || c == '+' || c == '-')) {\n                throw new IllegalArgumentException(\"PURL type contains invalid character '\" + c + \"': \" + type);\n            }\n        }\n    }\n\n    private static boolean isUnreserved(int c) {\n        return c >= 0 && c < 128 && UNRESERVED[c];\n    }\n\n    public static class Builder {\n\n        private String type;\n        private String namespace;\n        private String name;\n        private String version;\n        private TreeMap<String, String> qualifiers;\n        private String subpath;\n\n        private Builder() {","sourceCodeStart":583,"sourceCodeEnd":619,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/independent-projects/bootstrap/app-model/src/main/java/io/quarkus/sbom/Purl.java#L583-L619","documentation":"validateType() rejects any character after the first one that is not an ASCII letter, digit, '.', '+' or '-'. This keeps PURL types compliant with the spec's type grammar. The offending character is reported in the message along with the full type.","triggerScenarios":"Constructing or parsing a Purl whose type contains characters like '_', '/', ' ', ':', or non-ASCII letters after the first character, e.g. 'pkg:my_type/app' or 'pkg:foo bar/baz'.","commonSituations":"Using underscore-separated ecosystem names ('go_module' instead of 'gomodule'); passing a whole path or URL fragment instead of just the type; embedding spaces from split coordinates or log output.","solutions":["Replace invalid characters in the type: use '-', '.', or '+' where allowed, e.g. 'my-type' not 'my_type'","Normalize the ecosystem name to the canonical PURL type (lowercase, letters/digits/./+/- only) before constructing","Make sure only the segment between 'pkg:' and the first '/' is passed as the type, not a longer path"],"exampleFix":"// before\nPurl p = Purl.parse(\"pkg:my_type/app@1.0\");\n// after\nPurl p = Purl.parse(\"pkg:my-type/app@1.0\");","handlingStrategy":"validation","validationCode":"static boolean isValidPurlType(String type) {\n    if (type == null || type.isEmpty()) return false;\n    return type.matches(\"[a-zA-Z][a-zA-Z0-9.+-]*\");\n}","typeGuard":"String normalizeType(String raw) {\n    return raw == null ? null : raw.replaceAll(\"[^a-zA-Z0-9.+-]\", \"-\");\n}","tryCatchPattern":"try {\n    return Purl.parse(purlString);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().startsWith(\"PURL type contains invalid character\")) {\n        log.errorf(\"Illegal character in PURL type: %s\", purlString);\n    }\n    throw e;\n}","preventionTips":["Sanitize custom ecosystem names to [a-zA-Z0-9.+-] before use","Never pass paths or URLs as the type component — extract only the segment after 'pkg:' and before the first '/'","Add a unit test asserting every ecosystem constant you use passes the type regex"],"tags":["purl","validation","sbom"],"backgroundTag":"purl-invalid-type","analyzedSha":"e1c734241f34c7919086ceb4c9262b4a58f6de44","analyzedAt":"2026-09-05T17:01:29.979Z","contentChangedAt":"2026-09-05T17:01:29.979Z","schemaVersion":2},"datasetVersion":"2026-09-15T02:17:10.978Z"}