Generate compile-time-safe protobuf builders with the protogen-maven-plugin — proto2 required-field enforcement, proto3 presence-aware accessors, and oneof discriminators. Use when configuring the plugin, reasoning about the generated builder chain, or when the user says "generate builders", "protobuf builder", "required field", or "shift-left validation".
Scanned 8/31/2026
Install to Claude Code
npx -y skills add adamw7/tools --skill protogen --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Protogen?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/adamw7-protogen)More formats (shields.io, HTML) on the badges page.
---
name: protogen
description: Generate compile-time-safe protobuf builders with the protogen-maven-plugin — proto2 required-field enforcement, proto3 presence-aware accessors, and oneof discriminators. Use when configuring the plugin, reasoning about the generated builder chain, or when the user says "generate builders", "protobuf builder", "required field", or "shift-left validation".
---
# Protogen Skill
Generate and use the compile-time-safe protobuf builders produced by
`code/protogen-maven-plugin`. The point of this module is **shift-left**: stock
protobuf builders only detect a missing `required` field at runtime (an
`UninitializedMessageException` from `build()`), while the generated builder
chain makes the same mistake **fail to compile**.
## How the plugin is wired
The plugin runs **after** protobuf classes exist, so it consumes the compiled
`*.proto` output and emits builder sources. Bind its `code-generator` goal to a
generate-sources phase, point it at the packages holding the generated protobuf
messages, and name an output package for the builders:
```xml
<plugin>
<groupId>io.github.adamw7</groupId>
<artifactId>protogen-maven-plugin</artifactId>
<!-- Use the latest release: https://github.com/adamw7/tools/releases/latest -->
<configuration>
<generatedSourcesDir>${project.basedir}/target/generated-sources/</generatedSourcesDir>
<pkgs>
<param>io.github.adamw7.tools.code.protos</param>
</pkgs>
<outputpackage>io.github.adamw7.tools.code.builders</outputpackage>
</configuration>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>code-generator</goal>
</goals>
</execution>
</executions>
</plugin>
```
Mojo goal / parameters (`CodeMojo`, `defaultPhase = GENERATE_SOURCES`):
| Parameter | Property | Meaning |
|---|---|---|
| `generatedSourcesDir` | `generatedsourcesdir` | Where the builder sources are written |
| `pkgs` | `pkgs` | Packages to scan for compiled protobuf messages |
| `outputpackage` | `outputpackage` | Package the generated builders go into |
Add the output directory as a source root (e.g. `build-helper-maven-plugin`'s
`add-source` / `add-test-source`) so the builders are compiled with the rest of
the module.
## How the goal talks to its user
This module is the one place in the repository whose production code does not
log through log4j2, and `ProtogenArchitectureTest.pluginLogsThroughTheMojoLog`
holds it to that:
- **Output** goes through `AbstractMojo.getLog()`, which honours `-q` and `-X`
and attributes each line to the plugin in the reactor output. `MessagesFinder`
and `Code` take that `Log` as their first constructor argument rather than
reaching for a static logger of their own. log4j2 is test-scoped here, so it
cannot come back through the plugin jar.
- **Failures** leave `execute()` as a `MojoExecutionException` naming the output
package, with the original failure as its cause, so a mistake in a consumer's
pom reads as an attributed build failure. The generator packages stay free of
the Maven API and throw `MojoException` (message plus cause); `CodeMojo` is
where it is translated. Never let a `RuntimeException` escape the goal — Maven
prints one as "this is likely a bug in the plugin".
- **The classpath** the scan runs through is a `List<URL>` in the order Maven
resolved it — never a `Set`, whose hashing resolves the host and reorders the
scan — and the `URLClassLoader` built from it is closed by `execute()`, which
is what releases the jar handles Windows would otherwise keep locked.
## What the generated chain guarantees
For a message with `required` fields, the plugin emits a chain of single-method
interfaces so each required setter returns the interface exposing only the
*next* required setter. `build()` appears only after the last required field, so
you cannot call it early — the missing-field bug is now a compile error:
```java
// required: id, department → both must be set before build() is reachable
Person person = builder.setId(1).setDepartment("dep")
.setEmail("sth@sth.net").setName("Adam").build();
```
## proto2 vs proto3 (the rules that trip people up)
- **proto2**: every `required` field is enforced by the builder chain; every
singular field tracks presence, so all get a `hasXxx()` accessor.
- **proto3**: has no `required` fields, so the builder is all-optional — there is
nothing to enforce. `hasXxx()` is generated **only** for message fields and
fields declared with the explicit `optional` keyword. Implicit-presence proto3
scalars have no `hasXxx()` and are left alone.
- **`oneof`**: gets a `getXxxCase()` accessor returning protobuf's generated
`XxxCase` enum plus a `clearXxx()` that resets the whole group — both reachable
through the fluent chain. The synthetic oneofs backing proto3 `optional` fields
are **not** treated as groups, so no spurious case accessor is generated.
## Gotchas
- Run `mvn clean …` after removing a `.proto` source, so stale builders in
`target/` cannot mask the change (repo-wide clean-after-codegen rule).
- The plugin needs the compiled protobuf classes on the runtime classpath
(`requiresDependencyResolution = RUNTIME`); generate the `*.proto` first.
## References
- `README.md` — *Code generation* (worked proto2 example + generated chain)
- `docs/compile-time-safe-builders.md` — visual walkthrough of the chain
- `code/protogen-maven-plugin-test/pom.xml` — a working end-to-end configuration
- `AGENTS.md` — *Code generation* summary (source of truth)
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!