name: micronaut-sourcegen description: Add, integrate, or review Micronaut Sourcegen usage in modules that generate Java source, Kotlin source, Groovy-compatible source, or bytecode from ObjectDef, TypeDef, MethodDef, ExpressionDef, StatementDef, and SourceGenerator APIs. license: Apache-2.0 compatibility: Micronaut framework and library modules that use io.micronaut.sourcegen for compile-time generation metadata: author: Denis Stepanov version: "1.0.0"
Micronaut Sourcegen
Use this skill when a Micronaut module wants to generate types with Micronaut Sourcegen instead of handwritten source strings, reflection, JavaPoet/KotlinPoet calls in visitors, or ad hoc bytecode. The target module may emit Java source, Kotlin source, Groovy-compatible source, or bytecode, but the generation logic should model the type once with io.micronaut.sourcegen.model.
Goal
Help other modules adopt Micronaut Sourcegen safely: choose the right backend, wire the right processor dependencies, build language-neutral model definitions, and verify generated Java/Kotlin/bytecode behavior with targeted tests.
Trigger Examples
Should trigger:
- "Use Micronaut Sourcegen in this module to generate a repository implementation."
- "Generate bytecode instead of Java source for this visitor."
- "Make this annotation processor generate Java and Kotlin outputs with Sourcegen."
- "Replace handwritten generated-source strings with
ClassDefandMethodDef." - "Add method bodies with switches, static calls, static field arguments, and constructor calls."
Should not trigger:
- "Only edit user guide text."
- "Only change Gradle publishing metadata."
- "Only refactor backend writer internals without a module consuming Sourcegen."
- "Explain Micronaut annotation processing without generated types."
Procedure
- Identify the consuming module and generation target.
- Choose one backend for each compilation path.
- Wire dependencies using the repository version catalogs and processor configurations.
- Implement generation in a visitor with Sourcegen model builders and helpers.
- Emit through
SourceGenerator.write(...)or bytecode writer APIs as appropriate. - Add cross-target tests for the generated contract.
- Run focused compile/test/style verification.
1) Identify the Consuming Module
- Confirm whether the module is an annotation processor/generator module, an application/test-suite module consuming a generator, or a backend writer module.
- Locate existing visitor registration under
META-INF/services/io.micronaut.inject.visitor.TypeElementVisitor. - Identify source language paths:
- Java annotation processing uses
annotationProcessor. - Kotlin compilation usually uses
ksp. - Bytecode generation runs during Java-language processing and writes class files directly.
- Java annotation processing uses
- Inspect existing generated-type tests before changing behavior.
2) Choose the Backend
- Use
sourcegen-generator-javawhen the module should emit.javasource forVisitorContext.Language.JAVA; it also providesGroovyPoetSourceGeneratorforGROOVY. - Use
sourcegen-generator-kotlinwhen Kotlin/KSP processing should emit.ktsource forVisitorContext.Language.KOTLIN. - Use
sourcegen-generator-bytecodewhen Java-language processing should emit.classfiles directly throughByteCodeGenerator. - Do not put both Java-source and bytecode Sourcegen generators on the same Java annotation-processor path unless the module intentionally owns selection. Both advertise
VisitorContext.Language.JAVA, andSourceGenerators.findByLanguage(JAVA)expects a single effective backend. - Use
sourcegen-bytecode-writerdirectly only when implementing or testing bytecode writer behavior, not as the normal integration point for consuming annotation processors.
3) Wire Dependencies
- In a generator/processor module, depend on
sourcegen-generatorfor the model and service lookup APIs. Addsourcegen-annotationsonly when the module consumes Sourcegen annotations such as builders/wither support. - In a Java consumer/test module, add the chosen backend and the custom generator to
annotationProcessor. - In a Kotlin consumer/test module, add the Kotlin backend and KSP-compatible custom generator to
ksp. - In a bytecode consumer/test module, add
sourcegen-generator-bytecodetoannotationProcessor. - Use Gradle version catalogs and existing project accessors. Do not hardcode dependency coordinates or versions.
Example repository-local shapes:
dependencies {
implementation(projects.sourcegenGenerator)
annotationProcessor(projects.sourcegenGeneratorJava)
annotationProcessor(projects.myCustomGenerators)
}
dependencies {
ksp(projects.sourcegenGeneratorKotlin)
ksp(projects.myCustomGeneratorsKotlin)
}
dependencies {
annotationProcessor(projects.sourcegenGeneratorBytecode)
annotationProcessor(projects.myCustomGenerators)
}
4) Build Models With Factories, Not Record Constructors
- Always create Sourcegen model elements through builders, factory methods, or helper methods:
ClassDef.builder,InterfaceDef.builder,RecordDef.builder,EnumDef.builder,AnnotationObjectDef.builder,MethodDef.builder,MethodDef.constructor,FieldDef.builder,PropertyDef.builder,ParameterDef.builder,TypeDef.of,ClassTypeDef.of,TypeDef.parameterized,ExpressionDef.constant,StatementDef.multi,StatementDef.doTry,expr.returning,expr.invoke,type.invokeStatic,type.getStaticField, and switch/if helper methods. - Do not instantiate DSL records directly in consuming modules, for example avoid
new ClassTypeDef.Parameterized(...),new TypeDef.Array(...),new ExpressionDef.Constant(...),new StatementDef.Switch(...), or othernew ...record(...)forms. - If a construct has no public helper yet, add or use a small named helper/factory at the nearest appropriate level instead of spreading direct record construction through module code.
- Keep model construction language-neutral. Do not call JavaPoet, KotlinPoet, ASM, or write source strings from a visitor unless the task is explicitly inside a backend writer.
5) Implement Visitors
- Prefer
TypeElementVisitorwithVisitorKind.ISOLATINGwhen generated output depends only on the visited element. - Resolve the backend through
SourceGenerators.findByLanguage(context.getLanguage()).orElse(null)and return when none is available. - Build
ObjectDefinstances from Micronaut AST metadata (ClassElement,MethodElement,FieldElement,PropertyElement) when possible. - Emit with
sourceGenerator.write(objectDef, context, originatingElement). - Use
SourceGenerators.handleFatalException(...)when generation may postpone to a later round.
Minimal visitor shape:
SourceGenerator sourceGenerator = SourceGenerators.findByLanguage(context.getLanguage()).orElse(null);
if (sourceGenerator == null) {
return;
}
ClassDef generated = ClassDef.builder(element.getPackageName() + ".Generated")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(MethodDef.builder("name")
.addModifiers(Modifier.PUBLIC)
.returns(TypeDef.STRING)
.build((aThis, params) -> ExpressionDef.constant("generated").returning()))
.build();
sourceGenerator.write(generated, context, element);
6) Model Generated Behavior
- Use
ClassDef,InterfaceDef,RecordDef,EnumDef, andAnnotationObjectDeffor generated type kind. - Use
TypeDefandClassTypeDeffor primitives, arrays, generics, generated class names, nullability, and static field owners. - Use
MethodDefbody builders for executable logic. Prefer explicitreturns(...)for public or non-trivial generated methods. - When generated code reads or writes a field that the same generator defines, keep the
FieldDefinstance and reference it withaThis.field(fieldDef)orgeneratedType.getStaticField(fieldDef)instead of repeating the field name and type. - When generated code invokes a method that the same generator defines, keep the
MethodDefinstance and invoke it withreceiver.invoke(methodDef, ...)orgeneratedType.invokeStatic(methodDef, ...)instead of repeating the method name, parameter types, and return type. - When generated code targets an existing method or field that is accessible reflectively and present on the annotation processor classpath, prefer a reflective handle from
ReflectionUtils.getRequiredMethod(...)orReflectionUtils.getRequiredField(...). Pass method handles toreceiver.invoke(method, ...)ortype.invokeStatic(method, ...); pass static field handles totype.getStaticField(field); for instance fields, centralize the field-name/type bridge in a helper fed by the reflectiveField. This uses reflection only at generation time to describe a known member; it must not generate runtime reflection. - Use
StatementDef.multi(...),expr.returning(),expr.newLocal(...),condition.doIf(...),expr.asExpressionSwitch(...),expr.asStatementSwitch(...),StatementDef.doTry(...), and invocation helpers to compose bodies. - Use
ClassTypeDef.getStaticField("NAME", valueType)for enum constants, static constants, class literals, and static-field arguments passed to invocations. - String switches are supported. For string switch cases, use a
TypeDef.STRINGswitch expression andExpressionDef.constant("case")keys withasExpressionSwitch(...)orasStatementSwitch(...).
7) Verify
- Compile the generator module and the consuming test module.
- Run targeted tests that load or compile generated types for each backend touched.
- For Java source generation, verify generated
.javabehavior through Java test-suite tests. - For Kotlin source generation, verify KSP and Kotlin tests.
- For bytecode generation, verify runtime behavior and bytecode writer tests when the model shape exercises bytecode-specific paths.
- Finish source changes with the repository checks required by
AGENTS.md: affected compile tasks, targeted tests, full affected module tests,./gradlew -q cM, and./gradlew -q spotlessCheckif new files were added.
Backend Notes
- Java source backend:
JavaPoetSourceGeneratorwrites Java source forVisitorContext.Language.JAVA. - Groovy-compatible source backend:
GroovyPoetSourceGeneratorreuses the Java source generator forGROOVY. - Kotlin source backend:
KotlinPoetSourceGeneratorwrites Kotlin source forVisitorContext.Language.KOTLIN. - Bytecode backend:
ByteCodeGeneratoradvertisesJAVAand writes class files throughcontext.visitClass(...); it does not supportwrite(ObjectDef, Writer).
Cookbook
Use references/sourcegen-cookbook.md for adoption patterns covering:
- Gradle dependency placement for generator modules and consumers,
- backend selection for Java, Kotlin, Groovy-compatible source, and bytecode,
- type/model construction using builders and helper methods,
- generated classes, interfaces, records, enums, annotations, method bodies, switches, invocations, static fields, arrays, lambdas, and super constructors,
- testing and validation across source and bytecode backends.
Guardrails
- Do not instantiate Sourcegen DSL records directly in consuming modules; use builders, factories, or named helper methods.
- Do not duplicate generated member metadata in generated code bodies; prefer the existing
FieldDeforMethodDefinstance when reading/writing generated fields or invoking generated methods. - Do not use string-based references for known existing classpath methods/static fields when a
ReflectionUtils.getRequiredMethod(...)orReflectionUtils.getRequiredField(...)handle can describe the member safely. - Do not generate source text with string concatenation when a Sourcegen model construct exists.
- Do not place multiple
JAVASourcegen backends on the same annotation-processor path unless selection is explicit and tested. - Do not use backend-specific JavaPoet/KotlinPoet/ASM APIs from normal visitors.
- Do not use reflection for generation metadata when Micronaut AST elements are available.
- Do not break public Sourcegen APIs without explicit approval and compatibility checks.
Validation Checklist
- Skill folder name and
name:frontmatter aremicronaut-sourcegen. - Consuming module and target backend are identified.
- Java/Kotlin/bytecode processor dependencies are scoped correctly.
- Only one effective
JAVASourcegen backend is present for a Java compilation path. - Model definitions use builders/factories/helpers, not direct DSL record constructors.
- Generated fields/methods referenced from generated bodies are referenced through their
FieldDef/MethodDefinstances where available. - Generated sources/classes are emitted through
SourceGenerator.write(...)or backend writer APIs where appropriate. - Targeted and full affected tests pass, or unrun checks are reported clearly.
References
references/sourcegen-cookbook.md