Skip to Content
Course content

257: Working with Protocol Buffers in Java

Click on the "Edit" button in the top corner of the screen to edit your slide content.

How do I actually turn a .proto file into Java classes?

You don't write the Java classes yourself—that's the whole point. If you're manually writing POJOs to match a specification, you're doing it the hard way. Instead, you define your data structure in a .proto file and use the protoc compiler to generate the Java source code for you.

Let's say we're building a system to track player stats for an RPG. Your player.proto file would look like this:

syntax = "proto3";

option java_package = "com.game.stats";
option java_multiple_files = true;

message PlayerProfile {
  int32 id = 1;
  string username = 2;
  int32 level = 3;
  repeated string inventory_items = 4;
}

To get this into Java, I highly recommend using a build plugin (like the protobuf-maven-plugin or the Gradle equivalent) rather than running the protoc command manually every time you change a field. Once you run your build, the plugin generates a PlayerProfile class that handles all the heavy lifting of serialization and deserialization. It's a bit of a "magic" step at first, but it ensures that your Java code and your data contract are always in perfect sync.

Why can't I just use a constructor to create a Protobuf object?

If you try to call new PlayerProfile(), you'll find that the constructor is private. This trips up a lot of people coming from standard Java beans. Protobuf objects in Java are immutable; once they're created, they can't be changed. To build one, you have to use the Builder pattern.

Here is how I usually handle this in my services:

PlayerProfile player = PlayerProfile.newBuilder()
    .setId(101)
    .setUsername("ShadowKnight")
    .setLevel(25)
    .addInventoryItems("Steel Sword")
    .addInventoryItems("Health Potion")
    .build();

I actually prefer this approach over standard constructors because it makes the code much more readable when you're dealing with messages that have twenty or thirty optional fields. You only set what you need, and the builder handles the defaults for everything else.

How do I send this data over the wire or save it to a disk?

The real magic of Protobuf is that it doesn't store data as human-readable text like JSON; it stores it as a compact binary stream. This is why it's so much faster and smaller.

To turn your Java object into a byte array for transmission, you use toByteArray(). To bring it back to life on the receiving end, you use the static parseFrom() method. Look how clean this is:

// Serializing to bytes (sending)
byte[] data = player.toByteArray();

// Deserializing from bytes (receiving)
try {
    PlayerProfile decodedPlayer = PlayerProfile.parseFrom(data);
    System.out.println("Welcome back, " + decodedPlayer.getUsername());
} catch (InvalidProtocolBufferException e) {
    // This happens if the data is corrupted or not a PlayerProfile
    e.printStackTrace();
}

In a production environment, you'd typically stream these bytes over a TCP socket or save them to a binary file. Just remember: since it's binary, you can't just open the resulting file in a text editor to see what's inside. You'll need your .proto file and the generated classes to make sense of the data.

What happens if I need to add a new field to the message later?

This is where Protobuf beats JSON and XML. It's designed for "schema evolution." If you decide that your PlayerProfile needs a guild_name field, you just add it to the .proto file with a new unique tag number:

message PlayerProfile {
  // ... existing fields ...
  string guild_name = 5; 
}

Because every field is identified by its number (the = 5 part) rather than its name, old code that doesn't know about field #5 will simply ignore it when parsing. New code that expects field #5 will see it's missing in old messages and just use a default value (like an empty string). As long as you never change the tag numbers of existing fields, your system won't crash when you deploy a new version of your service. It's a lifesaver for maintaining backward compatibility in distributed systems.




📋 Practical Task

Exercise: Implementing a Persistent RPG Inventory System

Your task is to create a small Java application that simulates saving and loading a player's inventory using Protocol Buffers.

  1. Define the Schema: Create a .proto file defining an InventoryItem message with the following fields: itemId (int32), itemName (string), quantity (int32), and isQuestItem (bool).
  2. Implement the Saver: Write a Java method that takes a list of InventoryItem objects, wraps them in a parent PlayerInventory message (which should contain a repeated InventoryItem items field), and writes the serialized bytes to a file named inventory.bin.
  3. Implement the Loader: Write a Java method that reads inventory.bin from the disk and parses it back into a PlayerInventory object.
  4. Verification: In your main method, create three different items, save them to the file, clear your local variables, load the data back from the file, and print the names of the loaded items to the console to verify they match.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.