grpc/grpc-java · error · IllegalArgumentException

The lookupService field is not valid URI

Error message

The lookupService field is not valid URI: ${lookupService}

What it means

When converting the RLS (Route Lookup Service) LB policy JSON config, the 'lookupService' field must parse as a valid URI. This IllegalArgumentException is thrown from the config conversion when new URI(lookupService) raises URISyntaxException, i.e. the configured lookup service address is not a syntactically valid URI.

Solutions

  1. Correct the 'lookupService' value in the service config to a fully qualified, valid URI such as 'https://rls.example.com:443'.
  2. Validate the URI in advance: new URI(value) in a test or startup check to reproduce the URISyntaxException before deploying.
  3. Quote/escape special characters and remove whitespace; wrap bare IPv6 hosts in brackets, e.g. 'http://[::1]:50051'.
  4. Ensure the value is a non-empty string (empty values are rejected earlier with a different message).

Example fix

// before
{"rlsExperiments": {"lookupService": "rls.example.com:443"}}
// after
{"rlsExperiments": {"lookupService": "https://rls.example.com:443"}}
Defensive patterns

Strategy: validation

Validate before calling

static void validateLookupService(String uri) {
  try {
    new URI(uri);
  } catch (URISyntaxException e) {
    throw new IllegalArgumentException("invalid lookupService: " + uri, e);
  }
}

Prevention

When it happens

Trigger: Providing a service config (LoadBalancingConfig) for the rls LB policy where 'lookupService' is a malformed URI — missing scheme, illegal characters, unbalanced brackets in an IPv6 literal, spaces, or a scheme like 'localhost:50051' interpreted as scheme-only.

Common situations: Hand-writing gRPC service config JSON with an RLS policy, forgetting 'http://' or 'https://' prefix, typos in the host, or copy-pasting a target string with whitespace or quotes into the config.

Understand the failure class

Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.

Related errors


AI-assisted analysis of grpc/grpc-java@64daddc1f3 (2026-09-08). Data as JSON: /api/errors/459cfc8fc5fe3dce. Report an issue: GitHub.

Appendix: source

Thrown at rls/src/main/java/io/grpc/rls/RlsProtoConverters.java:149

        Set<String> keys = new HashSet<>();
        for (NameMatcher header : keyBuilder.headers()) {
          checkKeys(keys, header.key(), "header");
        }
        for (String key : keyBuilder.constantKeys().keySet()) {
          checkKeys(keys, key, "constant");
        }
        String extraKeyStr = keyToString(keyBuilder.extraKeys());
        checkArgument(keys.add(extraKeyStr),
            "duplicate extra key in grpc_keybuilders: " + extraKeyStr);
      }

      // Validate lookup_service
      String lookupService = JsonUtil.getString(json, "lookupService");
      checkArgument(!Strings.isNullOrEmpty(lookupService), "lookupService must not be empty");
      try {
        URI unused = new URI(lookupService);
      } catch (URISyntaxException e) {
        throw new IllegalArgumentException(
            "The lookupService field is not valid URI: " + lookupService, e);
      }
      long timeout = orDefault(
          JsonUtil.getStringAsDuration(json, "lookupServiceTimeout"),
          DEFAULT_LOOKUP_SERVICE_TIMEOUT);
      checkArgument(timeout > 0, "lookupServiceTimeout should be positive");
      Long maxAge = JsonUtil.getStringAsDuration(json, "maxAge");
      Long staleAge = JsonUtil.getStringAsDuration(json, "staleAge");
      if (maxAge == null) {
        checkArgument(staleAge == null, "to specify staleAge, must have maxAge");
        maxAge = MAX_AGE_NANOS;
      }
      // If staleAge is not set, clamp maxAge to <= 5.
      if (staleAge == null && maxAge > MAX_AGE_NANOS) {
        maxAge = MAX_AGE_NANOS;
      }
      // Clamp staleAge to <= 5
      if (staleAge == null || staleAge > MAX_AGE_NANOS) {

View on GitHub (pinned to 64daddc1f3)