1. Introduction
In this tutorial, we move to the next important concept in Protocol Buffers: composition, where one message type contains another message type as a field. This is very similar to object composition in Java, where one class holds a reference to another class.
2. What is Composition in Protobuf?
Composition in Protobuf means:
- One message type is used as a field inside another message type.
- This allows us to model real-world relationships more naturally, such as Student → Address or School → Address.
- It promotes reuse, because the same message type (like Address) can be embedded in multiple other messages.
For example:
- A
Studenthas anAddress. - A
Schoolalso has anAddress.
Instead of repeating street/city/state fields everywhere, we define them once in an Address message and reuse it.
3. Defining the .proto File
Create a file named composition.proto. Use Proto3 syntax and a package as usual.
composition.proto
syntax = "proto3";
package section03;
option java_multiple_files = true;
option java_package = "com.learnitweb.example03";
option java_outer_classname = "CompositionProto";
// Address message
message Address {
string street = 1;
string city = 2;
string state = 3;
}
// Student message
message Student {
string name = 1;
Address address = 2;
}
// School message
message School {
int32 id = 1;
string name = 2;
Address address = 3;
}
3) Understanding Field Numbers (Tags)
Each field in a Protobuf message has a field number (tag).
Important rules:
- Field numbers must be unique within the same message.
- Different messages can reuse the same numbers without any problem.
- For example,
street = 1in Address andname = 1in Student are perfectly valid because they belong to different messages. - But inside a single message, you cannot have two fields with the same number.
So this is valid:
message Address {
string street = 1;
string city = 2;
string state = 3;
}
message Student {
string name = 1;
Address address = 2;
}
But this is invalid:
message Address {
string street = 1;
string city = 1; // ❌ Not allowed
}
4. Java Example: Using Composition
package org.learnitweb;
import com.learnitweb.example03.Address;
import com.learnitweb.example03.Student;
import com.learnitweb.example03.School;
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 Address
Address address = Address.newBuilder()
.setStreet("123 Main Street")
.setCity("Atlanta")
.setState("Georgia")
.build();
// Build Student
Student student = Student.newBuilder()
.setName("Sam")
.setAddress(address)
.build();
// Build School with a different address
Address schoolAddress = Address.newBuilder()
.setStreet("234 Main Street")
.setCity("Atlanta")
.setState("Georgia")
.build();
School school = School.newBuilder()
.setId(1)
.setName("High School")
.setAddress(schoolAddress)
.build();
// Print
System.out.println("Student:");
System.out.println(student);
System.out.println("\nSchool:");
System.out.println(school);
}
}
When you run this:
- Student and School objects will print with nested Address data.
- You will see how Protobuf automatically formats nested messages.
Output
Student:
name: "Sam"
address {
street: "123 Main Street"
city: "Atlanta"
state: "Georgia"
}
School:
id: 1
name: "High School"
address {
street: "234 Main Street"
city: "Atlanta"
state: "Georgia"
}
5. build() vs buildPartial()
You might notice buildPartial() in the API.
Here is the key difference:
build()
- This is what you should use in Proto3.
- It creates a fully built message.
- Proto3 does not support required fields, so this is safe and standard.
buildPartial()
- This is mostly from Proto2 legacy behavior.
- In Proto2, required fields existed, and build() could throw exceptions if required fields were missing.
- buildPartial() allowed building even when required fields were not set.
Since Proto3 removed required fields:
- You should ignore
buildPartial()in modern Proto3 usage. - Use
build()only.
6. Accessing Nested Fields
Accessing nested fields is just like Java object access.
Example:
String street = student.getAddress().getStreet();
System.out.println("Student street: " + street);
This works exactly like:
student.getAddress().getStreet();
So if you know Java object composition, Protobuf composition will feel natural.
