Learnitweb

Using map in Protocol Buffers

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:

  • map must be lowercase (map, not Map).
  • 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 → Car message
  • 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 putInventory multiple 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:

  • true if the key exists
  • false otherwise

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.