Learnitweb

Protocol Buffers Composition

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 Student has an Address.
  • A School also has an Address.

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 = 1 in Address and name = 1 in 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.