Serialization and Deserialization using Protocol Buffers

What are Protocol Buffers?

Protocol buffers (also referred to as Protobuf) are Google's language-neutral, platform-neutral, extensible mechanism for serializing structured data – think XML, but smaller, faster, and simpler. You define how you want your data to be structured once, then you can use special generated source code to easily write and read your structured data to and from a variety of data streams and using a variety of languages.

What is Protocol Buffers used for?

Protocol buffers provide a language-neutral, platform-neutral, extensible mechanism for serializing structured data in a forward-compatible and backward-compatible way. It's like JSON, except it's smaller and faster, and it generates native language bindings.

What makes Protocol Buffers faster than JSON or XML ?

Protobuf has a defined format. It has data-types and other required keywords which are not needed to be encoded/decoded during the serialization/deserialization. So there is faster serialization and deserialization and even lesser content generated for protobuf. JSON provides only curly brackets and colons as inbuilt characters and same applies to XML too. Rest all needs to be encoded.


For this article, we will use Java to serialize and deserialize objects using Protobuf.

Project Setup

Create a maven project and open it in any IDE of your preference (Intellij , Eclipse, VSCode, etc.)

In the pom.xml , add the following dependencies and maven plugins.

pom.xml

<dependencies>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-protobuf</artifactId>
            <version>1.47.0</version>
        </dependency>
        <dependency> <!-- necessary for Java 9+ -->
            <groupId>org.apache.tomcat</groupId>
            <artifactId>annotations-api</artifactId>
            <version>6.0.53</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.13.3</version>
        </dependency>
    </dependencies>

    <build>
        <extensions>
            <extension>
                <groupId>kr.motd.maven</groupId>
                <artifactId>os-maven-plugin</artifactId>
                <version>1.6.2</version>
            </extension>
        </extensions>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>11</source>
                    <target>11</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.xolstice.maven.plugins</groupId>
                <artifactId>protobuf-maven-plugin</artifactId>
                <version>0.5.1</version>
                <configuration>
                    <protocArtifact>
                        com.google.protobuf:protoc:3.19.2:exe:${os.detected.classifier}
                    </protocArtifact>
                    <pluginId>grpc-java</pluginId>
                    <pluginArtifact>
                        io.grpc:protoc-gen-grpc-java:1.47.0:exe:${os.detected.classifier}
                    </pluginArtifact>
                    <protoSourceRoot>
                        ${basedir}/src/main/proto/
                    </protoSourceRoot>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>compile-custom</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>com.github.os72</groupId>
                <artifactId>protoc-jar-maven-plugin</artifactId>
                <version>3.11.4</version>
                <executions>
                    <execution>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>run</goal>
                        </goals>
                        <configuration>
                            <protocVersion>3.0.0</protocVersion> <!-- 2.4.1, 2.5.0, 2.6.1, 3.0.0 -->
                            <includeDirectories>
                                <include>src/main/resources/protobuf</include>
                            </includeDirectories>
                            <inputDirectories>
                                <include>src/main/resources/protobuf/</include>
                            </inputDirectories>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

${basedir}/src/main/proto/ is used to specify that all the proto files should be placed in this directory. You can change it to whichever location you wish, but this is a standard practice by the community.

Create a proto file.

Lets create out first simple proto file srudent.proto containing 2 fields - name and age. Make sure to place it under /src/main/proto/ directory.

student.proto

syntax = "proto3";

option java_multiple_files = true;
option java_package = "com.protobufdemo.models";

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

Here, syntax = "proto3"; specifies the syntax whether we are using proto2 or proto3. By specifying option java_multiple_files = true; , instead of creating one single file with all aggregated functionality, multiple classed will be generated for our ease of use. option java_package = "com.protobufdemo.models"; specifes the package which will contain our generated classes to keep them organized and if we need to create jar files for running or deploying purposes.

Here is a list of Proto types corresponding to their java types.

Java Type Proto Type
int int32
long int64
float float
double double
boolean bool
String string
byte[] bytes
Collection / List repeated
Map map

Now with this in place, run the command

mvn compile

You will see in the target/generated-sources/protobuf/java, our classes get created.

The generated Student class will look like this

Student.java (generated)

// Generated by the protocol buffer compiler.  DO NOT EDIT!
// source: student.proto

package com.protobufdemo.models;

/**
 * Protobuf type {@code Student}
 */
public final class Student extends
    com.google.protobuf.GeneratedMessageV3 implements
    // @@protoc_insertion_point(message_implements:Student)
    StudentOrBuilder {
private static final long serialVersionUID = 0L;
  // Use Student.newBuilder() to construct.
  private Student(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
    super(builder);
  }
  private Student() {
    name_ = "";
  }

  @java.lang.Override
  @SuppressWarnings({"unused"})
  protected java.lang.Object newInstance(
      UnusedPrivateParameter unused) {
    return new Student();
  }

  @java.lang.Override
  public final com.google.protobuf.UnknownFieldSet
  getUnknownFields() {
    return this.unknownFields;
  }
  private Student(
      com.google.protobuf.CodedInputStream input,
      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
      throws com.google.protobuf.InvalidProtocolBufferException {
    this();
    if (extensionRegistry == null) {
      throw new java.lang.NullPointerException();
    }
    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
        com.google.protobuf.UnknownFieldSet.newBuilder();
    try {
      boolean done = false;
      while (!done) {
        int tag = input.readTag();
        switch (tag) {
          case 0:
            done = true;
            break;
          case 10: {
            java.lang.String s = input.readStringRequireUtf8();

            name_ = s;
            break;
          }
          case 16: {

            age_ = input.readInt32();
            break;
          }
          default: {
            if (!parseUnknownField(
                input, unknownFields, extensionRegistry, tag)) {
              done = true;
            }
            break;
          }
        }
      }
    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
      throw e.setUnfinishedMessage(this);
    } catch (java.io.IOException e) {
      throw new com.google.protobuf.InvalidProtocolBufferException(
          e).setUnfinishedMessage(this);
    } finally {
      this.unknownFields = unknownFields.build();
      makeExtensionsImmutable();
    }
  }
  public static final com.google.protobuf.Descriptors.Descriptor
      getDescriptor() {
    return com.protobufdemo.models.StudentOuterClass.internal_static_Student_descriptor;
  }

  @java.lang.Override
  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
      internalGetFieldAccessorTable() {
    return com.protobufdemo.models.StudentOuterClass.internal_static_Student_fieldAccessorTable
        .ensureFieldAccessorsInitialized(
            com.protobufdemo.models.Student.class, com.protobufdemo.models.Student.Builder.class);
  }

  public static final int NAME_FIELD_NUMBER = 1;
  private volatile java.lang.Object name_;
  /**
   * <code>string name = 1;</code>
   * @return The name.
   */
  @java.lang.Override
  public java.lang.String getName() {
    java.lang.Object ref = name_;
    if (ref instanceof java.lang.String) {
      return (java.lang.String) ref;
    } else {
      com.google.protobuf.ByteString bs = 
          (com.google.protobuf.ByteString) ref;
      java.lang.String s = bs.toStringUtf8();
      name_ = s;
      return s;
    }
  }
  /**
   * <code>string name = 1;</code>
   * @return The bytes for name.
   */
  @java.lang.Override
  public com.google.protobuf.ByteString
      getNameBytes() {
    java.lang.Object ref = name_;
    if (ref instanceof java.lang.String) {
      com.google.protobuf.ByteString b = 
          com.google.protobuf.ByteString.copyFromUtf8(
              (java.lang.String) ref);
      name_ = b;
      return b;
    } else {
      return (com.google.protobuf.ByteString) ref;
    }
  }

  public static final int AGE_FIELD_NUMBER = 2;
  private int age_;
  /**
   * <code>int32 age = 2;</code>
   * @return The age.
   */
  @java.lang.Override
  public int getAge() {
    return age_;
  }

  private byte memoizedIsInitialized = -1;
  @java.lang.Override
  public final boolean isInitialized() {
    byte isInitialized = memoizedIsInitialized;
    if (isInitialized == 1) return true;
    if (isInitialized == 0) return false;

    memoizedIsInitialized = 1;
    return true;
  }

  @java.lang.Override
  public void writeTo(com.google.protobuf.CodedOutputStream output)
                      throws java.io.IOException {
    if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(name_)) {
      com.google.protobuf.GeneratedMessageV3.writeString(output, 1, name_);
    }
    if (age_ != 0) {
      output.writeInt32(2, age_);
    }
    unknownFields.writeTo(output);
  }

  @java.lang.Override
  public int getSerializedSize() {
    int size = memoizedSize;
    if (size != -1) return size;

    size = 0;
    if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(name_)) {
      size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, name_);
    }
    if (age_ != 0) {
      size += com.google.protobuf.CodedOutputStream
        .computeInt32Size(2, age_);
    }
    size += unknownFields.getSerializedSize();
    memoizedSize = size;
    return size;
  }

  @java.lang.Override
  public boolean equals(final java.lang.Object obj) {
    if (obj == this) {
     return true;
    }
    if (!(obj instanceof com.protobufdemo.models.Student)) {
      return super.equals(obj);
    }
    com.protobufdemo.models.Student other = (com.protobufdemo.models.Student) obj;

    if (!getName()
        .equals(other.getName())) return false;
    if (getAge()
        != other.getAge()) return false;
    if (!unknownFields.equals(other.unknownFields)) return false;
    return true;
  }

  @java.lang.Override
  public int hashCode() {
    if (memoizedHashCode != 0) {
      return memoizedHashCode;
    }
    int hash = 41;
    hash = (19 * hash) + getDescriptor().hashCode();
    hash = (37 * hash) + NAME_FIELD_NUMBER;
    hash = (53 * hash) + getName().hashCode();
    hash = (37 * hash) + AGE_FIELD_NUMBER;
    hash = (53 * hash) + getAge();
    hash = (29 * hash) + unknownFields.hashCode();
    memoizedHashCode = hash;
    return hash;
  }

  public static com.protobufdemo.models.Student parseFrom(
      java.nio.ByteBuffer data)
      throws com.google.protobuf.InvalidProtocolBufferException {
    return PARSER.parseFrom(data);
  }
  public static com.protobufdemo.models.Student parseFrom(
      java.nio.ByteBuffer data,
      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
      throws com.google.protobuf.InvalidProtocolBufferException {
    return PARSER.parseFrom(data, extensionRegistry);
  }
  public static com.protobufdemo.models.Student parseFrom(
      com.google.protobuf.ByteString data)
      throws com.google.protobuf.InvalidProtocolBufferException {
    return PARSER.parseFrom(data);
  }
  public static com.protobufdemo.models.Student parseFrom(
      com.google.protobuf.ByteString data,
      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
      throws com.google.protobuf.InvalidProtocolBufferException {
    return PARSER.parseFrom(data, extensionRegistry);
  }
  public static com.protobufdemo.models.Student parseFrom(byte[] data)
      throws com.google.protobuf.InvalidProtocolBufferException {
    return PARSER.parseFrom(data);
  }
  public static com.protobufdemo.models.Student parseFrom(
      byte[] data,
      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
      throws com.google.protobuf.InvalidProtocolBufferException {
    return PARSER.parseFrom(data, extensionRegistry);
  }
  public static com.protobufdemo.models.Student parseFrom(java.io.InputStream input)
      throws java.io.IOException {
    return com.google.protobuf.GeneratedMessageV3
        .parseWithIOException(PARSER, input);
  }
  public static com.protobufdemo.models.Student parseFrom(
      java.io.InputStream input,
      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
      throws java.io.IOException {
    return com.google.protobuf.GeneratedMessageV3
        .parseWithIOException(PARSER, input, extensionRegistry);
  }
  public static com.protobufdemo.models.Student parseDelimitedFrom(java.io.InputStream input)
      throws java.io.IOException {
    return com.google.protobuf.GeneratedMessageV3
        .parseDelimitedWithIOException(PARSER, input);
  }
  public static com.protobufdemo.models.Student parseDelimitedFrom(
      java.io.InputStream input,
      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
      throws java.io.IOException {
    return com.google.protobuf.GeneratedMessageV3
        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
  }
  public static com.protobufdemo.models.Student parseFrom(
      com.google.protobuf.CodedInputStream input)
      throws java.io.IOException {
    return com.google.protobuf.GeneratedMessageV3
        .parseWithIOException(PARSER, input);
  }
  public static com.protobufdemo.models.Student parseFrom(
      com.google.protobuf.CodedInputStream input,
      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
      throws java.io.IOException {
    return com.google.protobuf.GeneratedMessageV3
        .parseWithIOException(PARSER, input, extensionRegistry);
  }

  @java.lang.Override
  public Builder newBuilderForType() { return newBuilder(); }
  public static Builder newBuilder() {
    return DEFAULT_INSTANCE.toBuilder();
  }
  public static Builder newBuilder(com.protobufdemo.models.Student prototype) {
    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
  }
  @java.lang.Override
  public Builder toBuilder() {
    return this == DEFAULT_INSTANCE
        ? new Builder() : new Builder().mergeFrom(this);
  }

  @java.lang.Override
  protected Builder newBuilderForType(
      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
    Builder builder = new Builder(parent);
    return builder;
  }
  /**
   * Protobuf type {@code Student}
   */
  public static final class Builder extends
      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
      // @@protoc_insertion_point(builder_implements:Student)
      com.protobufdemo.models.StudentOrBuilder {
    public static final com.google.protobuf.Descriptors.Descriptor
        getDescriptor() {
      return com.protobufdemo.models.StudentOuterClass.internal_static_Student_descriptor;
    }

    @java.lang.Override
    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
        internalGetFieldAccessorTable() {
      return com.protobufdemo.models.StudentOuterClass.internal_static_Student_fieldAccessorTable
          .ensureFieldAccessorsInitialized(
              com.protobufdemo.models.Student.class, com.protobufdemo.models.Student.Builder.class);
    }

    // Construct using com.protobufdemo.models.Student.newBuilder()
    private Builder() {
      maybeForceBuilderInitialization();
    }

    private Builder(
        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
      super(parent);
      maybeForceBuilderInitialization();
    }
    private void maybeForceBuilderInitialization() {
      if (com.google.protobuf.GeneratedMessageV3
              .alwaysUseFieldBuilders) {
      }
    }
    @java.lang.Override
    public Builder clear() {
      super.clear();
      name_ = "";

      age_ = 0;

      return this;
    }

    @java.lang.Override
    public com.google.protobuf.Descriptors.Descriptor
        getDescriptorForType() {
      return com.protobufdemo.models.StudentOuterClass.internal_static_Student_descriptor;
    }

    @java.lang.Override
    public com.protobufdemo.models.Student getDefaultInstanceForType() {
      return com.protobufdemo.models.Student.getDefaultInstance();
    }

    @java.lang.Override
    public com.protobufdemo.models.Student build() {
      com.protobufdemo.models.Student result = buildPartial();
      if (!result.isInitialized()) {
        throw newUninitializedMessageException(result);
      }
      return result;
    }

    @java.lang.Override
    public com.protobufdemo.models.Student buildPartial() {
      com.protobufdemo.models.Student result = new com.protobufdemo.models.Student(this);
      result.name_ = name_;
      result.age_ = age_;
      onBuilt();
      return result;
    }

    @java.lang.Override
    public Builder clone() {
      return super.clone();
    }
    @java.lang.Override
    public Builder setField(
        com.google.protobuf.Descriptors.FieldDescriptor field,
        java.lang.Object value) {
      return super.setField(field, value);
    }
    @java.lang.Override
    public Builder clearField(
        com.google.protobuf.Descriptors.FieldDescriptor field) {
      return super.clearField(field);
    }
    @java.lang.Override
    public Builder clearOneof(
        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
      return super.clearOneof(oneof);
    }
    @java.lang.Override
    public Builder setRepeatedField(
        com.google.protobuf.Descriptors.FieldDescriptor field,
        int index, java.lang.Object value) {
      return super.setRepeatedField(field, index, value);
    }
    @java.lang.Override
    public Builder addRepeatedField(
        com.google.protobuf.Descriptors.FieldDescriptor field,
        java.lang.Object value) {
      return super.addRepeatedField(field, value);
    }
    @java.lang.Override
    public Builder mergeFrom(com.google.protobuf.Message other) {
      if (other instanceof com.protobufdemo.models.Student) {
        return mergeFrom((com.protobufdemo.models.Student)other);
      } else {
        super.mergeFrom(other);
        return this;
      }
    }

    public Builder mergeFrom(com.protobufdemo.models.Student other) {
      if (other == com.protobufdemo.models.Student.getDefaultInstance()) return this;
      if (!other.getName().isEmpty()) {
        name_ = other.name_;
        onChanged();
      }
      if (other.getAge() != 0) {
        setAge(other.getAge());
      }
      this.mergeUnknownFields(other.unknownFields);
      onChanged();
      return this;
    }

    @java.lang.Override
    public final boolean isInitialized() {
      return true;
    }

    @java.lang.Override
    public Builder mergeFrom(
        com.google.protobuf.CodedInputStream input,
        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
        throws java.io.IOException {
      com.protobufdemo.models.Student parsedMessage = null;
      try {
        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
        parsedMessage = (com.protobufdemo.models.Student) e.getUnfinishedMessage();
        throw e.unwrapIOException();
      } finally {
        if (parsedMessage != null) {
          mergeFrom(parsedMessage);
        }
      }
      return this;
    }

    private java.lang.Object name_ = "";
    /**
     * <code>string name = 1;</code>
     * @return The name.
     */
    public java.lang.String getName() {
      java.lang.Object ref = name_;
      if (!(ref instanceof java.lang.String)) {
        com.google.protobuf.ByteString bs =
            (com.google.protobuf.ByteString) ref;
        java.lang.String s = bs.toStringUtf8();
        name_ = s;
        return s;
      } else {
        return (java.lang.String) ref;
      }
    }
    /**
     * <code>string name = 1;</code>
     * @return The bytes for name.
     */
    public com.google.protobuf.ByteString
        getNameBytes() {
      java.lang.Object ref = name_;
      if (ref instanceof String) {
        com.google.protobuf.ByteString b = 
            com.google.protobuf.ByteString.copyFromUtf8(
                (java.lang.String) ref);
        name_ = b;
        return b;
      } else {
        return (com.google.protobuf.ByteString) ref;
      }
    }
    /**
     * <code>string name = 1;</code>
     * @param value The name to set.
     * @return This builder for chaining.
     */
    public Builder setName(
        java.lang.String value) {
      if (value == null) {
    throw new NullPointerException();
  }

      name_ = value;
      onChanged();
      return this;
    }
    /**
     * <code>string name = 1;</code>
     * @return This builder for chaining.
     */
    public Builder clearName() {

      name_ = getDefaultInstance().getName();
      onChanged();
      return this;
    }
    /**
     * <code>string name = 1;</code>
     * @param value The bytes for name to set.
     * @return This builder for chaining.
     */
    public Builder setNameBytes(
        com.google.protobuf.ByteString value) {
      if (value == null) {
    throw new NullPointerException();
  }
  checkByteStringIsUtf8(value);

      name_ = value;
      onChanged();
      return this;
    }

    private int age_ ;
    /**
     * <code>int32 age = 2;</code>
     * @return The age.
     */
    @java.lang.Override
    public int getAge() {
      return age_;
    }
    /**
     * <code>int32 age = 2;</code>
     * @param value The age to set.
     * @return This builder for chaining.
     */
    public Builder setAge(int value) {

      age_ = value;
      onChanged();
      return this;
    }
    /**
     * <code>int32 age = 2;</code>
     * @return This builder for chaining.
     */
    public Builder clearAge() {

      age_ = 0;
      onChanged();
      return this;
    }
    @java.lang.Override
    public final Builder setUnknownFields(
        final com.google.protobuf.UnknownFieldSet unknownFields) {
      return super.setUnknownFields(unknownFields);
    }

    @java.lang.Override
    public final Builder mergeUnknownFields(
        final com.google.protobuf.UnknownFieldSet unknownFields) {
      return super.mergeUnknownFields(unknownFields);
    }


    // @@protoc_insertion_point(builder_scope:Student)
  }

  // @@protoc_insertion_point(class_scope:Student)
  private static final com.protobufdemo.models.Student DEFAULT_INSTANCE;
  static {
    DEFAULT_INSTANCE = new com.protobufdemo.models.Student();
  }

  public static com.protobufdemo.models.Student getDefaultInstance() {
    return DEFAULT_INSTANCE;
  }

  private static final com.google.protobuf.Parser<Student>
      PARSER = new com.google.protobuf.AbstractParser<Student>() {
    @java.lang.Override
    public Student parsePartialFrom(
        com.google.protobuf.CodedInputStream input,
        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
        throws com.google.protobuf.InvalidProtocolBufferException {
      return new Student(input, extensionRegistry);
    }
  };

  public static com.google.protobuf.Parser<Student> parser() {
    return PARSER;
  }

  @java.lang.Override
  public com.google.protobuf.Parser<Student> getParserForType() {
    return PARSER;
  }

  @java.lang.Override
  public com.protobufdemo.models.Student getDefaultInstanceForType() {
    return DEFAULT_INSTANCE;
  }

}

Notice that the Person class is final , hence it cannot be extended and also the constructor is private , so we cannot create objects using new keyword. Protobuf uses builder pattern to create objects.

Now lets create our first object using our generated Student.java class. Create a class StudentDemo.java.

StudentDemo.java;

import com.protobufdemo.models.Student;

public class StudentDemo
{
    public static void main(String[] args)
    {
        Student Alex = Student
                .newBuilder()
                .setName("Alex")
                .setAge(20)
                .build();

        System.out.println(Alex);
    }
}

Console Output

name: "Alex"
age: 20

Serializing the object

Lets Serialize our student class object and save it to a local file. We'll create a seperate class StudentSerialize.java

StudentSerialize.java

import com.protobufdemo.models.Student;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class StudentSerialize
{
    public static void main(String[] args) throws IOException {

          // Create the student object
          Student Alex = Student
                .newBuilder()
                .setName("Alex")
                .setAge(20)
                .build();

        // Get the file path
        Path path = Paths.get("Alex.ser");

        // Convert the object to bytes and store it in the file.
        Files.write(path, Alex.toByteArray());
    }
}

Run the main() method of the StudentSerialize class and you will find Alex.ser class being created.

Now lets deserialize the object. We will create a class StudentDeserialize.java

StudentDeserialize.java

import com.protobufdemo.models.Student;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class StudentDeserialize
{
    public static void main(String[] args) throws IOException {

        // Get the file path
        Path path = Paths.get("Alex.ser");

        // Read the bytes from the file
        byte[] bytes = Files.readAllBytes(path);

        // Create the student object from the bytes 
        Student deserializedAlex = Student
                                    .parseFrom(bytes);

        System.out.println(deserializedAlex);

    }
}

Run the main() method of StudentDeserialize.java class

Console output (StudentDeserialize.java)

name: "Alex"
age: 20

Look how we were able to serialize and deserialize java object using protocol buffers.

Let us see how we can create objects involving composition. We will add an address field in the Student object and therefore create a separate proto file for Address.

address.proto

syntax = "proto3";

option java_multiple_files = true;
option java_package = "com.protobufdemo.models";

message Address {
    string street = 1;
    string city = 2;
    string pincode = 3;
}

Now, we will update the student.proto file

student.proto

syntax = "proto3";

import "address.proto";

option java_multiple_files = true;
option java_package = "com.protobufdemo.models";

message Student {
    string name = 1;
    int32 age = 2;
    Address address = 3;
}

Now run the command

mvn  clean install

and you will see updated generated classes for Address and Student classes;

Lets update our StudentDemo.java class to include the address field.

StudentDemo.java

import com.protobufdemo.models.Address;
import com.protobufdemo.models.Student;

public class StudentDemo
{
    public static void main(String[] args)
    {
        Address address = Address
                .newBuilder()
                .setStreet("street 1")
                .setCity("city 1")
                .setPincode("123456")
                .build();

        Student Alex = Student
                .newBuilder()
                .setName("Alex")
                .setAddress(address)
                .setAge(20)

                .build();

        System.out.println(Alex);
    }
}

Run the main() method of StudentDemo.java class;

Console output (StudentDemo.java)

name: "Alex"
age: 20
address {
  street: "street 1"
  city: "city 1"
  pincode: "123456"
}

This article explained how we can use Protocol Buffers to serialize and deserialize data in a language neutral, platform neutral extensible manner. For example , you can Serialize data from from one service using Java, and deserialize it on other service using C++ or JavaScript or any language that Protocol Buffers support if you have the same proto files on both the services.

The article provides a good introduction to Protocol Buffers with a small hands on example, and is best for beginners who are just starting out to learn Protocol Buffers.

I hope you found the article useful.

Lets connect :

Happy Coding :) .