{"record":{"id":"1c4a38032605ace9","repo":"grpc/grpc-go","slug":"failed-to-unmarshal-message-is-t-want-proto-mes","errorCode":null,"errorMessage":"failed to unmarshal, message is %T, want proto.Message","messagePattern":"failed to unmarshal, message is %T, want proto\\.Message","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"encoding/proto/proto.go","lineNumber":88,"sourceCode":"\t\t}\n\t\tdata = append(data, mem.SliceBuffer(buf))\n\t} else {\n\t\tpool := mem.DefaultBufferPool()\n\t\tbuf := pool.Get(size)\n\t\tif _, err := marshalOptions.MarshalAppend((*buf)[:0], vv); err != nil {\n\t\t\tpool.Put(buf)\n\t\t\treturn nil, err\n\t\t}\n\t\tdata = append(data, mem.NewBuffer(buf, pool))\n\t}\n\n\treturn data, nil\n}\n\nfunc (c *codecV2) Unmarshal(data mem.BufferSlice, v any) (err error) {\n\tvv := messageV2Of(v)\n\tif vv == nil {\n\t\treturn fmt.Errorf(\"failed to unmarshal, message is %T, want proto.Message\", v)\n\t}\n\n\tbuf := data.MaterializeToBuffer(mem.DefaultBufferPool())\n\tdefer buf.Free()\n\t// TODO: Upgrade proto.Unmarshal to support mem.BufferSlice. Right now, it's not\n\t//  really possible without a major overhaul of the proto package, but the\n\t//  vtprotobuf library may be able to support this.\n\treturn proto.Unmarshal(buf.ReadOnlyData(), vv)\n}\n\nfunc messageV2Of(v any) proto.Message {\n\tswitch v := v.(type) {\n\tcase protoadapt.MessageV1:\n\t\treturn protoadapt.MessageV2Of(v)\n\tcase protoadapt.MessageV2:\n\t\treturn v\n\t}\n","sourceCodeStart":70,"sourceCodeEnd":106,"githubUrl":"https://github.com/grpc/grpc-go/blob/03255a9237b6eb32710f6bc4f2de9a675b99fe36/encoding/proto/proto.go#L70-L106","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nvar resp struct{ Msg string }\nclient.Invoke(ctx, \"/svc/Do\", req, &resp)\n\n// after\nvar resp pb.Pong\nclient.Invoke(ctx, \"/svc/Do\", req, &resp)","handlingStrategy":"type-guard","validationCode":"func isProtoPtr(v any) bool {\n    // best-effort: check the concrete type implements protoadapt.MessageV1/V2\n    switch v.(type) {\n    case protoadapt.MessageV1, protoadapt.MessageV2:\n        return true\n    }\n    return false\n}","typeGuard":"func isUnmarshalableProto(v any) bool {\n    switch v.(type) {\n    case protoadapt.MessageV1, protoadapt.MessageV2:\n        return true\n    }\n    return false\n}","tryCatchPattern":"if err := proto.Unmarshal(buf, m); err != nil {\n    return fmt.Errorf(\"unmarshal: %w\", err)\n}","preventionTips":["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."],"tags":["grpc","proto","codec","unmarshal","encoding","serialization"],"analyzedSha":"03255a9237b6eb32710f6bc4f2de9a675b99fe36","analyzedAt":"2026-08-07T00:29:34.215Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}