grpc/grpc-go · error

rls: unregistered scheme in lookup_service %s

Error message

rls: unregistered scheme in lookup_service %s

What it means

Returned by parseRLSProto (config.go:206-211) when the scheme parsed from lookup_service is not a registered resolver scheme (resolver.Get(scheme) returns nil). gRPC needs a resolver for the scheme to turn the target into endpoints; an unregistered scheme means no resolver can handle it.

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 03255a9237)

Solutions

  1. Register the resolver for the scheme: add the blank import, e.g. _ "google.golang.org/grpc/xds" or _ "google.golang.org/grpc/balancer/rls" as appropriate, or your custom resolver package.
  2. Use a scheme that is registered by default (e.g. "dns:///") if you don't need a special resolver.
  3. If you mistyped the scheme, correct it (note: lookup_service with no scheme is auto-prefixed with the default scheme, so this error implies a non-default, unregistered scheme was present).

Example fix

// before: "xds" resolver not imported, lookup_service = "xds:///rls"
import (
  _ "google.golang.org/grpc/balancer/rls"
)

// after: also import the resolver for the scheme you reference
import (
  _ "google.golang.org/grpc/balancer/rls"
  _ "google.golang.org/grpc/xds"  // registers the xds resolver
)
// or use the default scheme:
rlc.LookupService = "dns:///rls.example:443"
Defensive patterns

Strategy: validation

Validate before calling

import "google.golang.org/grpc/resolver"

func validateLookupServiceScheme(svc string) error {
    t, err := url.Parse(svc)
    if err != nil { return nil } // caught by the URI check
    scheme := t.Scheme
    if scheme == "" { scheme = resolver.GetDefaultScheme() }
    if resolver.Get(scheme) == nil {
        return fmt.Errorf("unregistered scheme %s in %s", scheme, svc)
    }
    return nil
}

Prevention

When it happens

Trigger: lookup_service uses a scheme whose resolver is not registered in the client binary, e.g. "xds://..." without the xds resolver imported, or a custom/typo scheme like "grpcs:///".

Common situations: Client binary does not import the side-effect import for the needed resolver (e.g. _ "google.golang.org/grpc/xds" for xds, or a custom resolver package). Typos in the scheme. Control plane using a scheme the client doesn't support.

Related errors


AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07). Data as JSON: /api/errors/75002af6c89c9413. Report an issue: GitHub.