grpc/grpc-go · error
proto: failed to marshal, message is %T, want proto.Message
Error message
proto: failed to marshal, message is %T, want proto.Message
What it means
The default proto codec's Marshal received a value that is neither a proto.MessageV1 (legacy github.com/golang/protobuf) nor a proto.MessageV2 (google.golang.org/protobuf), as determined by messageV2Of returning nil. gRPC's default wire format is protobuf; sending a non-proto Go value (a plain struct, map, string, etc.) triggers this on the sending side.
Source
Thrown at encoding/proto/proto.go:46
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/protoadapt"
)
// Name is the name registered for the proto compressor.
const Name = "proto"
func init() {
encoding.RegisterCodecV2(&codecV2{})
}
// codec is a CodecV2 implementation with protobuf. It is the default codec for
// gRPC.
type codecV2 struct{}
func (c *codecV2) Marshal(v any) (data mem.BufferSlice, err error) {
vv := messageV2Of(v)
if vv == nil {
return nil, fmt.Errorf("proto: failed to marshal, message is %T, want proto.Message", v)
}
// Important: if we remove this Size call then we cannot use
// UseCachedSize in MarshalOptions below.
size := proto.Size(vv)
// MarshalOptions with UseCachedSize allows reusing the result from the
// previous Size call. This is safe here because:
//
// 1. We just computed the size.
// 2. We assume the message is not being mutated concurrently.
//
// Important: If the proto.Size call above is removed, using UseCachedSize
// becomes unsafe and may lead to incorrect marshaling.
//
// For more details, see the doc of UseCachedSize:
// https://pkg.go.dev/google.golang.org/protobuf/proto#MarshalOptions
marshalOptions := proto.MarshalOptions{UseCachedSize: true}View on GitHub (pinned to 03255a9237)
Solutions
- Make sure every RPC request/response is a generated protobuf message type (import the generated *.pb.go and use its structs).
- If you intentionally want non-proto payloads, register a custom encoding.Codec (e.g. a JSON codec named "json") and set the :content-type metadata / WithDefaultCallOptions(ForceCodec(...)).
- If you must use legacy github.com/golang/protobuf messages, ensure that module is imported so protoadapt.MessageV1Of can adapt them.
Example fix
// before
type Ping struct{ Msg string } // not a proto message
client.Invoke(ctx, "/svc/Do", &Ping{Msg:"hi"}, &Pong{})
// after
// after generating pb.go: client.Invoke(ctx, "/svc/Do", &pb.Ping{Msg:"hi"}, &pb.Pong{}) Defensive patterns
Strategy: type-guard
Validate before calling
import (
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/protoadapt"
)
func isProtoMessage(v any) bool {
switch v.(type) {
case protoadapt.MessageV1, protoadapt.MessageV2, proto.Message:
return true
}
return false
} Type guard
func isMarshalableProto(v any) bool {
switch v.(type) {
case protoadapt.MessageV1, protoadapt.MessageV2:
return true
}
return false
} Try / catch
data, err := proto.Marshal(m) // or via codec
if err != nil { return fmt.Errorf("marshal: %w", err) } Prevention
- Always use generated *pb.Message types for RPC payloads.
- Register a custom codec (e.g. json) if you must send non-proto payloads and force it via ForceCodec.
- Keep github.com/golang/protobuf imported if you interop with V1 messages.
When it happens
Trigger: Using grpc.UnaryClientInterceptor or a generic client to call client.Invoke with a non-proto request struct; passing a *struct{} or a custom message type that does not embed proto message methods; mixing grpc-go with a hand-rolled codec but forgetting to register it with encoding.RegisterCodec.
Common situations: Beginner mistake of trying to send a Go map or plain struct over gRPC; generating stubs with an outdated protoc plugin that produced V1 messages but the binary also lacks the V1->V2 adapter; sending the empty interface value nil.
Related errors
- failed to unmarshal, message is %T, want proto.Message
- nil receiver passed to UnmarshalJSON
- pemfile: protojson.Unmarshal(%+v) failed: %v
- grpc: invalid gzip compression level: %d
- header key %q contains value with non-printable ASCII charac
AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07).
Data as JSON: /api/errors/a6526bdf6f6af260.
Report an issue: GitHub.