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
- Import the resolver package so its init() registers the builder (e.g., _ "google.golang.org/grpc/resolver/dns").
- Use a correct built-in scheme ("dns:///", "passthrough:///") or a registered custom one.
- 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
- Import resolver packages for any custom scheme before dialing (side-effect registration).
- Use built-in schemes (dns, passthrough) for plain hostnames.
- Call resolver.SetDefaultScheme only during init() and before any dial.
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
- failed to start resolver
- ClientConn's authority from transport creds
- failed to exit idle mode
- grpc: the provided default service config is invalid
- "allow_rules" is not present
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)