Skip to content
Back to skills

Protogen

ASecurity

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".

  • 11 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added August 31, 2026
developmentgojavareactgitapi

Works with

  • terminal
  • api

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned September 28, 2026

npx -y skills add adamw7/tools --skill protogen --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Protogen?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Protogen
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/adamw7-protogen/badge)](https://www.skillsdirectory.com/skills/adamw7-protogen)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
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 chain holds only the `repeated`
  and `map` fields, which are steps in every syntax (`ClassInfo.nonOptional()`
  is `required` ∪ `map` ∪ `repeated`); a message without them gets a single
  terminal interface. `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)

Files in this skill

  • README.md992 B
  • SKILL.md5.5 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.