1. Introduction
When working with Protobuf in Java projects, one common surprise is the “outer class” that Protobuf generates around your messages. Many developers expect to access their message types directly, but instead they find themselves navigating through a wrapper class.
This tutorial explains:
- What the outer class is
- Why Protobuf generates it
- How to remove the need to use it
- How
java_multiple_filesworks - How to use the generated classes in Java
We will build a clear mental model so you understand what Protobuf is doing behind the scenes and how to configure it for cleaner Java usage.
2. The Default Behavior: Outer Class Generation
By default, when you define a .proto file like this:
syntax = "proto3";
package section02;
option java_package = "com.learnitweb.models.section02";
message Person {
string name = 1;
int32 age = 2;
}
Protobuf typically generates:
- One outer Java class named after the proto file
- Inner classes inside it for each message
If your file is named:
Person.proto
Protobuf may generate something like:
PersonOuterClass.Person
This means your actual message type is nested inside another class.
So instead of:
Person.newBuilder()
you might need:
PersonOuterClass.Person.newBuilder()
This feels verbose and inconvenient in real projects.
3. The Goal
We want to access the message directly like a normal Java class.
Example:
Person.newBuilder()
without going through an outer wrapper.
4. The Solution: java_multiple_files
Protobuf provides a Java-specific option:
option java_multiple_files = true;
By default:
java_multiple_files = false
When set to true, Protobuf generates:
- One Java file per message
- One Java file per enum
- One Java file per service
Instead of grouping everything inside one outer class.
5. Updated Proto Configuration
Here is the improved .proto:
syntax = "proto3";
package section02;
option java_package = "com.learnitweb.models.section02";
option java_multiple_files = true;
message Person {
string name = 1;
int32 age = 2;
}
6. What Happens After mvn clean compile
When you run:
mvn clean compile
Now Protobuf generates:
Person.java PersonOrBuilder.java <ProtoFileName>.java (descriptor holder)
Important Understanding
The Person.java is now your actual message class. You can use it directly.
Example:
Person person = Person.newBuilder()
.setName("Sam")
.setAge(12)
.build();
This is much cleaner and matches normal Java expectations.
7. But Why Do We Still See Another Class?
Even with java_multiple_files = true, you may still see a class related to the proto file itself.
Example:
Hello.java
if your file is named:
hello.proto
What is this class?
This class represents metadata about the entire proto file.
It contains:
- Descriptor information
- Schema metadata
- Internal references used by Protobuf runtime
This is not meant for everyday use.
You usually do not touch this class directly.
8. Why the “Outer Class” Appears Sometimes
If your proto file and message share the same name:
Person.proto message Person
Protobuf avoids naming conflicts by introducing a wrapper/outer structure.
This is a naming conflict resolution strategy.
9. Simple Naming Strategy to Avoid Confusion
A practical tip:
- Avoid giving proto files the exact same name as messages
- Use descriptive file names
Example:
person_messages.proto user_models.proto account_types.proto
This reduces confusion and keeps generated code predictable.
10. Using the Generated Class in Java
Example main class:
package com.guru.section02;
import com.guru.models.section02.Person;
public class MainApp {
public static void main(String[] args) {
Person person = Person.newBuilder()
.setName("Sam")
.setAge(12)
.build();
System.out.println(person);
}
}
What Happens Here
The builder pattern is automatically generated by Protobuf, which allows safe and structured object creation.
You do not implement the builder yourself because Protobuf generates the implementation behind the scenes.
The message class is final because Protobuf messages are immutable.
11. Best Practices for Java + Protobuf
Always enable java_multiple_files = true in Java projects because it produces cleaner and more modular code.
Always define java_package explicitly to keep generated code organized.
Treat descriptor/outer classes as internal infrastructure rather than application-level code.
Use meaningful proto file names that do not clash with message names.
Rely on builders to create message instances because Protobuf messages are immutable by design.
