This guide walks you through adding PBJ to a Gradle project, writing a .proto file, and using the generated Java classes.
For comprehensive usage patterns, see usage-guide.md. For the full protobuf feature reference, see protobuf-and-schemas.md.
In your build.gradle.kts:
plugins {
id("com.hedera.pbj.pbj-compiler") version "<version>"
}
dependencies {
implementation("com.hedera.pbj:pbj-runtime:<version>")
}pbj {
javaPackageSuffix = ".pbj" // suffix appended to derived package names
generateTestClasses = false // disable generated unit tests (default: true)
}The javaPackageSuffix is useful when you need PBJ-generated and protoc-generated classes to coexist in the same project under different packages.
Create a .proto file in src/main/proto/:
// src/main/proto/greeter.proto
syntax = "proto3";
package com.example.greeter;
option java_package = "com.example.greeter.protoc";
// <<<pbj.java_package = "com.example.greeter">>>
message HelloRequest {
string name = 1;
int32 count = 2;
}
message HelloReply {
string message = 1;
}
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}The // <<<pbj.java_package = "...">>> comment tells PBJ to generate classes in a different package than protoc would use. This is optional but recommended if you need both generators.
./gradlew assembleGenerated code appears in build/generated/source/pbj-proto/main/java/. For the example above:
com/example/greeter/
├── HelloRequest.java (model)
├── HelloReply.java (model)
├── GreeterServiceInterface.java (gRPC service interface)
├── schema/
│ ├── HelloRequestSchema.java
│ └── HelloReplySchema.java
└── codec/
├── HelloRequestProtoCodec.java
├── HelloRequestJsonCodec.java
├── HelloReplyProtoCodec.java
└── HelloReplyJsonCodec.java
PBJ model objects are immutable. Use the builder pattern to construct them:
HelloRequest request = HelloRequest.newBuilder()
.name("World")
.count(5)
.build();String name = request.name(); // returns null if not set
String safeName = request.nameOrElse(""); // returns "" if not set
int count = request.count(); // returns 0 if not set (primitive)PBJ returns null for absent reference-type fields (unlike protoc, which returns defaults). This forces explicit handling of missing data.
Use copyBuilder() to create a modified copy:
HelloRequest modified = request.copyBuilder()
.count(10)
.build();
// original request is unchanged// Protobuf binary
Bytes bytes = HelloRequest.PROTOBUF.toBytes(request);
HelloRequest parsed = HelloRequest.PROTOBUF.parse(bytes);
// JSON
String json = HelloRequest.JSON.toJSON(request);
HelloRequest fromJson = HelloRequest.JSON.parse(jsonReadableData);try {
HelloRequest msg = HelloRequest.PROTOBUF.parse(untrustedInput);
} catch (ParseException e) {
// Handle malformed or invalid protobuf data
}To use PBJ's gRPC implementation on Helidon, add the server dependency:
dependencies {
implementation("com.hedera.pbj:pbj-grpc-helidon:<version>")
}Implement the generated service interface:
public class GreeterServiceImpl implements GreeterServiceInterface {
@Override
public HelloReply sayHello(HelloRequest request) {
return HelloReply.newBuilder()
.message("Hello " + request.nameOrElse("stranger"))
.build();
}
}Start a Helidon server:
WebServer.builder()
.port(8080)
.addRouting(PbjRouting.builder().service(new GreeterServiceImpl()))
.build()
.start();- Usage Guide — comprehensive reference for all PBJ features
- Protobuf & Schemas — type mappings, nullability, oneofs, maps, and PBJ-specific extensions
- Architecture — module structure and design decisions