In Java, we commonly use interfaces and abstract classes to achieve polymorphism. A base type defines a contract, and multiple implementations provide different behaviors. For example, a Car interface might have a drive() method, and classes like SportsCar and LuxuryCar implement it differently. At runtime, the behavior depends on the concrete type passed.
Protocol Buffers approach this idea differently because Protobuf is about data modeling, not behavior. There are no methods to override or behaviors to customize. Instead, Protobuf provides a mechanism to model polymorphic data — and that mechanism is called oneof.
Conceptual Understanding of oneof
oneof allows you to define a field that can hold exactly one of several possible message types.
You can think of it like:
- A union type
- A tagged variant
- Or a data-oriented version of polymorphism
The key guarantee is:
Only one of the fields inside a
oneofcan be set at any time.
This is not about behavior like Java polymorphism; it is about representing data that can exist in multiple alternative forms.
Real-World Motivation
Consider login credentials. A user might log in using:
- Email + password
- Phone number + OTP
Both represent “credentials,” but their structures differ. Instead of forcing a single structure with many optional fields, oneof lets you model mutually exclusive data clearly.
Another example is server responses:
- Success response
- Error response
- Validation failure
- Authorization failure
You know you will receive a response, but its form varies. oneof models this cleanly.
Defining Messages
First, define the possible data shapes.
Email Credentials
message Email {
string address = 1;
string password = 2;
}
Phone Credentials
message Phone {
string number = 1;
string code = 2;
}
Each message can have any number of fields. They do not need to match each other structurally.
Defining the oneof
Now define a wrapper message that uses oneof.
message Credentials {
oneof login_method {
Email email = 1;
Phone phone = 2;
}
}
This says:
- A
Credentialsobject contains one login method - That method is either email or phone
- Never both at the same time
The name login_method is just a label for the group. You can choose any meaningful name.
Generated Code Behavior
Even with oneof, Protobuf still generates a normal final class. It does not become an interface or abstract class. However, Protobuf automatically generates:
- A case enum
- Helper methods to detect which field is set
For example:
credentials.getLoginMethodCase();
This returns an enum like:
- PHONE
- LOGINMETHOD_NOT_SET
This enum is your runtime indicator of which variant is present.
Handling oneof in Java
A common pattern is a switch statement.
switch (credentials.getLoginMethodCase()) {
case EMAIL -> {
Email email = credentials.getEmail();
// process email
}
case PHONE -> {
Phone phone = credentials.getPhone();
// process phone
}
case LOGINMETHOD_NOT_SET -> {
// handle missing data
}
}
This gives type-safe branching based on actual data.
Creating Instances
Email Credentials
Email email = Email.newBuilder()
.setAddress("sam@gmail.com")
.setPassword("admin")
.build();
Credentials cred = Credentials.newBuilder()
.setEmail(email)
.build();
Phone Credentials
Phone phone = Phone.newBuilder()
.setNumber("123456789")
.setCode("123")
.build();
Credentials cred = Credentials.newBuilder()
.setPhone(phone)
.build();
You must wrap them inside Credentials. You cannot pass Email where Credentials is expected.
What If You Set Multiple Fields?
This is an important rule.
If you do:
Credentials.newBuilder()
.setEmail(email)
.setPhone(phone)
.build();
You might expect an error, but Protobuf allows it.
However:
The last field set wins.
So in this case:
phoneoverwritesemail- Only
phoneremains set
If you reverse the order, email wins.
This overwrite behavior is by design and ensures the oneof guarantee remains intact.
Why oneof Is Powerful
oneof provides several advantages:
- Clear modeling of mutually exclusive data
- Smaller serialized size
- Strong data validation by schema
- Cleaner API contracts
- Better documentation of intent
Instead of many optional fields with unclear rules, oneof makes exclusivity explicit.
Complete Working Program
Proto file
syntax = "proto3";
option java_package = "com.example.protobuf";
option java_multiple_files = true;
package demo;
// Email credential type
message Email {
string address = 1;
string password = 2;
}
// Phone credential type
message Phone {
string number = 1;
string code = 2;
}
// Wrapper with oneof
message Credentials {
oneof login_method {
Email email = 1;
Phone phone = 2;
}
}
Java Program
package org.learnitweb;
import com.example.protobuf.Credentials;
import com.example.protobuf.Email;
import com.example.protobuf.Phone;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
//TIP To <b>Run</b> code, press <shortcut actionId="Run"/> or
// click the <icon src="AllIcons.Actions.Execute"/> icon in the gutter.
public class Main {
private static final Logger logger = LoggerFactory.getLogger(Main.class);
public static void main(String[] args) {
// Build Email
Email email = Email.newBuilder()
.setAddress("sam@gmail.com")
.setPassword("admin")
.build();
// Build Phone
Phone phone = Phone.newBuilder()
.setNumber("1234567890")
.setCode("9999")
.build();
// Credentials with Email
Credentials emailCred = Credentials.newBuilder()
.setEmail(email)
.build();
// Credentials with Phone
Credentials phoneCred = Credentials.newBuilder()
.setPhone(phone)
.build();
// Test login handling
login(emailCred);
login(phoneCred);
// Demonstrate "last one wins"
Credentials both = Credentials.newBuilder()
.setEmail(email)
.setPhone(phone) // overwrites email
.build();
login(both);
}
private static void login(Credentials cred) {
switch (cred.getLoginMethodCase()) {
case EMAIL -> {
System.out.println("Email login:");
System.out.println("Address: " + cred.getEmail().getAddress());
}
case PHONE -> {
System.out.println("Phone login:");
System.out.println("Number: " + cred.getPhone().getNumber());
}
case LOGINMETHOD_NOT_SET -> {
System.out.println("No credentials provided.");
}
}
System.out.println("----");
}
}
Output
Email login: Address: sam@gmail.com ---- Phone login: Number: 1234567890 ---- Phone login: Number: 1234567890 ----
