Plugins
Extend the build with external source generators. Any binary named curie-<name> on PATH becomes a plugin — Curie invokes it before compilation, feeds it the resolved tool paths, and adds its output directories to the compiler's source roots.
Activating a plugin
Add a [plugin.<name>] table to Curie.toml. The key after plugin. is the plugin name; Curie will look for a binary called curie-<name> on PATH.
[application] name = "proto-greeter" version = "0.1.0" [java] version = "21" [dependencies] "com.google.protobuf:protobuf-java" = "3.25.0" [plugin.protobuf] version = "3.25.0" sourceDir = "proto"
The entire [plugin.protobuf] table — including any nested subtables like [plugin.protobuf.importPaths] — is forwarded verbatim as JSON to the plugin. Plugin keys are camelCase by convention.
Multiple plugins can be active at once: each [plugin.<name>] key activates one binary. Order of execution follows alphabetical key order.
What Curie handles
Plugins are pure logic — they never download anything. Curie owns the parts that touch the network and the filesystem:
- Staleness tracking — the plugin declares which input directories and files to watch. Curie records their mtimes in a stamp file (
target/.curie-plugins/<name>.stamp) and skips generation when nothing has changed. - Artifact downloading — the plugin's manifest lists the Maven artifacts it needs (e.g. the
protocbinary). Curie downloads them from Maven Central into the local~/.m2/repository/cache, verifies with SHA-1, and sets the executable bit. - Source root wiring — the plugin's declared output directories are added to
javac's source root list, so generated.javafiles are picked up automatically on the next compile.
Protobuf plugin
curie-protobuf is the reference plugin. It generates Java stubs from .proto files and optionally gRPC service interfaces.
Installation
cargo install curie-protobuf
Plain protobuf
[plugin.protobuf] version = "3.25.0" # protobuf + protoc version sourceDir = "proto" # directory containing .proto files (default: "proto")
Generated sources land in target/generated-sources/protobuf/. After a successful run the stamp records the mtime of every file under proto/, so subsequent builds skip protoc when nothing changed.
gRPC
[plugin.protobuf] version = "3.25.0" grpc = true grpcVersion = "1.60.0" sourceDir = "proto"
With grpc = true, the plugin also downloads protoc-gen-grpc-java and passes it as a protoc plugin, producing both message classes and service stubs.
Build output
Building proto-greeter v0.1.0 Plugin protobuf generate-sources proto/greeter.proto Compile 3 source file(s) Tests no test sources found Package proto-greeter-0.1.0.jar Done target/proto-greeter-0.1.0.jar
On the second run with no changes the Plugin protobuf line is omitted — the stamp matched, so protoc is not invoked and the previously generated sources are used directly.
Plugin protocol (for plugin authors)
A plugin is any binary that implements two subcommands. The full stdin/stdout protocol is described here so you can write your own.
Subcommand 1: manifest
Called on every build, even when up-to-date. Must be fast — no I/O other than reading stdin.
curie-<name> manifest --project <dir>
Stdin — JSON envelope:
{
"curie_version": "0.6.0",
"config": {
"version": "3.25.0",
"sourceDir": "proto"
}
}
config is the full [plugin.<name>] TOML tree converted to JSON, including nested tables. Ignore unknown top-level fields for forward-compatibility.
Stdout — manifest JSON:
{
"name": "protobuf",
"description": "Generate Java stubs from .proto files",
"version": "0.1.0",
"types": ["source-generator"],
"inputs": {
"dirs": ["proto"],
"file_regex": "\\.proto$"
},
"outputs": {
"source_dirs": ["target/generated-sources/protobuf"]
},
"artifacts": [
{
"id": "protoc",
"group": "com.google.protobuf",
"artifact": "protoc",
"version": "3.25.0",
"classifier":"linux-x86_64",
"extension": "exe",
"executable":true
}
]
}
| Field | Meaning |
|---|---|
types | Capability array. Only "source-generator" is acted on today; list multiple to serve future roles. |
inputs.dirs | Directories Curie watches for new/removed/modified files. |
inputs.file_regex | Optional regex filter applied to files within dirs. |
inputs.files | Optional explicit file list, used instead of (or in addition to) dirs. |
outputs.source_dirs | Directories added to javac's source root list after generation. |
artifacts[].id | Logical key used in the generate-sources call's artifact map. |
artifacts[].classifier | Platform-specific (the plugin detects OS/arch here). |
artifacts[].executable | Curie sets the executable bit on the cached file when true. |
Subcommand 2: generate-sources
Called only when the stamp is stale. Curie has already downloaded all declared artifacts before this call.
curie-<name> generate-sources --project <dir> [--offline]
Stdin — the same envelope as manifest, extended with artifacts:
{
"curie_version": "0.6.0",
"config": { "version": "3.25.0", "sourceDir": "proto" },
"artifacts": {
"protoc": "/home/user/.m2/repository/com/google/protobuf/protoc/3.25.0/protoc-3.25.0-linux-x86_64.exe"
}
}
Write progress to stderr. Exit 0 on success. Curie writes the stamp after a successful exit.
Artifact caching
Curie caches downloaded artifacts in the standard Maven local repository layout:
~/.m2/repository/<group/path>/<artifact>/<version>/<artifact>-<version>[-<classifier>].<ext>
Downloads are verified against a SHA-1 sidecar fetched from the same repository. A cached file is never re-downloaded. The --offline flag passed to generate-sources reflects curie build --offline; the plugin can use it to skip any network calls of its own.
OpenAPI plugin
curie-openapi generates Java model and API sources from an OpenAPI 3.x spec using openapi-generator-cli. The CLI JAR is cross-platform; no native binary or platform classifier is needed.
Installation
cargo install curie-openapi
Configuration
[plugin.openapi] version = "7.2.0" # openapi-generator-cli version specFile = "api/greeter.yaml" generatorName = "java" # optional modelPackage = "com.example.model" apiPackage = "com.example.api" invokerPackage = "com.example" outputDir = "target/generated-sources/openapi" # default sourceFolder = "src/main/java" # default [plugin.openapi.additionalProperties] library = "native" # use java.net.http + Jackson; no OkHttp needed openApiNullable = "false" hideGenerationTimestamp = "true" [plugin.openapi.globalProperties] models = "" supportingFiles = ""
All keys under [plugin.openapi.additionalProperties] are forwarded to openapi-generator as --additional-properties key=val,.... Keys under [plugin.openapi.globalProperties] are passed as repeated --global-property key=val flags.
Generated sources land in target/generated-sources/openapi/src/main/java/ and are added to javac's source root automatically. The specFile mtime is stamped so protoc-style incremental builds work: regeneration only happens when the spec file changes.
Build output
Building openapi-greeter v0.1.0 Plugin openapi greeter.yaml Resolve deps 6 JAR(s) Compile 14 source file(s) [no class files] Package openapi-greeter-0.1.0.jar Done target/openapi-greeter-0.1.0.jar
Example: proto-greeter
The examples/proto-greeter project shows the full setup end-to-end.
syntax = "proto3"; option java_package = "com.example.greeter"; option java_multiple_files = true; message GreetRequest { string name = 1; } message GreetResponse { string message = 1; }
package com.example; import com.example.greeter.GreetRequest; import com.example.greeter.GreetResponse; public class Greeter { public static GreetResponse greet(String name) { return GreetResponse.newBuilder() .setMessage("Hello, " + name + "!") .build(); } }