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

  1. Rename the type so it starts with a letter, e.g. 'pkg:npm/...' instead of 'pkg:3rdparty/...'
  2. Ensure the type comes from the standard list (maven, npm, pypi, golang, etc.) or starts with an ASCII letter
  3. 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

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


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