grpc-ecosystem/grpc-gateway · error

unable to marshal non proto field

Error message

unable to marshal non proto field

What it means

ProtoMarshaller.Marshal only accepts values implementing the proto.Message interface; if the runtime.Marshaler receives anything else (string, struct, map, json.RawMessage, etc.) it returns "unable to marshal non proto field". This marshaller is meant for endpoints configured with the proto content type (application/proto), so passing a non-proto value to it is a programming/configuration error.

Source

Thrown at runtime/marshal_proto.go:22

	"errors"
	"io"

	"google.golang.org/protobuf/proto"
)

// ProtoMarshaller is a Marshaller which marshals/unmarshals into/from serialize proto bytes
type ProtoMarshaller struct{}

// ContentType always returns "application/octet-stream".
func (*ProtoMarshaller) ContentType(_ interface{}) string {
	return "application/octet-stream"
}

// Marshal marshals "value" into Proto
func (*ProtoMarshaller) Marshal(value interface{}) ([]byte, error) {
	message, ok := value.(proto.Message)
	if !ok {
		return nil, errors.New("unable to marshal non proto field")
	}
	return proto.Marshal(message)
}

// Unmarshal unmarshals proto "data" into "value"
func (*ProtoMarshaller) Unmarshal(data []byte, value interface{}) error {
	message, ok := value.(proto.Message)
	if !ok {
		return errors.New("unable to unmarshal non proto field")
	}
	return proto.Unmarshal(data, message)
}

// NewDecoder returns a Decoder which reads proto stream from "reader".
func (marshaller *ProtoMarshaller) NewDecoder(reader io.Reader) Decoder {
	return DecoderFunc(func(value interface{}) error {
		buffer, err := io.ReadAll(reader)
		if err != nil {

View on GitHub (pinned to a58a4436a3)

Solutions

  1. Ensure the value passed to Marshal implements proto.Message (use generated types like *pb.MyResponse).
  2. If returning JSON, use the JSONPb marshaller instead of ProtoMarshaller.
  3. When writing custom handlers, obtain the proto message first (e.g. from the generated handler or by constructing the pb type) before marshalling.
  4. Check mux/route configuration so the right marshaler is registered for the content type.

Example fix

// before
var resp map[string]interface{} = ...
data, err := protoMarshaller.Marshal(resp)
// after
resp := &pb.MyResponse{Name: "x"}
data, err := protoMarshaller.Marshal(resp)
Defensive patterns

Strategy: type-guard

Validate before calling

if _, ok := v.(proto.Message); !ok {
    return fmt.Errorf("%T does not implement proto.Message; use JSONPb for JSON output", v)
}

Type guard

func isProtoMessage(v interface{}) bool {
    _, ok := v.(proto.Message)
    return ok
}

Try / catch

data, err := marshaller.Marshal(value)
if err != nil {
    if err.Error() == "unable to marshal non proto field" {
        return fmt.Errorf("use JSONPb for non-proto values (%T)", value)
    }
    return err
}

Prevention

When it happens

Trigger: Calling runtime.Marshaler.Marshal (or serving a route configured with runtime.ProtoMarshaller{}) with a value that does not implement proto.Message — e.g. passing an HTTP handler's struct, a map, or a pointer to a non-proto type into runtime.Marshal/ServeContent with the proto marshaller.

Common situations: Writing a custom HTTP handler that mixes the JSON marshaller and proto marshaller incorrectly; wrapping generated handlers with custom code that passes a plain struct; using runtime.ServeContent/HTTPBody with a non-message argument on a proto-configured server.

Related errors


AI-assisted analysis of grpc-ecosystem/grpc-gateway@a58a4436a3 (2026-09-02). Data as JSON: /api/errors/f4d43977f2e132b5. Report an issue: GitHub.