Annotation processing (Java + Kotlin)
Bleep runs Java annotation processors (Lombok, MapStruct, Immutables, Dagger, …) via three composable fields under java:. The default is off, nothing scans, nothing runs unless you opt in.
Kotlin annotation processing: bleep ships KSP1 support as the standalone Analysis-API runner. See the Kotlin section below for the user surface, scan vs. explicit mode, version pairing, and current limitations. KAPT is not supported; migrate to KSP first.
Resolution and processor-jar discovery happen lazily during compile as a first-class DAG task (bleep:resolve-annotation-processors:<project>), not at bootstrap. Failures are isolated per project and surface as a build failure with the project name in the message.
Quick start: Lombok
Lombok ships annotations and a processor in a single jar. List it as a dependency, opt in to scanning, done:
projects:
myapp:
dependencies:
- org.projectlombok:lombok:1.18.46
java:
scanForAnnotationProcessors: true
Bleep finds Lombok's META-INF/services/javax.annotation.processing.Processor entry inside the jar at compile time and puts it on javac's -processorpath. ServiceLoader picks the processor class. @Data's getName(), setName(), toString(), etc. are generated as expected.
Three knobs
| Field | Default | Purpose |
|---|---|---|
scanForAnnotationProcessors: Boolean | false | Opt-in to scan resolved-dependencies jars for processors. Matches found jars get added to -processorpath. |
annotationProcessors: [Dep] | [] | Explicit processor-only deps. Always added to -processorpath; never on the runtime/compile classpath of the project. |
annotationProcessorOptions: {key: value} | {} | -A<key>=<value> flags. Visible to every configured processor (JSR 269 has no per-processor -A). |
The two collections compose. You can scan AND list explicit extras in the same project.
MapStruct, AutoValue, Immutables, Dagger (split-jar processors)
These ship their API as a small annotation jar (runtime-needed) and a separate processor jar (compile-time only). To keep the runtime classpath lean, put the annotations in dependencies and the processor in annotationProcessors. Each processor jar has a standard META-INF/services/javax.annotation.processing.Processor entry, so javac's ServiceLoader picks the classes. Bleep doesn't have to name them.
# MapStruct
projects:
myapp:
dependencies:
- org.mapstruct:mapstruct:1.5.5.Final
java:
annotationProcessors:
- org.mapstruct:mapstruct-processor:1.5.5.Final
annotationProcessorOptions:
mapstruct.suppressGeneratorTimestamp: "true"
# AutoValue
projects:
myapp:
dependencies:
- com.google.auto.value:auto-value-annotations:1.10.4
java:
annotationProcessors:
- com.google.auto.value:auto-value:1.10.4
scanForAnnotationProcessors is omitted, pure-explicit mode. Lombok-style jars in dependencies would not be auto-detected here.
This matches Maven's <annotationProcessorPaths> and Gradle's annotationProcessor configurations. The Maven idiom of <scope>provided</scope> for the processor jar is the equivalent of annotationProcessors: here, both keep the heavy processor JAR off the runtime classpath.
Composed: scan + explicit
For projects that mix Lombok-style auto-discovery with an explicit processor-only dep:
projects:
myapp:
dependencies:
- org.projectlombok:lombok:1.18.46
- org.mapstruct:mapstruct:1.5.5.Final
java:
scanForAnnotationProcessors: true
annotationProcessors:
- org.mapstruct:mapstruct-processor:1.5.5.Final
Both processors run in a single javac invocation. Generated source files land under .bleep/generated-sources/<crossName>/annotations/ and are added to the project's compile source path.
Power-user mode: pure-explicit, no scanning
Build engineers who want full reproducibility (no surprises from scanning) omit the boolean and pin every processor by hand:
projects:
myapp:
dependencies:
- org.projectlombok:lombok:1.18.46 # for @Data references on compile classpath
java:
annotationProcessors:
- org.projectlombok:lombok:1.18.46 # also pinned as a processor
Dependency upgrades cannot silently introduce new processors here, only what's in the explicit list runs.
Errors and escape hatches
- No-op opt-in fails loud.
scanForAnnotationProcessors: truewith emptyannotationProcessorsand zero processor-bearing jars independenciesmakes the AP DAG task fail at compile time, propagating up ascommands.compile/commands.runthrowing aBleepExceptionwith"Annotation processor resolution failed for N project(s)". The user typedtruefor a reason; we don't silently disable. - Manual
-proc:noneis honored. Putting-proc:noneinjava.optionsskips all auto-wiring. Use this when you want to disable processing for a project that has processor-bearing dependencies. - Conflicting flags rejected. Manual
-processorpath,-A,-proc:*, or-sinjava.optionsraise an error when bleep is also wiring annotation processing, choose one or the other. -processor(the comma-separated class-name list) is allowed injava.options. Bleep never emits-processoritself; use it to pin a subset (e.g. one processor from a multi-processor jar) or to work around the rare jar that ships its processor classes without aMETA-INF/services/javax.annotation.processing.Processorregistration.
Where generated sources go
.bleep/generated-sources/<crossName>/annotations/, a single directory per project per cross-id. All processors share this -s directory (per JSR 269). Output files separate naturally by Java package.
The directory is reserved at bootstrap whenever a project has any annotation-processor configuration, and added to the project's source set so downstream compile picks up the generated .java files. bleep clean wipes it.
Kotlin: KSP
Kotlin annotation processing in bleep goes through KSP, the Kotlin
Symbol Processing API. Three composable fields under kotlin:. Default
off, nothing runs unless you opt in:
projects:
myapp:
source-layout: kotlin
kotlin:
version: 2.1.20
kspVersion: 1.0.32
symbolProcessors:
- com.squareup.moshi:moshi-kotlin-codegen:1.15.0
symbolProcessorOptions:
room.schemaLocation: ./schemas
dependencies:
- com.squareup.moshi:moshi:1.15.0
platform:
name: jvm
KSP runs as a separate process before kotlinc. It reads your Kotlin and
Java sources, invokes the configured processors, and emits generated
files under .bleep/generated-sources/<crossName>/ksp/. The next
kotlinc step picks those generated files up via the project's source
set automatically.
Versioning
kspVersion is the KSP-side suffix only. Bleep concatenates it with
kotlin.version to form the full Maven coord
com.google.devtools.ksp:symbol-processing-aa-embeddable:<kotlin>-<ksp>,
e.g. 2.1.20-1.0.32. KSP releases are pinned 1:1 to exact kotlinc
versions, so the kotlin prefix is forced; you only pick the KSP-side
release. Browse KSP releases
to find a release paired with your kotlin.version.
Required when symbolProcessors is non-empty or
scanForSymbolProcessors: true. No default. Bleep fails loud at
compile time if missing.
Two ways to declare processors
Explicit (recommended):
kotlin:
kspVersion: 1.0.32
symbolProcessors:
- androidx.room:room-compiler:2.7.0
- com.google.dagger:hilt-compiler:2.51
Each entry resolves with its full transitive closure and is passed to KSP via the processor classpath. Processor jars never leak onto the runtime classpath.
Scan-mode:
kotlin:
kspVersion: 1.0.32
scanForSymbolProcessors: true
dependencies:
- com.squareup.moshi:moshi:1.15.0
- com.squareup.moshi:moshi-kotlin-codegen:1.15.0
When scanForSymbolProcessors: true, bleep scans the resolved
dependency JARs for
META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider
and treats any matches as KSP processors. Useful when a single library
ships both runtime and processor classes (Moshi codegen ships in the
same artifact graph as Moshi itself).
Failing loud: scan-mode without any matched processors raises at compile time. The opt-in had to mean something.
Processor options
symbolProcessorOptions is a string-to-string map. Bleep encodes the
entries into KSP's -processor-options=k1=v1:k2=v2:... argument; any
processor in the run can read them via
SymbolProcessorEnvironment.options.
Where generated sources go
.bleep/generated-sources/<crossName>/ksp/
├── kotlin/ # KSP-emitted .kt files (read by kotlinc on the next compile)
├── java/ # KSP-emitted .java files (read by javac in mixed compilation)
├── classes/ # pre-compiled .class files (rare; some processors emit these)
├── resources/ # KSP-emitted resources (packaged with the project)
└── caches/ # KSP's own incremental state (today: unused; see below)
The kotlin/, java/, and resources/ dirs are added to the
project's source / resource set automatically.
bleep clean wipes them.
Incrementality (current state)
KSP runs from scratch on every compile in bleep today. KSP1's
incremental mode requires the caller to track and pass changed-source
file lists explicitly. Bleep doesn't yet track per-file changes
between runs, so we run with -incremental=false. The trade-off is
slower KSP runs on large modules in exchange for not silently missing
new files. Wiring up per-file change tracking is a planned follow-up.
What works
- Real-world JVM KSP processors: Moshi codegen, Room (JVM-pure 2.7+), Hilt, Dagger, Koin KSP, kotlinx.serialization (KSP variant), kotlin-inject, Ktorfit, Wire, Moshi IR, and similar.
- Kotlin compiler plugins compose with KSP:
kotlin.compilerPlugins: [allopen, jpa, spring, noarg, serialization]runs alongside KSP processors without conflict. - Multi-project builds: when project A depends on project B, KSP for A waits on B's compile so KSP can resolve cross-project types.
What's not supported
- KAPT. Kotlin's older annotation-processing model. Migrate to KSP1; every major KAPT processor has a KSP equivalent.
- Kotlin/JS, Kotlin/Native. KSP supports them; bleep wires only the JVM target.
- Tracked-incremental KSP. As above, runs are full each compile.
Path-separator caveat
KSP's CLI accepts list arguments as :-separated on Unix and
;-separated on Windows. A project directory whose path itself
contains the platform separator (a : on macOS / Linux) will break
KSP. This is rare for normal filesystem layouts; flagged here because
the underlying tool can't escape.