In this lesson, we learn how to use the map field type in Protocol Buffers, how it is defined in a .proto file, how code is generated from it, and how we interact with it from Java. The goal is to understand how Protobuf maps behave, how they relate to Java maps, and which helper methods Protobuf generates for us.
1. Defining Messages in the .proto File
We start by defining our schema. Suppose we want to model cars and a dealer that keeps an inventory of cars indexed by year.
Car message
The Car message contains basic properties:
- make – brand of the car (e.g., Honda, Toyota)
- model – specific model (e.g., Civic, Camry)
- year – manufacturing year
Example:
message Car {
string make = 1;
string model = 2;
int32 year = 3;
}
Dealer message with a map
Now we define a Dealer that maintains an inventory. Instead of a repeated list, we use a map so that we can quickly look up cars by year.
Key points about Protobuf maps:
mapmust be lowercase (map, notMap).- You must specify key and value types.
- The key must be a scalar type (like int32, string, etc.).
- The value can be a message type.
Example:
message Dealer {
map<int32, Car> inventory = 1;
}
Here:
- Key →
int32(car year) - Value →
Carmessage - Field name →
inventory
This means the dealer stores cars indexed by their year.
3. Creating Objects in Java
Creating Car objects
We use the builder pattern generated by Protobuf.
Car car1 = Car.newBuilder()
.setMake("Honda")
.setModel("Civic")
.setYear(2000)
.build();
Car car2 = Car.newBuilder()
.setMake("Toyota")
.setModel("Camry")
.setYear(2002)
.build();
Each object is immutable after build().
4. Creating Dealer and populating the map
The Dealer builder provides putInventory methods.
Dealer dealer = Dealer.newBuilder()
.putInventory(car1.getYear(), car1)
.putInventory(car2.getYear(), car2)
.build();
Important observations:
- Each entry is added using the key and value.
- You can call
putInventorymultiple times. - If you already have a Java map, you can use
putAllInventory.
5. Printing and Inspecting the Object
toString behavior
System.out.println(dealer);
Protobuf’s generated toString() prints map entries as key–value pairs, which is helpful for debugging.
Output
inventory {
key: 2000
value {
make: "Honda"
model: "Civic"
year: 2000
}
}
inventory {
key: 2002
value {
make: "Toyota"
model: "Camry"
year: 2002
}
}
5. Generated Helper Methods for Maps
Protobuf generates several useful methods for map fields.
5.1 Check if a key exists
dealer.containsInventory(2002);
Returns:
trueif the key existsfalseotherwise
This is equivalent to containsKey in Java maps.
5.2 Get the whole map
Map<Integer, Car> map = dealer.getInventoryMap();
This returns a Java map view.
Use this when:
- Iterating over entries
- Performing bulk operations
- Integrating with other APIs
5.3 Get value by key
Car car = dealer.getInventoryOrThrow(2002);
Behavior:
- Returns the car if present
- Throws an exception if not present
This is useful when the key must exist.
5.4 Get value with default
Car car = dealer.getInventoryOrDefault(2003, defaultCar);
Behavior:
- Returns the stored value if present
- Returns the provided default if absent
This prevents exceptions.
6. Example Checks
Example flow:
System.out.println(dealer.containsInventory(2002)); System.out.println(dealer.containsInventory(2003)); Car c = dealer.getInventoryOrThrow(2002); System.out.println(c.getModel());
Expected result:
- 2002 → exists →
true - 2003 → does not exist →
false - Model for 2002 → prints
"Camry"
7. Key Takeaways
- Protobuf maps behave very similarly to Java maps but are immutable after build.
- You must use builder methods (
put,putAll) to populate them. - Generated helper methods simplify checking, retrieving, and default handling.
- Maps are ideal when you need fast lookup instead of iterating through repeated lists.
- They are stored internally as repeated entries but exposed as maps in generated code.
