grpc-ecosystem/grpc-gateway · error

must not set request body when http method is GET

Error message

must not set request body when http method is GET: %s

What it means

HTTP GET requests carry parameters only in the URL; the generator rejects google.api.http 'get' rules that also declare a 'body'. A GET with a body is not valid HTTP semantics, so template building fails with the method descriptor name.

Solutions

  1. Remove the 'body' field from the google.api.http GET annotation
  2. Pass parameters via the path template and query parameters instead
  3. If a request body is truly required, switch the rpc to POST or PUT
  4. Keep GET to a single request field mapped into the URL

Example fix

// before
option (google.api.http) = { get: "/v1/books/{id}" body: "*" };
// after
option (google.api.http) = { get: "/v1/books/{id}" };
Defensive patterns

Strategy: validation

Validate before calling

if rule.GetGet() != "" && rule.GetBody() != "" {
    return fmt.Errorf("rpc %s: GET rule must not declare body", rpc.GetName())
}
err := buildTemplates(rpc)

Prevention

When it happens

Trigger: A rpc's google.api.http option sets both 'get' and 'body' fields; building the template for such a method raises this error immediately when opts.Body != "" under the GetGet() branch.

Common situations: Hand-editing annotations and leaving a 'body: "*"' from a copied PUT/POST rule; tools migrating POST to GET that keep the body field; misunderstandings about GET semantics for large payloads.

Related errors


AI-assisted analysis of grpc-ecosystem/grpc-gateway@a58a4436a3 (2026-09-02). Data as JSON: /api/errors/daa92a4043b128ed. Report an issue: GitHub.

Appendix: source

Thrown at internal/descriptor/services.go:108

	}
	meth := &Method{
		Service:               svc,
		MethodDescriptorProto: md,
		RequestType:           requestType,
		ResponseType:          responseType,
	}

	newBinding := func(opts *options.HttpRule, idx int) (*Binding, error) {
		var (
			httpMethod   string
			pathTemplate string
		)
		switch {
		case opts.GetGet() != "":
			httpMethod = "GET"
			pathTemplate = opts.GetGet()
			if opts.Body != "" {
				return nil, fmt.Errorf("must not set request body when http method is GET: %s", md.GetName())
			}

		case opts.GetPut() != "":
			httpMethod = "PUT"
			pathTemplate = opts.GetPut()

		case opts.GetPost() != "":
			httpMethod = "POST"
			pathTemplate = opts.GetPost()

		case opts.GetDelete() != "":
			httpMethod = "DELETE"
			pathTemplate = opts.GetDelete()
			if opts.Body != "" && !r.allowDeleteBody {
				return nil, fmt.Errorf("must not set request body when http method is DELETE except allow_delete_body option is true: %s", md.GetName())
			}

		case opts.GetPatch() != "":

View on GitHub (pinned to a58a4436a3)