go-kratos/kratos · error
failed to marshal, message is %T, want proto.Message
Error message
failed to marshal, message is %T, want proto.Message
What it means
Codec error from Kratos' protojson encoding.RegisterCodec: Marshal was called with a value that does not implement proto.Message (google.golang.org/protobuf/proto). The codec is registered under the protojson name, so any framework path that marshals with it - HTTP response encoding, codec.Marshal, mid-client calls - requires a real protobuf-generated message, not a plain Go struct, map, or string. %T reports the concrete type that was passed.
Source
Thrown at encoding/protojson/protojson.go:36
EmitUnpopulated: true,
}
// UnmarshalOptions is a configurable JSON format parser.
UnmarshalOptions = protojson.UnmarshalOptions{
DiscardUnknown: true,
}
)
func init() {
encoding.RegisterCodec(codec{})
}
// codec is a Codec implementation with protojson.
type codec struct{}
func (codec) Marshal(v any) ([]byte, error) {
m, ok := v.(proto.Message)
if !ok {
return nil, fmt.Errorf("failed to marshal, message is %T, want proto.Message", v)
}
return MarshalOptions.Marshal(m)
}
func (codec) Unmarshal(data []byte, v any) error {
if len(data) == 0 {
return nil
}
m, ok := v.(proto.Message)
if !ok {
return fmt.Errorf("failed to unmarshal, message is %T, want proto.Message", v)
}
return UnmarshalOptions.Unmarshal(data, m)
}
func (codec) Name() string {
return Name
}View on GitHub (pinned to 668db92c2c)
Solutions
- Return the protobuf-generated message type itself (v2 API, google.golang.org/protobuf) from handlers/marshal calls
- Remove non-proto envelope wrappers; put metadata into proto fields instead of wrapping the message in an anonymous struct
- If you intended plain JSON, register/use the 'json' codec for that route instead of 'protojson'
- For legacy gogo/v1 generated code, regenerate with the modern protoc-gen-go or provide a custom codec that accepts those types
- Check the %T in the message to find which call site passed the wrong type
Example fix
// before: handler returns a plain struct under the protojson codec
type resp struct{ Msg string }
return &resp{Msg: "hi"} // -> failed to marshal, message is *main.resp, want proto.Message
// after: return the generated proto message
return &v1.SayHelloResponse{Msg: "hi"}, nil Defensive patterns
Strategy: type-guard
Validate before calling
// Guard before marshaling with the protojson codec
func marshalProtojson(v any) ([]byte, error) {
if _, ok := v.(proto.Message); !ok {
return nil, fmt.Errorf("protojson codec requires proto.Message, got %T", v)
}
return protojson.Marshal(v.(proto.Message))
} Type guard
func isProtoMessage(v any) bool {
_, ok := v.(proto.Message)
return ok
} Try / catch
data, err := codec.Marshal(v)
if err != nil {
if strings.Contains(err.Error(), "want proto.Message") {
// wrong type at call site: return a developer-facing 500, never retry
log.Error("protojson misuse", "type", fmt.Sprintf("%T", v))
return errors.InternalServer("CODEC_MISUSE", err.Error())
}
} Prevention
- Type handler returns as generated proto messages; compile-time check: func() proto.Message { return h() }
- Use the 'json' codec (encoding/json) for routes that return plain structs
- Avoid envelope wrappers around proto messages; add metadata as proto fields
- During protobuf migrations, grep for legacy gogo/golang-protobuf types in handlers
When it happens
Trigger: Returning a non-proto type (plain struct, map[string]any, string) from a Kratos HTTP handler whose response codec is protojson; passing a gogo/protobuf or golang/protobuf (v1 API) message, which does not implement the new proto.Message; configuring the route/service to use codec name 'protojson' while the handler returns JSON-built structs; client-side codec.Marshal(protojson.Name) over arbitrary values.
Common situations: Mixing JSON-first handlers into a proto-based service; migrating from gogo/golang-protobuf generated types to google.golang.org/protobuf and missing one message; wrapping proto messages in an envelope struct (e.g. {Data: msg}) that itself is not a proto message; using protojson codec by name string where a json codec was intended.
Related errors
- invalid path: %q is not a message
- too many values for field %q: %s
- response Error %d
- field already set for oneof %q
- parsing field %q: %w
AI-assisted analysis of go-kratos/kratos@668db92c2c (2026-08-16).
Data as JSON: /api/errors/a7858d33fb0ecc10.
Report an issue: GitHub.