go-kratos/kratos · error
not proto message
Error message
not proto message
What it means
'not proto message' (encoding/proto/proto.go:46) is returned by getProtoMessage during codec.Unmarshal for the 'proto' codec. The helper accepts a proto.Message directly or unwraps any chain of pointers (val.Elem().Interface() recursion), but as soon as it meets a non-pointer that is not a proto.Message it fails. Note the asymmetry: Marshal does a raw type assertion v.(proto.Message) and would panic instead, so this error is specifically the Unmarshal path's graceful rejection.
Source
Thrown at encoding/proto/proto.go:46
func (codec) Unmarshal(data []byte, v any) error {
pm, err := getProtoMessage(v)
if err != nil {
return err
}
return proto.Unmarshal(data, pm)
}
func (codec) Name() string {
return Name
}
func getProtoMessage(v any) (proto.Message, error) {
if msg, ok := v.(proto.Message); ok {
return msg, nil
}
val := reflect.ValueOf(v)
if val.Kind() != reflect.Pointer {
return nil, errors.New("not proto message")
}
val = val.Elem()
return getProtoMessage(val.Interface())
}
View on GitHub (pinned to 668db92c2c)
Solutions
- Pass a pointer to a protoc-generated message (new google.golang.org/protobuf API) as the Unmarshal target, e.g. &pb.HelloReply{}
- If the payload is JSON, switch the codec/content-type to json instead of proto
- Ensure generated code comes from protoc-gen-go matching the protobuf runtime the codec registers (no gogo/legacy types)
- In generic code, type-switch and only route proto.Message targets through this codec
Example fix
// before
codec.Unmarshal(data, &map[string]any{}) // not proto message
// or handler with non-proto reply under proto content-type
// after
var reply pb.HelloReply
if err := codec.Unmarshal(data, &reply); err != nil { return err } Defensive patterns
Strategy: type-guard
Type guard
func isProtoMessage(v any) bool {
_, ok := v.(proto.Message)
if ok { return true }
rv := reflect.ValueOf(v)
for rv.Kind() == reflect.Pointer && !rv.IsNil() {
rv = rv.Elem()
if _, ok := rv.Interface().(proto.Message); ok { return true }
}
return false
} Try / catch
if err := codec.Unmarshal(data, target); err != nil {
if err.Error() == "not proto message" {
return fmt.Errorf("route negotiated proto codec but target %T is not a proto.Message", target)
}
return err
} Prevention
- Always pass pointers to protoc-generated messages (new protobuf API) as codec targets
- Keep client and server codec/content-type negotiation aligned (proto with proto, json with json)
- Generate code with the protoc-gen-go that matches google.golang.org/protobuf - avoid gogo/legacy types through this codec
- Type-switch in generic layers and route only proto.Message targets to the proto codec
When it happens
Trigger: The proto codec is selected (gRPC, or HTTP with Content-Type whose subtype is 'proto') and Unmarshal is handed a non-proto target: a map[string]any, *struct{}, a value type rather than pointer, a string/[]byte wrapper, or a custom struct from another codebase. Typical with reflect-based generic wrappers that allocate reply types dynamically.
Common situations: Server handler bound to a non-proto request/response struct while the route negotiated the proto codec; client declared with proto codec calling an endpoint whose reply is plain JSON-shaped; proxy/middleware that wraps messages in envelope types; protoc version skew producing types registered under google.golang.org/protobuf vs gogo/goprotobuf (the latter does not satisfy the new-gen proto.Message).
Related errors
- invalid formatting for map key
- no field path
- no value provided
- %s already exists
- unsupported key: %s format: %s
AI-assisted analysis of go-kratos/kratos@668db92c2c (2026-08-16).
Data as JSON: /api/errors/ce777168eb859ee7.
Report an issue: GitHub.