Core Java

Java Protobuf Maps Example

Protocol Buffers (Protobuf) is a language-neutral, platform-neutral extensible mechanism for serializing structured data. Let us delve into understanding how Java Protobuf maps work and how they enable efficient key-value pair representation within message definitions.

1. Understanding Maps in Protobuf

Protobuf (Protocol Buffers), developed by Google, supports the use of maps to represent key-value pairs directly within message definitions. This makes it easier to work with associative arrays or dictionaries when designing your schema.

A map field in a Protobuf message is declared using the following syntax:

map<key_type, value_type> map_field = N;

The key_type must be a scalar type such as int32, int64, uint32, uint64, bool, or string. Notably, floating-point numbers (float, double) and composite types (such as other messages or enums) are not permitted as map keys due to serialization concerns. The value_type can be any valid Protobuf type, including scalar types (like int32, string, etc.) and complex types (like nested message types). This allows you to structure your data more richly when needed.

2. Implementing Maps in Protobuf

Let us delve into understanding how Java Protobuf maps can be implemented to manage structured key-value data efficiently.

2.1 Define the Protobuf File

The following is a simple .proto file that defines an AddressBook message with a map from email strings to `Person` messages.

// address_book.proto

syntax = "proto3";

option java_package = "com.example.protobuf";
option java_outer_classname = "AddressBookProto";

// A map field named 'contacts' mapping email (string) to Person
message AddressBook {
  map<string, Person> contacts = 1;
}

message Person {
  string name = 1;
  int32 age = 2;
}

In the definition above, map<string, Person> contacts = 1; creates a field that maps email IDs to Person entries, and Person is a nested message that stores name and age details.

2.1.1 Integrating Protobuf with Maven

To compile .proto files automatically during the Maven build, you can use the protobuf-maven-plugin along with the os-maven-plugin to handle platform-specific settings.

2.1.1.1 Add dependencies to your pom.xml
<project>
  ...
  <dependencies>
    <!-- Protobuf runtime for Java -->
    <dependency>
      <groupId>com.google.protobuf</groupId>
      <artifactId>protobuf-java</artifactId>
      <version>3.25.1</version>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <!-- Plugin to compile .proto files -->
      <plugin>
        <groupId>org.xolstice.maven.plugins</groupId>
        <artifactId>protobuf-maven-plugin</artifactId>
        <version>0.6.1</version>
        <configuration>
          <protocArtifact>com.google.protobuf:protoc:3.25.1:exe:${os.detected.classifier}</protocArtifact>
        </configuration>
        <executions>
          <execution>
            <goals>
              <goal>compile</goal>
              <goal>compile-custom</goal>
            </goals>
          </execution>
        </executions>
      </plugin>

      <!-- Detect OS for protoc binary -->
      <plugin>
        <groupId>kr.motd.maven</groupId>
        <artifactId>os-maven-plugin</artifactId>
        <version>1.7.0</version>
        <executions>
          <execution>
            <goals>
              <goal>detect</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>
2.1.1.2 Organize your .proto files

Put your proto files in the following standard Maven path:

src/main/proto/address_book.proto
2.1.1.3 Build the project

To generate the Java code from proto files, run:

mvn clean install

This compiles the .proto files and places the generated code in:

target/generated-sources/protobuf/java

These files are added automatically to your build classpath.

2.1.1.4 Step 4: Use the Generated Classes

Now you can use the generated classes like AddressBookProto.AddressBook and AddressBookProto.Person in your Java application, as shown earlier in section 2.3.

2.2 Compile the Protobuf File

To generate Java classes from the .proto file, use the Protocol Buffers compiler (protoc):

protoc --java_out=. address_book.proto

This generates AddressBookProto.java inside the specified package structure, containing classes for AddressBook and Person.

2.3 Java Code to Use the Map

Here’s how you can create and use the contacts map field in Java:

import com.example.protobuf.AddressBookProto.AddressBook;
import com.example.protobuf.AddressBookProto.Person;

import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.util.Map;

public class ProtobufMapDemo {

    public static void main(String[] args) throws Exception {
        // Create person entries
        Person alice = Person.newBuilder().setName("Alice").setAge(30).build();
        Person bob = Person.newBuilder().setName("Bob").setAge(25).build();

        // Create address book with a map
        AddressBook.Builder bookBuilder = AddressBook.newBuilder();
        bookBuilder.putContacts("alice@example.com", alice);
        bookBuilder.putContacts("bob@example.com", bob);
        AddressBook book = bookBuilder.build();

        // Serialize to binary file
        try (FileOutputStream fos = new FileOutputStream("addressbook.bin")) {
            book.writeTo(fos);
        }

        // Deserialize from binary file
        AddressBook readBook;
        try (FileInputStream fis = new FileInputStream("addressbook.bin")) {
            readBook = AddressBook.parseFrom(fis);
        }

        // Print the entries
        for (Map.Entry<String, Person> entry : readBook.getContactsMap().entrySet()) {
            System.out.println("Email: " + entry.getKey());
            System.out.println("Name: " + entry.getValue().getName());
            System.out.println("Age: " + entry.getValue().getAge());
            System.out.println("---");
        }
    }
}

2.3.1 Code Explanation

The provided Java code demonstrates how to use maps in Protobuf by creating an address book application. It begins by creating two Person objects, Alice and Bob, each with a name and age. These are then added to a Map<String, Person> field in the AddressBook using email IDs as keys. The address book is then serialized to a binary file named addressbook.bin. Later, this file is deserialized back into an AddressBook object. Finally, the code iterates through the contacts map and prints out each person’s email, name, and age to the console.

2.3.2 Code Output

When we run the above program, the output would be:

Email: alice@example.com
Name: Alice
Age: 30
---
Email: bob@example.com
Name: Bob
Age: 25

This confirms that the map field in Protobuf successfully stores and retrieves key-value pairs with custom message types. The program first serializes the AddressBook containing the map of email addresses to Person objects into a binary file using Protobuf’s writeTo method. It then reads the binary file using parseFrom, reconstructing the original AddressBook structure. The loop through the contacts map shows that all data is preserved accurately, verifying Protobuf’s ability to handle nested messages inside maps effectively and efficiently.

3. Conclusion

Protobuf maps are a powerful feature to represent key-value pairs compactly. In this article, we saw how to define maps in .proto files, use them in Java, and serialize/deserialize data with ease. This pattern is especially useful in modern systems where configuration, state, or user-defined data is naturally key-based.

Yatin Batra

An experience full-stack engineer well versed with Core Java, Spring/Springboot, MVC, Security, AOP, Frontend (Angular & React), and cloud technologies (such as AWS, GCP, Jenkins, Docker, K8).
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button