Writing Your First SymbolProcessor
Implement a KSP processor that scans for annotated classes and logs them.
Writing Your First SymbolProcessor is a free Kotlin Academy lesson on CoddyKit — lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Kotlin Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Project Structure
A KSP processor lives in a separate Gradle module (e.g., :processor). The consuming module applies the KSP plugin and declares a ksp dependency on the processor module. The processor module itself has a regular implementation dependency on the KSP API.
Adding KSP Dependencies
In your processor module's build.gradle.kts:
plugins { kotlin("jvm") }
dependencies {
implementation("com.google.devtools.ksp:symbol-processing-api:2.0.0-1.0.21")
}The SymbolProcessor Interface
Implement SymbolProcessor. The main entry point is process(resolver: Resolver): List. Return symbols you could not process (e.g., those whose dependencies haven't resolved yet) for a second round.
import com.google.devtools.ksp.processing.*
import com.google.devtools.ksp.symbol.*
class MyProcessor(private val logger: KSPLogger,
private val codeGenerator: CodeGenerator) : SymbolProcessor {
override fun process(resolver: Resolver): List<KSAnnotated> {
val symbols = resolver.getSymbolsWithAnnotation("com.example.MyAnnotation")
// process symbols here
return emptyList()
}
}The SymbolProcessorProvider
KSP discovers your processor through a SymbolProcessorProvider. Register it in resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider:
class MyProcessorProvider : SymbolProcessorProvider {
override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor =
MyProcessor(
logger = environment.logger,
codeGenerator = environment.codeGenerator
)
}The Service Registration File
Create the file at the exact path in your processor module's resources:
- Path:
src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider - Content: the fully qualified name of your
SymbolProcessorProviderclass
Resolving Symbols by Annotation
Use resolver.getSymbolsWithAnnotation(fqn) to get all declarations annotated with your annotation. Filter to the type of declaration you expect (e.g., class declarations):
val classes = resolver
.getSymbolsWithAnnotation("com.example.MyAnnotation")
.filterIsInstance<KSClassDeclaration>()Validating Symbols
Before processing, validate that each symbol is fully resolvable. A symbol whose type references haven't been compiled yet is not valid. Return such symbols from process() to retry them in the next round:
val (valid, deferred) = classes.partition { it.validate() }
// process valid; return deferredVisiting a Class Declaration
Use the KSVisitorVoid visitor pattern to traverse a class's structure. Override visitClassDeclaration to access properties, functions, and nested classes:
class MyVisitor : KSVisitorVoid() {
override fun visitClassDeclaration(classDeclaration: KSClassDeclaration, data: Unit) {
val name = classDeclaration.simpleName.asString()
val props = classDeclaration.getAllProperties().toList()
println("Class: $name, props: ${props.size}")
}
}Logging from a Processor
Use KSPLogger to emit messages at various levels. logger.error() fails the build; logger.warn() prints a warning; logger.info() prints an informational message visible with --info.
logger.info("Processing class: ${classDeclaration.simpleName.asString()}")
logger.error("Missing required annotation", classDeclaration)Accessing Annotations on a Symbol
Each KSDeclaration has an annotations sequence. Use filter and arguments to read annotation values:
val ann = classDeclaration.annotations
.first { it.shortName.asString() == "MyAnnotation" }
val value = ann.arguments.first { it.name?.asString() == "value" }.value as StringIncremental Processing Hints
Tell KSP which output files depend on which input symbols by associating them via CodeGenerator.createNewFile(dependencies = ...). This enables KSP to skip your processor when none of its inputs changed.
Quick Check
How does KSP discover your SymbolProcessorProvider implementation?
Recap: Writing Your First SymbolProcessor
Key takeaways:
- Implement
SymbolProcessorandSymbolProcessorProviderin a separate module - Register the provider via
META-INF/services/ - Use
resolver.getSymbolsWithAnnotation()to find annotated symbols - Validate symbols before processing; return unresolved ones for retry
- Use
KSPLoggerfor build-time messages andCodeGeneratorto write files
Frequently asked questions
Is the “Writing Your First SymbolProcessor” lesson free?
Yes — the full text of “Writing Your First SymbolProcessor” is free to read here on the web, and the Kotlin Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Kotlin Academy course, upgrade to CoddyKit PRO.
What will I learn in “Writing Your First SymbolProcessor”?
Implement a KSP processor that scans for annotated classes and logs them. You practise Kotlin Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Kotlin Academy?
No prior experience is required. Kotlin Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Writing Your First SymbolProcessor” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Kotlin Academy lesson?
Yes. Every Kotlin Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- KSP vs KAPT: Why KSP Is Faster
- Writing Your First SymbolProcessor
- Generating Kotlin Source Files with KotlinPoet
- Integrating KSP Processors into a Gradle Build