Protocol Buffers (aka “protobufs”) is another Google technology which integrates neatly with Java (and other languages). As their tag-lines says: “Protocol Buffers are language-neutral, platform-neutral extensible mechanisms for serializing structured data”.

For the sake of this example, we will look at 1) definition of a “proto” file; 2) integration with Bazel; 3) and use in a small Java test example.

Proto Message

At the heart of protobufs is the Message, which is an abstract language agnostic definition of a data class. As any class, the Message has fields, which can be of a few basic common native types or other Message types. Thus, any data structure can be realised.

For this example, let’s take the Person message from the official example. It comes with a few basic fields:

message Person {
  string firstname = 1;
  string lastname = 2;
  int32 id = 3;
  string email = 4;
}

The proto file is compiled using the protoc command line tool, which will produce language specific source code to depend on.

Bazel

For the sake of this example, we will use Bazel to build. It has native (more or less) support for protobufs. No extra dependencies are needed in the MODULE.bazel nor the BUILD file.

There are two rules to specify, one for the proto itself and one for the Java specific library. The latter will depend on the former:

proto_library(
    name = "person_proto",
    srcs = ["person.proto"],
)

java_proto_library(
    name = "person_java_proto",
    deps = [":person_proto"],
)

This can now be compiled using:

bazel build :person_java_proto

Use in Java code

The generated class will be available for import as any other class. Notice the package, which is defined specifically for Java at the top of the .proto file.

import com.rememberjava.protobuf.Person;

A proto message can be instantiated using its builder methods. The instance is immutable.

	Person person = Person.newBuilder()
	    .setFirstname("Bob")
	    .setLastname("Johnson")
	    .setEmail("bob@example.com")
	    .build();

The official example shows a slightly more complex structure, with nested objects:

Person john =
  Person.newBuilder()
    .setId(1234)
    .setName("John Doe")
    .setEmail("jdoe@example.com")
    .addPhones(
      Person.PhoneNumber.newBuilder()
        .setNumber("555-4321")
        .setType(Person.PhoneType.PHONE_TYPE_HOME)
        .build());
    .build();

Files from this example:

person.proto
GitHub Raw
syntax = "proto3";

option java_package = "com.rememberjava.protobuf";
option java_multiple_files = true;

message Person {
  string firstname = 1;
  string lastname = 2;
  int32 id = 3;
  string email = 4;
}
PersonTest.java
GitHub Raw
package com.rememberjava.protobuf;

import com.rememberjava.protobuf.Person;

import static org.junit.Assert.*;
import org.junit.Test;

public class PersonTest {

    @Test
    public void testPerson() {
	Person person = Person.newBuilder()
	    .setFirstname("Bob")
	    .setLastname("Johnson")
	    .setEmail("bob@example.com")
	    .build();

	assertEquals("Bob", person.getFirstname());
    }
}
BUILD
GitHub Raw
package(default_visibility = ["//visibility:public"])

proto_library(
    name = "person_proto",
    srcs = ["person.proto"],
)

java_proto_library(
    name = "person_java_proto",
    deps = [":person_proto"],
)

java_test(
    name = "PersonTest",
    size = "small",
    srcs = ["PersonTest.java"],
    deps = [
        ":person_java_proto",
    ],
)