1. Creating the Maven Project
You can use any IDE you like, but in this tutorial we will assume IntelliJ IDEA. Create a new Maven project with the name:
gRPC-playground
The word playground is intentional. It reminds us that:
- This project is for learning and experimentation, not for production.
- We will try many small examples and throw them away.
- We will focus on understanding features, not on building a polished application.
Use:
- Language: Java
- Build tool: Maven
- Java version: Java 21 or Java 17 (it does not matter, because we are not using any version-specific features).
Once the project is created, let Maven finish importing and indexing.
2. Installing the Protobuf IntelliJ Plugin (Optional but Helpful)
In IntelliJ, go to:
Settings → Plugins → Search for “Proto” or “Protocol Buffers”
You will find a Protobuf-related plugin. Install it.
This plugin is not mandatory. If you are not using IntelliJ, or if you do not see this plugin, it is perfectly fine. The project will still work. The plugin mainly gives you:
- Syntax highlighting for
.protofiles - Some basic IDE assistance
It does not affect the build or runtime behavior.
3. Adding Dependencies to pom.xml
Conceptually, the dependencies fall into a few groups:
- gRPC and Protobuf dependencies, which we will slowly understand as we progress through the course.
- Java 9+ compatibility dependencies, because some older annotations and APIs were removed from the JDK after Java 8.
- Logback, so that we do not rely on
System.out.printlnand can use proper logging. - Jackson, which we will use only for a performance comparison test between JSON and Protobuf.
- JUnit, for writing and running tests.
In addition to dependencies, we also configure:
- Maven Compiler Plugin, to control the Java version.
- Maven Surefire Plugin, so we can run tests from the command line.
- Protobuf Maven Plugin, which is the most important part for this section.
<?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>org.learnitweb</groupId>
<artifactId>grpc-playground</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<grpc.version>1.72.0</grpc.version>
<protoc.version>3.25.5</protoc.version>
<logback.version>1.5.12</logback.version>
<jackson.version>2.18.1</jackson.version>
<tomcat.annotations.version>6.0.53</tomcat.annotations.version>
<os.maven.plugin.version>1.7.1</os.maven.plugin.version>
<protobuf.maven.plugin.version>0.6.1</protobuf.maven.plugin.version>
<junit.version>5.11.3</junit.version>
<surefire.version>3.5.2</surefire.version>
</properties>
<dependencies>
<!-- grpc / proto related dependencies -->
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>${grpc.version}</version>
</dependency>
<!-- necessary for Java 9+ -->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>annotations-api</artifactId>
<version>${tomcat.annotations.version}</version>
<scope>provided</scope>
</dependency>
<!-- logback -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
<!-- Jackson lib for a performance test -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- junit -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>${os.maven.plugin.version}</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>21</source>
<target>21</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${surefire.version}</version>
</plugin>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>${protobuf.maven.plugin.version}</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:${protoc.version}:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}: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>
</plugins>
</build>
</project>
4. Why We Need the Protobuf Maven Plugin
This part is extremely important conceptually.
A .proto file is:
- Language-neutral
- Platform-neutral
But the JVM is not platform-neutral in terms of native binaries. To convert a .proto file into Java source code, we need a tool called:
protoc (the Protobuf compiler)
This tool is:
- Different for Windows, Mac, and Linux
- Different for different CPU architectures
The Protobuf Maven plugin solves this problem for us by:
- Detecting your operating system and architecture
- Downloading the correct
protocbinary automatically - Running it during the Maven build
- Generating Java source files from your
.protofiles
So you never have to manually install or manage protoc.
Even if this feels a little abstract right now, do not worry. Once you see the generated code, this will become very clear.
5. Adding Logback Configuration
Because we want to use logging instead of System.out.println, copy the provided logback.xml file and place it under:
src/main/resources/logback.xml
Whenever you change pom.xml, always remember to:
Reload Maven Project in IntelliJ
Sometimes IntelliJ shows red errors simply because it has not yet downloaded the dependencies. A Maven reload usually fixes that.
6. Creating Your First .proto File
Now that the project is set up, let us create our very first Protobuf file.
6.1 Where to Put .proto Files in a Maven Project
The recommended convention is:
src/main/proto
Just like:
src/main/java
If the proto directory does not exist, create it.
Your project structure should now look like this:
src
└── main
├── java
├── resources
└── proto
The Protobuf Maven plugin is already configured to look in this directory.
6.2 Creating person.proto
Under src/main/proto, create a file:
person.proto
Add the following content:
syntax = "proto3";
message Person {
string name = 1;
int32 age = 2;
}
At this point, you might not fully understand:
- What
proto3means - What
= 1and= 2mean - Why types look like
stringandint32
That is completely fine. Right now, the goal is to see the workflow, not to master the syntax.
7. Proto2 vs Proto3 and the syntax Line
If you do not write:
syntax = "proto3";
Your IDE will complain and say something like:
Fields must have labels: optional, required, or repeated
That is because, by default, it assumes Proto2.
Proto2 had:
requiredoptionalrepeated
This turned out to cause many API compatibility and evolution problems in real systems. Because of this, Proto3 was introduced, which simplifies many rules and removes several dangerous concepts.
Proto1 was never public. It was internal to Google and can be ignored.
So, for all modern systems:
Always use Proto3
8. Generating Java Code Using Maven
Now go to the project root and run:
mvn clean compile
If everything is set up correctly, the build should succeed.
Now look inside:
target/generated-sources/protobuf/java
You will see something like:
PersonOuterClass.java
This file is:
- Automatically generated
- Not meant to be edited by hand
- The result of running
protocon your.protofile
If you open it, you will see many methods like getName(), getAge(), builders, parsers, and many other things. It looks complex, but you are not supposed to write or maintain this file yourself. You only use it.
9. Adding a Java Package to the Generated Code
Right now, the generated Java file has no package. That is because:
A
.protofile is language-neutral.
If you want Java-specific behavior, you must specify it explicitly.
Modify person.proto like this:
syntax = "proto3";
option java_package = "com.learnitweb.models";
message Person {
string name = 1;
int32 age = 2;
}
Now run:
mvn clean compile
You will see that the generated file is now under:
com.learnitweb.models
Protobuf also supports:
go_package- Other language-specific options
Because each language has its own conventions.
10. Fixing IntelliJ Not Recognizing Generated Sources
Sometimes Maven works perfectly, but IntelliJ shows errors and cannot import the generated classes. This is an IDE issue, not a build issue. It is very similar to how Lombok behaves.
To fix this:
- Go to File → Project Structure → Modules
- Find:
target/generated-sources/protobuf/java - Right-click it and choose:
Mark Directory As → Generated Sources Root
Now IntelliJ will immediately recognize the classes.
If it still behaves strangely, use:
Invalidate Caches and Restart
11. Writing the First Demo Program
package org.learnitweb;
import com.learnitweb.models.PersonOuterClass;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
//TIP To <b>Run</b> code, press <shortcut actionId="Run"/> or
// click the <icon src="AllIcons.Actions.Execute"/> icon in the gutter.
public class Main {
private static final Logger logger = LoggerFactory.getLogger(Main.class);
public static void main(String[] args) {
var person = PersonOuterClass.Person
.newBuilder()
.setName("sam")
.setAge(31)
.build();
logger.info("{}", person);
}
}
Output
SLF4J(I): Connected with provider of type [ch.qos.logback.classic.spi.LogbackServiceProvider] 00:53:36.781 INFO [ main] org.learnitweb.Main : name: "sam" age: 31
