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 namejava_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.