grpc/grpc-go · error
rls: unregistered scheme in lookup_service
Error message
rls: unregistered scheme in lookup_service %s
What it means
Emitted from parseRLSProto (config.go:210) when the URI scheme parsed from lookup_service has no resolver registered via resolver.Get. The RLS balancer resolves the lookup_service address through the standard gRPC resolver machinery, so the scheme must correspond to a registered resolver (dns, passthrough, xds, unix, etc.).
Solutions
- Use the default scheme by omitting the scheme (the balancer prepends resolver.GetDefaultScheme()), or use a scheme you know is registered.
- Blank-import the resolver package providing the scheme, e.g. _ 'google.golang.org/grpc/resolver/dns' or _ 'google.golang.org/grpc/xds/internal/resolver'.
- Register your custom resolver with resolver.Register before dialing if you have a proprietary scheme.
- Switch to 'passthrough:///host:port' for tests or when the address is already resolved.
Example fix
// before "lookupService": "mydns:///rls.example.com:443" // mydns not registered // after "lookupService": "dns:///rls.example.com:443" // and ensure the binary imports the dns resolver if required by your grpc version
Defensive patterns
Strategy: validation
Validate before calling
func validateLookupScheme(s string) error {
u, err := url.Parse(s)
if err != nil {
u, err = url.Parse(resolver.GetDefaultScheme() + "/" + s)
if err != nil {
return fmt.Errorf("lookup_service %q is not a valid URI", s)
}
}
scheme := u.Scheme
if scheme == "" {
scheme = resolver.GetDefaultScheme()
}
if resolver.Get(scheme) == nil {
return fmt.Errorf("no resolver registered for scheme %q", scheme)
}
return nil
} Type guard
func lookupSchemeIsRegistered(s string) bool {
u, _ := url.Parse(s)
scheme := u.Scheme
if scheme == "" {
scheme = resolver.GetDefaultScheme()
}
return resolver.Get(scheme) != nil
} Prevention
- Blank-import the resolver package for any non-default scheme you reference.
- Prefer 'dns:///host:port' or omit the scheme to use the default.
- Register custom resolvers via resolver.Register before dialing.
When it happens
Trigger: lookupService uses a scheme like 'foo:///' where 'foo' was never registered with resolver.Register, or uses a scheme whose resolver lives in a package the binary does not import.
Common situations: Using 'dns:///host' but the binary does not import the dns resolver (in older grpc versions) ; using a custom resolver scheme without blank-importing its package; a typo in the scheme ('gdns', 'xds2'); relying on xds-federation/experimental schemes that are not built into this binary.
Related errors
- rls: empty lookup_service in route lookup config %+v
- rls: invalid target URI in lookup_service
- dns resolver: missing address
- dns resolver: missing port after port-separator colon
- invalid target address
AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11).
Data as JSON: /api/errors/75002af6c89c9413.
Report an issue: GitHub.
Appendix: source
Thrown at balancer/rls/config.go:210
// `lookup_service` field must be set and must parse as a target URI.
lookupService := rlsProto.GetLookupService()
if lookupService == "" {
return nil, fmt.Errorf("rls: empty lookup_service in route lookup config %+v", rlsProto)
}
parsedTarget, err := url.Parse(lookupService)
if err != nil {
// url.Parse() fails if scheme is missing. Retry with default scheme.
parsedTarget, err = url.Parse(resolver.GetDefaultScheme() + ":///" + lookupService)
if err != nil {
return nil, fmt.Errorf("rls: invalid target URI in lookup_service %s", lookupService)
}
}
if parsedTarget.Scheme == "" {
parsedTarget.Scheme = resolver.GetDefaultScheme()
}
if resolver.Get(parsedTarget.Scheme) == nil {
return nil, fmt.Errorf("rls: unregistered scheme in lookup_service %s", lookupService)
}
lookupServiceTimeout, err := convertDuration(rlsProto.GetLookupServiceTimeout())
if err != nil {
return nil, fmt.Errorf("rls: failed to parse lookup_service_timeout in route lookup config %+v: %v", rlsProto, err)
}
if lookupServiceTimeout == 0 {
lookupServiceTimeout = defaultLookupServiceTimeout
}
// Validations performed here:
// - if `max_age` > 5m, it should be set to 5 minutes
// only if stale age is not set
// - if `stale_age` > `max_age`, ignore it
// - if `stale_age` is set, then `max_age` must also be set
maxAgeSet := false
maxAge, err := convertDuration(rlsProto.GetMaxAge())
if err != nil {View on GitHub (pinned to 0c51461d27)