grpc/grpc-go · error

rls: method name is not of the form '/service/method

Error message

rls: method name %q is not of the form '/service/method

What it means

Every gRPC full method name must be of the form "/<service>/<method>" (exactly one leading slash and exactly two slashes total). The RLS picker validates this with isFullMethodNameValid (picker.go:77-79) before building the RLS key, because key extraction splits the path on the last '/'. A malformed method name makes RLS routing impossible, so the RPC fails immediately.

Solutions

  1. Ensure the method name passed to Invoke is "/<fully.qualified.Service>/<Method>".
  2. Use generated client stubs which always produce correct full method names.
  3. If using generic calls (grpc.NewClient with a manual method), double-check the string has one leading slash and exactly one interior slash.

Example fix

// before
err := cc.Invoke(ctx, "/SayHello", req, resp) // missing service, only 1 slash
// after
err := cc.Invoke(ctx, "/my.pkg.Greeter/SayHello", req, resp)
Defensive patterns

Strategy: validation

Validate before calling

func isValidFullMethodName(name string) bool {
	return strings.HasPrefix(name, "/") && strings.Count(name, "/") == 2
}

Try / catch

// inside your RPC wrapper
err := cc.Invoke(ctx, method, req, resp)
if err != nil && strings.Contains(err.Error(), "is not of the form '/service/method") {
    // bad method name; fix the caller rather than retry
    return fmt.Errorf("programming error: malformed method %q", method)
}

Prevention

When it happens

Trigger: A Pick is invoked (an RPC is made) whose FullMethodName lacks the leading '/', has fewer or more than two slashes, or is empty. With generated stubs this is rare; it happens with manual invocations or custom balancer/picker plumbing that sets a bad method name.

Common situations: Calling ClientConn.Invoke / InvokeNew with a hand-written method string; a custom interceptor or balancer that overrides FullMethodName; misconfigured generic/unary calls.

Related errors


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

Appendix: source

Thrown at balancer/rls/picker.go:84

	grpcTarget      string
	metricsRecorder estats.MetricsRecorder
	defaultPolicy   *childPolicyWrapper // Child policy for the default target.
	ctrlCh          *controlChannel     // Control channel to the RLS server.
	maxAge          time.Duration       // Cache max age from LB config.
	staleAge        time.Duration       // Cache stale age from LB config.
	bg              exitIdler
	logger          *internalgrpclog.PrefixLogger
}

// isFullMethodNameValid return true if name is of the form `/service/method`.
func isFullMethodNameValid(name string) bool {
	return strings.HasPrefix(name, "/") && strings.Count(name, "/") == 2
}

// Pick makes the routing decision for every outbound RPC.
func (p *rlsPicker) Pick(info balancer.PickInfo) (balancer.PickResult, error) {
	if name := info.FullMethodName; !isFullMethodNameValid(name) {
		return balancer.PickResult{}, fmt.Errorf("rls: method name %q is not of the form '/service/method", name)
	}

	// Build the request's keys using the key builders from LB config.
	md, _ := metadata.FromOutgoingContext(info.Ctx)
	reqKeys := p.kbm.RLSKey(md, p.origEndpoint, info.FullMethodName)

	p.lb.cacheMu.Lock()
	var pr balancer.PickResult
	var err error

	// Record metrics without the cache mutex held, to prevent lock contention
	// between concurrent RPC's and their Pick calls. Metrics Recording can
	// potentially be expensive.
	metricsCallback := func() {}
	defer func() {
		p.lb.cacheMu.Unlock()
		metricsCallback()
	}()

View on GitHub (pinned to 0c51461d27)