quarkusio/quarkus · error · java.lang.IllegalArgumentException
PURL type must start with a letter:
Error message
PURL type must start with a letter:
What it means
validateType() enforces the PURL spec rule that the type component must begin with an ASCII letter. When the first character of the type is a digit, symbol, or other character, an IllegalArgumentException is thrown. Types are validated whenever a Purl is constructed or parsed.
Source
Thrown at independent-projects/bootstrap/app-model/src/main/java/io/quarkus/sbom/Purl.java:595
for (char c = 'a'; c <= 'z'; c++) {
UNRESERVED[c] = true;
}
for (char c = 'A'; c <= 'Z'; c++) {
UNRESERVED[c] = true;
}
for (char c = '0'; c <= '9'; c++) {
UNRESERVED[c] = true;
}
UNRESERVED['-'] = true;
UNRESERVED['.'] = true;
UNRESERVED['_'] = true;
UNRESERVED['~'] = true;
}
private static void validateType(String type) {
char first = type.charAt(0);
if (!((first >= 'a' && first <= 'z') || (first >= 'A' && first <= 'Z'))) {
throw new IllegalArgumentException("PURL type must start with a letter: " + type);
}
for (int i = 1; i < type.length(); i++) {
char c = type.charAt(i);
if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
|| (c >= '0' && c <= '9') || c == '.' || c == '+' || c == '-')) {
throw new IllegalArgumentException("PURL type contains invalid character '" + c + "': " + type);
}
}
}
private static boolean isUnreserved(int c) {
return c >= 0 && c < 128 && UNRESERVED[c];
}
public static class Builder {
private String type;
private String namespace;View on GitHub (pinned to e1c734241f)
Solutions
- Rename the type so it starts with a letter, e.g. 'pkg:npm/...' instead of 'pkg:3rdparty/...'
- Ensure the type comes from the standard list (maven, npm, pypi, golang, etc.) or starts with an ASCII letter
- Check your parsing code that it slices the segment between 'pkg:' and the first '/' exactly, without an empty or shifted start
Example fix
// before
Purl p = Purl.parse("pkg:3rdparty/acme-lib@1.0");
// after
Purl p = Purl.parse("pkg:thirdparty/acme-lib@1.0"); Defensive patterns
Strategy: validation
Validate before calling
static boolean isValidPurlTypeStart(String type) {
if (type == null || type.isEmpty()) return false;
char first = type.charAt(0);
return (first >= 'a' && first <= 'z') || (first >= 'A' && first <= 'Z');
} Type guard
boolean looksLikePurl(String s) {
return s != null && s.startsWith("pkg:") && isValidPurlTypeStart(s.substring(4, Math.min(s.indexOf('/', 4), s.length())));
} Try / catch
try {
return Purl.parse(purlString);
} catch (IllegalArgumentException e) {
if (e.getMessage().startsWith("PURL type must start with a letter")) {
log.errorf("Unsupported PURL type in: %s", purlString);
}
throw e;
} Prevention
- Prefer the standard PURL types (maven, npm, pypi, golang, ...) over custom ones
- Validate ecosystem names from external data feeds before constructing PURLs
- Check your scheme-splitting logic leaves a non-empty, letter-initial type
When it happens
Trigger: Purl.parse() or Purl.Builder with a type whose first character is not a-z or A-Z, e.g. 'pkg:3rdparty/name', 'pkg:_internal/app', 'pkg:1 Maven-type' or an empty/blank type.
Common situations: Inventing custom ecosystem names starting with digits ('2fa', '3d'); passing an empty type because the coordinate scheme was mis-split ('pkg:/' with nothing between); truncating the 'pkg:' scheme leaving a slash or symbol as the first char of the parsed type.
Related errors
- Invalid PURL:
- Invalid PURL qualifier
- type must not be empty
- name must not be empty
- Maven PURL is missing a namespace (groupId) for name (artifa
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/949ce93283586c6f.
Report an issue: GitHub.