Skip to content

The Evolution of Required

The required keyword is gone in proto3. Dropping it was deliberate. Schemas needed to evolve safely without breaking backward compatibility, and required got in the way of that.

Why was it removed?

If a field is marked required, it must be present in every message. If you later decide to stop sending that field, every older client in the world will crash when they try to decode the new message. Required fields are considered harmful for long-term schema evolution.

What to do instead

  • Application Validation: Use generated getters that return zero values if the field is missing (e.g., Go's GetField()) and perform null checks in your business logic.

  • Metadata Validation: Declare constraints in the schema as custom options and enforce them when a message is validated rather than when it is decoded, so the field stays wire compatible. protovalidate is the usual implementation; since the mechanism is just options, you can define and check your own instead.

Metadata validation
import "buf/validate/validate.proto";

message CreateUserRequest {
  // Required at the validation layer
  // but optional at the wire layer.
  string email = 1 [
    (buf.validate.field).string.email = true,
    (buf.validate.field).required = true
  ];
}
Application validation (Go)
// Safe access even if req is nil
if req.GetEmail() == "" {
    return status.Error(InvalidArgument, "email is required")
}

Further Reading

Next

Size Limits

Practical size limits for Protobuf messages: the 2GB hard ceiling, recursion and memory behavior, and how to design schemas for large payloads.