apple/pkl · error · IllegalArgumentException

Rewrite rule must end with '/', but was

Error message

Rewrite rule must end with '/', but was '%s'

What it means

validateRewriteRule also requires rewrite rule URIs to end with '/' so that rule keys and targets behave as directory-like prefixes. This IllegalArgumentException is thrown when the rule URI is http(s) but lacks the trailing slash.

Solutions

  1. Append a trailing '/' to the rewrite rule URI in your configuration
  2. Normalize the URI string before passing it to the API (add '/' if missing)
  3. Re-run with the corrected value in PklProject.yml or CLI flags

Example fix

// before
rewrite { ["example.com/pkg/"] = "https://mirror.example/pkg" }
// after
rewrite { ["example.com/pkg/"] = "https://mirror.example/pkg/" }
Defensive patterns

Strategy: validation

Validate before calling

if (!ruleStr.endsWith("/")) ruleStr = ruleStr + "/";

Type guard

function endsWithSlash(u) { return u.toString().endsWith("/"); }

Try / catch

try { IoUtils.validateRewriteRule(rewrite) } catch (IllegalArgumentException e) { /* auto-append slash or report config error */ }

Prevention

When it happens

Trigger: validateRewriteRule receives e.g. https://localhost:8080/mypackage (no trailing slash) as a rewrite rule from --rewrite-settings or project config.

Common situations: Writing rewrite targets in config without the trailing slash, causing prefix matching to fail or be ambiguous.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


AI-assisted analysis of apple/pkl@f3efcbfc9b (2026-09-08). Data as JSON: /api/errors/bf38a474e3e690ed. Report an issue: GitHub.

Appendix: source

Thrown at pkl-core/src/main/java/org/pkl/core/util/IoUtils.java:905

    }
    return index;
  }

  public static void validateFileUri(URI uri) throws URISyntaxException {
    if (!uri.getSchemeSpecificPart().startsWith("/")) {
      throw new URISyntaxException(uri.toString(), ErrorMessages.create("invalidOpaqueFileUri"));
    }
  }

  public static void validateRewriteRule(URI rewrite) {
    if (!Objects.equals(rewrite.getScheme(), "http")
        && !Objects.equals(rewrite.getScheme(), "https")) {
      throw new IllegalArgumentException(
          "Rewrite rule must start with 'http://' or 'https://', but was '%s'".formatted(rewrite));
    }

    if (!rewrite.toString().endsWith("/")) {
      throw new IllegalArgumentException(
          "Rewrite rule must end with '/', but was '%s'".formatted(rewrite));
    }
  }

  private static boolean isReservedHeaderName(String headerName) {
    var normalizedHeader = headerName.toLowerCase(Locale.ROOT);
    for (var reservedHeader : reservedHeaderNames) {
      if (normalizedHeader.equals(reservedHeader)) {
        return true;
      }
    }
    return false;
  }

  private static boolean hasReservedHeaderPrefix(String headerName) {
    var normalizedHeader = headerName.toLowerCase(Locale.ROOT);
    for (var prefix : reservedHeaderPrefixes) {
      if (normalizedHeader.startsWith(prefix)) {

View on GitHub (pinned to f3efcbfc9b)