When a parser finds a field number its schema doesn't define, it keeps the data. The wire format already says how many bytes the value takes up, so the parser reads them, stores them as an unknown field, and keeps going. When the message is serialized again, the unknown fields are written out with it. The data is the same, but the bytes may differ. C++, for example, stores an unknown varint as a decoded integer and re-encodes it.
Much of Protobuf's compatibility story depends on this. An old client can accept a message from a new server, touch the fields it knows about, and pass the message along without stripping fields added after it was compiled. The same goes for proxies and queues sitting between services that upgrade on different schedules.
Unknown fields only survive on the decoded message. They are kept when that message is serialized again, or copied with a message-aware operation like proto.Clone in Go or CopyFrom in Python, and many services do neither. If a service unpacks a request into a domain object and builds a fresh response (or even a fresh copy of the same type, field by field), the new message has no unknown fields at all. Converting to JSON drops them too, because the JSON mapping has no way to name a field the reader doesn't know. So unknown fields make it through services that forward messages as they are, and get lost at any service that rebuilds them.
Be careful with this at the edge of your system. A payload from outside can carry unknown fields your validation never inspected, and preservation passes them on to internal services that do understand them. Many teams that expose Protobuf APIs publicly drop unknown fields at the boundary as a precaution. Most runtimes give you a discard option when parsing.
message User {
string id = 1;
string email = 2;
}
{ "id": "u_1", "email": "a@b.c" }message User {
string id = 1;
}
id = "u_1"send(user)send(toProto(toDomain(user)))Unknown fields live on the decoded message. Forwarding it, or copying it with a message-aware clone or merge, keeps them. Rebuilding it field by field, or converting it to JSON, drops them.
Further Reading
- Unknown Fields in Protobuf
How each runtime preserves unknown fields, and the risks of forwarding them across a security boundary.
- Unknown fields in the official language guide
The reference semantics: what parsers must retain and how each runtime exposes it.
Next
Schema Evolution
Which Protobuf schema changes are safe and which break deployed clients: wire and JSON compatibility levels, plus the deprecate-and-reserve lifecycle for removing fields.