apple/pkl · error · IOException

Unsupported protocol:

Error message

Unsupported protocol: 

What it means

IoUtils.toUrl converts a URI to a java.net.URL; on GraalVM native-image, URI schemes without a registered protocol handler throw UnsupportedFeatureError at runtime. toUrl catches that specific error and rethrows an IOException "Unsupported protocol: <scheme>", leaving context-giving to the caller, since the scheme has no URL stream handler available in the native binary.

Solutions

  1. Use a scheme with a registered handler (file:, http:, https:) for the actual fetch
  2. Route custom-scheme URIs through their proper reader/resolver instead of toUrl/URL.openStream
  3. Check the scheme spelling in the URI
  4. If embedding Pkl native-image, register a protocol handler for the custom scheme

Example fix

// before
URL url = IoUtils.toUrl(URI.create("projectpartial:foo"));
// after
URL url = IoUtils.toUrl(URI.create("file:///path/to/project/foo"));
Defensive patterns

Strategy: try-catch

Validate before calling

const SUPPORTED = ['http', 'https', 'file', 'jar'];
const hasSupportedProtocol = (uri) => SUPPORTED.includes((uri.match(/^([a-z][a-z0-9+.-]*):/) || [])[1] || '');

Type guard

const isSupportedUrl = (uri) => /^(https?|file|jar):/.test(uri);

Try / catch

try {
  URL url = IoUtils.toUrl(uri);
} catch (IOException e) {
  if (e.getMessage().startsWith("Unsupported protocol:")) {
    // resolve via the scheme-specific reader instead of URL.openStream
  } else throw e;
}

Prevention

When it happens

Trigger: Calling IoUtils.toUrl with a URI whose scheme has no java.net.URL protocol handler in the current runtime — e.g. a custom scheme ("project:", "module:", "pkg:") on GraalVM native-image, or genuinely unhandled schemes.

Common situations: Running Pkl as a native image where only file/http(s)/jar handlers are registered but code attempts to open a custom-scheme URI as a URL; typo'd schemes; or code bypassing the proper scheme-specific reader.

Related errors


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

Appendix: source

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

    "upgrade",
    "te",
    "transfer-encoding",
    "trailer"
  };

  // keep in sync with stdlib `EvaluatorSettings.reservedHttpHeaderPrefix`
  private static final String[] reservedHeaderPrefixes = {"proxy-", "sec-"};

  private IoUtils() {}

  public static URL toUrl(URI uri) throws IOException {
    try {
      return uri.toURL();
    } catch (Error e) {
      // best we can do for now
      // rely on caller to provide context, e.g., the requested module URI
      if (e.getClass().getName().equals("com.oracle.svm.core.jdk.UnsupportedFeatureError")) {
        throw new IOException("Unsupported protocol: " + uri.getScheme());
      }

      throw e;
    }
  }

  /** Checks whether the given string is "URI-like", i.e. matches a pattern like {@code foo:bar}. */
  public static boolean isUriLike(String str) {
    return uriLike.matcher(str).matches();
  }

  public static boolean isWindowsAbsolutePath(String str) {
    return windowsDriveLetterLike.matcher(str).matches();
  }

  /**
   * Converts the given string to a {@link URI}. This method MUST be used for constructing module
   * and resource URIs. Unlike {@code new URI(str)}, it correctly escapes paths of relative URIs.

View on GitHub (pinned to f3efcbfc9b)