grpc/grpc-go · critical

could not get resolver for default scheme

Error message

could not get resolver for default scheme: %q

What it means

initParsedTargetAndResolverBuilder first tries the target's own scheme, then falls back to the default scheme (clientconn.go:1822-1858). If neither yields a registered resolver builder, NewClient/Dial fails with this error naming the unresolved scheme. This is a hard failure: without a resolver the channel cannot run.

Solutions

  1. Import the resolver package so its init() registers the builder (e.g., _ "google.golang.org/grpc/resolver/dns").
  2. Use a correct built-in scheme ("dns:///", "passthrough:///") or a registered custom one.
  3. If you call resolver.SetDefaultScheme, do it before any NewClient/Dial and ensure the scheme is registered.

Example fix

// before
cc, err := grpc.NewClient("customproto:///host", grpc.WithTransportCredentials(insecure.NewCredentials()))
// 'customproto' resolver not registered -> error
// after
import _ "example.com/mymodule/customresolver" // registers "customproto"
cc, err := grpc.NewClient("customproto:///host", grpc.WithTransportCredentials(insecure.NewCredentials()))
Defensive patterns

Strategy: validation

Validate before calling

func resolverRegistered(scheme string) bool {
	return resolver.Get(scheme) != nil
}

Try / catch

cc, err := grpc.NewClient(target, opts...)
if err != nil && strings.Contains(err.Error(), "could not get resolver for default scheme") {
    // the target/default scheme is not registered; import the resolver package
}

Prevention

When it happens

Trigger: The target has no scheme and the default scheme's resolver is not registered, or the target's scheme is a typo/unregistered name. cc.getResolver(parsedTarget.URL.Scheme) returns nil at clientconn.go:1852.

Common situations: Using a custom resolver without importing its package (side-effect registration); typo in scheme (e.g., "dnss:///" instead of "dns:///"); NewClient with a bare host but the default scheme was changed and not registered; calling resolver.SetDefaultScheme after dial.

Related errors


AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11). Data as JSON: /api/errors/72c91b26ace740bf. Report an issue: GitHub.

Appendix: source

Thrown at clientconn.go:1854

	// We are here because the user's dial target did not contain a scheme or
	// specified an unregistered scheme. We should fallback to the default
	// scheme, except when a custom dialer is specified in which case, we should
	// always use passthrough scheme. For either case, we need to respect any overridden
	// global defaults set by the user.
	defScheme := cc.dopts.defaultScheme
	if internal.UserSetDefaultScheme {
		defScheme = resolver.GetDefaultScheme()
	}

	canonicalTarget := defScheme + ":///" + cc.target

	parsedTarget, err = parseTarget(canonicalTarget)
	if err != nil {
		return err
	}
	rb = cc.getResolver(parsedTarget.URL.Scheme)
	if rb == nil {
		return fmt.Errorf("could not get resolver for default scheme: %q", parsedTarget.URL.Scheme)
	}
	cc.parsedTarget = parsedTarget
	cc.resolverBuilder = rb
	return nil
}

// parseTarget uses RFC 3986 semantics to parse the given target into a
// resolver.Target struct containing url. Query params are stripped from the
// endpoint.
func parseTarget(target string) (resolver.Target, error) {
	u, err := url.Parse(target)
	if err != nil {
		return resolver.Target{}, err
	}

	return resolver.Target{URL: *u}, nil
}

View on GitHub (pinned to 0c51461d27)