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
- Correct the 'lookupService' value in the service config to a fully qualified, valid URI such as 'https://rls.example.com:443'.
- Validate the URI in advance: new URI(value) in a test or startup check to reproduce the URISyntaxException before deploying.
- Quote/escape special characters and remove whitespace; wrap bare IPv6 hosts in brackets, e.g. 'http://[::1]:50051'.
- 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
- Always include scheme and authority: https://host[:port].
- Run service-config JSON through a startup validator before registering the LB policy.
- Avoid hand-editing configs in production; template them with validation.
- Remember bare host:port strings are not valid URIs — 'localhost:50051' parses as scheme-only.
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
- keys in KeyBuilder must be unique
- Authorization policy should be a JSON object. Found: null
- cannot create a NameResolver for
- Could not find a NameResolverProvider for
- Invalid authority:
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)