How to Use Protocol Buffers in Java

A practical guide to working with Protocol Buffers in Java applications

Published: January 2025 • 9 min read

Protocol Buffers works great with Java. Whether you're building microservices, Android apps, or backend systems, protobuf provides efficient data serialization that's faster and more compact than JSON or XML.

This guide shows you how to set up and use Protocol Buffers in Java. We'll start from scratch and build a working example that you can use in your own projects.

What You'll Need

  • Java JDK: Version 8 or higher (download from oracle.com)
  • Maven or Gradle: For dependency management (we'll use Maven in this guide)
  • IDE: IntelliJ IDEA, Eclipse, or any Java IDE
  • Basic Java knowledge: Understanding of classes and Maven projects

Step 1: Create a Maven Project

Create a new Maven project with this structure:

protobuf-java-example/
├── pom.xml
└── src/
    └── main/
        ├── java/
        │   └── com/example/
        └── proto/
            └── person.proto

You can create this manually or use Maven command:

mvn archetype:generate -DgroupId=com.example -DartifactId=protobuf-java-example -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false

Step 2: Configure pom.xml

Update your pom.xml file to include Protocol Buffers:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>protobuf-java-example</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <protobuf.version>3.25.0</protobuf.version>
    </properties>

    <dependencies>
        <!-- Protocol Buffers -->
        <dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java</artifactId>
            <version>${protobuf.version}</version>
        </dependency>
    </dependencies>

    <build>
        <extensions>
            <extension>
                <groupId>kr.motd.maven</groupId>
                <artifactId>os-maven-plugin</artifactId>
                <version>1.7.1</version>
            </extension>
        </extensions>
        <plugins>
            <!-- Protobuf Maven Plugin -->
            <plugin>
                <groupId>org.xolstice.maven.plugins</groupId>
                <artifactId>protobuf-maven-plugin</artifactId>
                <version>0.6.1</version>
                <configuration>
                    <protocArtifact>
                        com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}
                    </protocArtifact>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

This configuration automatically compiles .proto files to Java classes when you build the project.

Step 3: Create Your .proto File

Create src/main/proto/person.proto:

syntax = "proto3";

package com.example.proto;

option java_package = "com.example.proto";
option java_outer_classname = "PersonProto";

message Person {
  string name = 1;
  int32 id = 2;
  string email = 3;
  repeated string phone_numbers = 4;
  
  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }
  
  message PhoneNumber {
    string number = 1;
    PhoneType type = 2;
  }
  
  repeated PhoneNumber phones = 5;
}

message AddressBook {
  repeated Person people = 1;
}

Important options explained:

  • java_package - Sets the Java package name
  • java_outer_classname - Sets the outer class name for the generated code

Step 4: Compile the Project

Run Maven to generate Java classes from your .proto file:

mvn clean compile

This generates Java classes in target/generated-sources/protobuf/java/. Your IDE should automatically recognize them.

You'll now have access to PersonProto.Person andPersonProto.AddressBook classes.

Step 5: Use Protocol Buffers in Your Code

Create src/main/java/com/example/Main.java:

package com.example;

import com.example.proto.PersonProto.Person;
import com.example.proto.PersonProto.AddressBook;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class Main {
    public static void main(String[] args) {
        try {
            // Create a Person using Builder pattern
            Person person = Person.newBuilder()
                .setName("John Doe")
                .setId(1234)
                .setEmail("john.doe@example.com")
                .addPhoneNumbers("555-1234")
                .addPhoneNumbers("555-5678")
                .addPhones(
                    Person.PhoneNumber.newBuilder()
                        .setNumber("555-9999")
                        .setType(Person.PhoneType.MOBILE)
                        .build()
                )
                .build();

            System.out.println("Created Person:");
            System.out.println("Name: " + person.getName());
            System.out.println("ID: " + person.getId());
            System.out.println("Email: " + person.getEmail());

            // Serialize to byte array
            byte[] binaryData = person.toByteArray();
            System.out.println("\nSerialized to " + binaryData.length + " bytes");

            // Deserialize from byte array
            Person deserializedPerson = Person.parseFrom(binaryData);
            System.out.println("\nDeserialized Person:");
            System.out.println("Name: " + deserializedPerson.getName());
            System.out.println("ID: " + deserializedPerson.getId());

            // Save to file
            try (FileOutputStream output = new FileOutputStream("person.bin")) {
                person.writeTo(output);
                System.out.println("\nSaved to person.bin");
            }

            // Read from file
            try (FileInputStream input = new FileInputStream("person.bin")) {
                Person personFromFile = Person.parseFrom(input);
                System.out.println("\nRead from file: " + personFromFile.getName());
            }

            // Create an AddressBook with multiple people
            AddressBook addressBook = AddressBook.newBuilder()
                .addPeople(person)
                .addPeople(
                    Person.newBuilder()
                        .setName("Jane Smith")
                        .setId(5678)
                        .setEmail("jane.smith@example.com")
                        .build()
                )
                .build();

            System.out.println("\nAddress book has " + 
                addressBook.getPeopleCount() + " people");

        } catch (IOException e) {
            System.err.println("Error: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

Step 6: Run Your Application

Compile and run:

mvn clean package
mvn exec:java -Dexec.mainClass="com.example.Main"

You should see output like:

Created Person:
Name: John Doe
ID: 1234
Email: john.doe@example.com

Serialized to 58 bytes

Deserialized Person:
Name: John Doe
ID: 1234

Saved to person.bin

Read from file: John Doe

Address book has 2 people

Common Operations

Check if Field is Set

if (person.hasEmail()) {
    System.out.println("Email: " + person.getEmail());
}

Clear a Field

Person updatedPerson = person.toBuilder()
    .clearEmail()
    .build();

Merge Two Messages

Person merged = person1.toBuilder()
    .mergeFrom(person2)
    .build();

Convert to JSON

import com.google.protobuf.util.JsonFormat;

String json = JsonFormat.printer()
    .includingDefaultValueFields()
    .print(person);
System.out.println(json);

Parse from JSON

String json = "{\"name\": \"Bob\", \"id\": 9999}";
Person.Builder builder = Person.newBuilder();
JsonFormat.parser().merge(json, builder);
Person person = builder.build();

Working with Repeated Fields

Repeated fields in protobuf work like lists in Java:

// Add items one by one
Person person = Person.newBuilder()
    .addPhoneNumbers("555-1111")
    .addPhoneNumbers("555-2222")
    .build();

// Add all from a list
List<String> numbers = Arrays.asList("555-3333", "555-4444");
person = person.toBuilder()
    .addAllPhoneNumbers(numbers)
    .build();

// Get count
int count = person.getPhoneNumbersCount();

// Get by index
String firstNumber = person.getPhoneNumbers(0);

// Iterate through all
for (String number : person.getPhoneNumbersList()) {
    System.out.println(number);
}

Best Practices for Java

Use Builder Pattern

Always use the Builder pattern to create protobuf messages. Generated classes are immutable, so you need builders to construct or modify them.

Check Field Presence with has Methods

Use hasFieldName() methods to check if optional fields are set before accessing them.

Handle IOException

Parsing and serialization can throw IOException. Always handle these exceptions properly in production code.

Use Descriptors for Reflection

If you need runtime reflection, use the descriptor API:person.getDescriptorForType()

Using Protobuf in Android

For Android projects, use the Lite version for smaller APK size:

// In your build.gradle
dependencies {
    implementation 'com.google.protobuf:protobuf-javalite:3.25.0'
}

// In your .proto file
option java_multiple_files = true;
option optimize_for = LITE_RUNTIME;

The Lite version excludes reflection and descriptors, making it much smaller and faster for mobile apps.

Common Issues

Issue: Generated classes not found

Solution: Run mvn clean compile. Make sure your IDE has indexed the target/generated-sources/ folder.

Issue: Plugin not working on Windows

Solution: Make sure you have the os-maven-plugin extension in your pom.xml. This detects your operating system for the correct protoc binary.

Issue: InvalidProtocolBufferException when parsing

Solution: This usually means you're trying to parse data with a different schema. Make sure the .proto file used to serialize matches the one used to deserialize.

Related Tools

Conclusion

Protocol Buffers is well-supported in Java with excellent tooling and Maven integration. The generated code is clean, type-safe, and easy to work with using the builder pattern.

Start with simple messages like we did in this guide, then expand to more complex schemas as needed. The performance benefits and strong typing make protobuf a solid choice for Java applications that need efficient serialization.