oneof fields in Protocol Buffers are a way to declare that a message can have at most one of a set of fields set at any given time, but only one.

Let’s see this in action. Imagine you’re building a system to represent different kinds of user events.

message UserEvent {
  string event_id = 1;
  oneof event_type {
    UserCreated user_created = 2;
    UserLoggedIn user_logged_in = 3;
    UserLoggedOut user_logged_out = 4;
  }
}

message UserCreated {
  string user_id = 1;
  string username = 2;
}

message UserLoggedIn {
  string user_id = 1;
  string ip_address = 2;
  int64 login_timestamp = 3;
}

message UserLoggedOut {
  string user_id = 1;
  int64 logout_timestamp = 4;
}

Here, UserEvent can be either a user_created event, a user_logged_in event, or a user_logged_out event, but never more than one. If you set user_created, any previously set user_logged_in or user_logged_out field is automatically cleared.

The problem oneof solves is ensuring data integrity and simplifying message handling. Without oneof, you might have a message with three separate optional fields: UserCreated user_created = 2; UserLoggedIn user_logged_in = 3; UserLoggedOut user_logged_out = 4;. In this scenario, your application logic would have to rigorously check that only one of these fields is populated, which is error-prone. A oneof declaration enforces this constraint at the serialization and deserialization level, making your code cleaner and safer.

Internally, the protobuf runtime handles oneof by keeping track of which field within the oneof group is currently set. When you serialize a message, it only includes the data for the one field that’s active. When you deserialize, it knows which field to expect.

The exact levers you control are the types of events you define within the oneof and the fields within those nested message types. For example, in the UserEvent above, you can set event_id independently of the oneof fields, as it’s not part of the event_type group.

When you’re working with generated code, the oneof typically manifests as a way to check which field is set and a way to access its value. For instance, in Python, you might have something like this:

event = UserEvent(event_id="evt-123")
event.user_created.user_id = "user-abc"
event.user_created.username = "alice"

# Now, if you try to set user_logged_in, user_created is cleared
event.user_logged_in.user_id = "user-abc"
event.user_logged_in.ip_address = "192.168.1.1"
event.user_logged_in.login_timestamp = 1678886400

print(event.HasField("user_created")) # This will be False
print(event.HasField("user_logged_in")) # This will be True

# To get the value:
if event.HasField("user_logged_in"):
    login_info = event.user_logged_in
    print(f"User logged in: {login_info.user_id} from {login_info.ip_address}")

A common pitfall is forgetting that setting one field in a oneof clears any other field that was previously set within that same oneof. This isn’t an error; it’s the defining behavior. If you intended to have multiple event types associated with a single event_id and wanted them all to be present, oneof is the wrong tool. You’d simply use separate, non-oneof optional fields in that case.

The next concept you’ll likely encounter is how to handle oneof fields in repeated fields, which allows a message to contain a list of items where each item can be one of several different types.

Want structured learning?

Take the full Protobuf course →