grpc/grpc-go · error
failed to unmarshal, message is %T, want proto.Message
Error message
failed to unmarshal, message is %T, want proto.Message
What it means
The default proto codec's Unmarshal received a destination value that is not a proto.MessageV1 or proto.MessageV2. The receiving side of an RPC must hand the codec a generated protobuf message pointer; passing a non-proto pointer causes messageV2Of to return nil and Unmarshal to fail before decoding.
Source
Thrown at encoding/proto/proto.go:88
}
data = append(data, mem.SliceBuffer(buf))
} else {
pool := mem.DefaultBufferPool()
buf := pool.Get(size)
if _, err := marshalOptions.MarshalAppend((*buf)[:0], vv); err != nil {
pool.Put(buf)
return nil, err
}
data = append(data, mem.NewBuffer(buf, pool))
}
return data, nil
}
func (c *codecV2) Unmarshal(data mem.BufferSlice, v any) (err error) {
vv := messageV2Of(v)
if vv == nil {
return fmt.Errorf("failed to unmarshal, message is %T, want proto.Message", v)
}
buf := data.MaterializeToBuffer(mem.DefaultBufferPool())
defer buf.Free()
// TODO: Upgrade proto.Unmarshal to support mem.BufferSlice. Right now, it's not
// really possible without a major overhaul of the proto package, but the
// vtprotobuf library may be able to support this.
return proto.Unmarshal(buf.ReadOnlyData(), vv)
}
func messageV2Of(v any) proto.Message {
switch v := v.(type) {
case protoadapt.MessageV1:
return protoadapt.MessageV2Of(v)
case protoadapt.MessageV2:
return v
}
View on GitHub (pinned to 03255a9237)
Solutions
- Use the generated protobuf message pointer types (e.g. *pb.GetResponse) as the reply argument for every client call and handler return.
- Regenerate stubs with protoc / buf after .proto changes and rebuild the whole module.
- For non-proto payloads, register a custom codec and request it via content-type / ForceCodec so the proto codec is bypassed entirely.
Example fix
// before
var resp struct{ Msg string }
client.Invoke(ctx, "/svc/Do", req, &resp)
// after
var resp pb.Pong
client.Invoke(ctx, "/svc/Do", req, &resp) Defensive patterns
Strategy: type-guard
Validate before calling
func isProtoPtr(v any) bool {
// best-effort: check the concrete type implements protoadapt.MessageV1/V2
switch v.(type) {
case protoadapt.MessageV1, protoadapt.MessageV2:
return true
}
return false
} Type guard
func isUnmarshalableProto(v any) bool {
switch v.(type) {
case protoadapt.MessageV1, protoadapt.MessageV2:
return true
}
return false
} Try / catch
if err := proto.Unmarshal(buf, m); err != nil {
return fmt.Errorf("unmarshal: %w", err)
} Prevention
- Always pass generated *pb.X pointer types as the reply argument.
- Regenerate stubs after .proto changes and rebuild everything.
- Use ForceCodec with a non-proto codec for non-proto payloads.
When it happens
Trigger: Calling a generated client method with a reply argument that is a plain *struct, *map, or *string instead of the generated *pb.SomeReply; service handler returning into the wrong type; nil reply pointer.
Common situations: Declaring local request/response structs that mirror the proto but are not the generated types; upgrading proto definitions and forgetting to regenerate stubs; passing &someConcreteStruct where the generated *pb.X was expected.
Related errors
- proto: failed to marshal, 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/1c4a38032605ace9.
Report an issue: GitHub.