Skip to content
Unknown Fields and Well-Known Types
step 1/5

Reading — step 1 of 5

Read

~1 min readCode Generation

Unknown Fields and Well-Known Types

Forward compatibility via unknown-field preservation

When a parser sees a field number not in its schema, it must NOT drop it. It records the raw bytes (tag + value) in an "unknown fields" set and re-emits them on the next serialize. That is the mechanism that makes forward compatibility actually work: an old binary can round-trip a new message without losing the fields it doesn't understand.

In Python and Java this is automatic. In Go it requires the protoreflect machinery; in older language stubs (some C++ lite runtimes, some embedded generators) unknown-field preservation is explicitly OFF for size reasons — a known production gotcha.

Default values vs missing fields (proto3)

In proto3, scalar fields with the default value (0, "", false, b"") are NOT serialized. The receiver cannot distinguish "field was set to 0" from "field was never set". That ambiguity is why:

  • oneof exists (gives explicit presence to bundled fields).
  • optional was re-added in proto3.3+ (optional int32 score = 1;).
  • The well-known wrapper types (google.protobuf.Int32Value, etc.) exist — they're message types, so presence is explicit via the LEN wire wrapper.

Well-known types

google.protobuf.* ships with a small set of canonical messages every implementation supports:

TypeUse
Timestampseconds (int64) + nanos (int32)
Durationsame fields, semantic = span
Anytype_url + value (packed payload)
Emptyzero-field marker (for RPCs with no body)
Struct/Value/ListValuedynamic JSON-like data
FieldMasklist of dotted paths (partial-update selector)
Int32Value, StringValue, ...nullable scalar wrappers

Use them instead of inventing your own. Code generators, JSON conversion, and SDK utilities all special-case these names.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…