3 minutes
Kotlin Compile-Time Magic: Annotation Processors & KSP
Summary: A comparison of KAPT and KSP, implementation of Java- and Kotlin-based annotation processors, Gradle configuration for KSP (including incremental processing), analysis of Hilt and Room’s code generation internals, and observations on build optimization. This exploration covers DI components, DAOs, and custom boilerplate generation through Kotlin’s compile-time tools.
KAPT vs KSP: Performance & Architecture
KAPT (Kotlin Annotation Processing Tool) uses Java’s annotation processing framework by generating Java stubs for Kotlin code, resulting in slower compile times and occasional mapping issues between Kotlin and Java sources (blog.jetbrains.com, medium.com). KSP (Kotlin Symbol Processing) integrates directly with the Kotlin compiler, examining real Kotlin symbols without stub generation, typically running twice as fast as KAPT (developer.android.com, kotlinlang.org). KSP’s operation outside the JVM results in lower memory overhead and broader multi-platform support compared to KAPT’s JVM-only constraints (blog.jetbrains.com, kotlinlang.org).
Processor Implementation Notes
Java Processor with KAPT
A KAPT processor implementation using javax.annotation.processing.Processor
:
@SupportedAnnotationTypes("com.example.AutoLog")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class AutoLogProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
for (Element elem : env.getElementsAnnotatedWith(AutoLog.class)) {
// generate code with Filer
}
return true;
}
}
The stub-based approach requires JVM compilation, adding overhead and complexity (geeksforgeeks.org).
Kotlin Processor with KSP
KSP processor implementation using SymbolProcessorProvider
and SymbolProcessor
:
class MyProcessorProvider : SymbolProcessorProvider {
override fun create(env: SymbolProcessorEnvironment): SymbolProcessor {
return MyProcessor(env.codeGenerator, env.logger)
}
}
class MyProcessor(
private val codeGen: CodeGenerator,
private val logger: KSPLogger
) : SymbolProcessor {
override fun process(resolver: Resolver): List<KSAnnotated> {
val symbols = resolver.getSymbolsWithAnnotation("com.example.AutoLog")
symbols.filterIsInstance<KSClassDeclaration>().forEach { ksClass ->
val file = codeGen.createNewFile(
Dependencies(false, ksClass.containingFile!!),
ksClass.packageName.asString(),
"${ksClass.simpleName.asString()}AutoLog"
)
file.bufferedWriter().use { writer ->
writer.write("package ${ksClass.packageName.asString()}\n")
writer.write("fun log${ksClass.simpleName.asString()}() = println(\"AutoLog: ${ksClass.simpleName.asString()}\")")
}
}
return emptyList()
}
}
The Kotlin-native API provides streamlined symbol traversal and code emission (dhiwise.com, github.com).
Gradle Configuration & Incremental Processing
Gradle configuration for KSP:
plugins {
kotlin("jvm") version "1.9.20"
id("com.google.devtools.ksp") version "1.9.23-1.0.20"
}
dependencies {
implementation("com.google.devtools.ksp:symbol-processing-api:1.9.23-1.0.20")
ksp("com.example:my-processor:1.0.0")
}
KSP’s default incremental processing reduces build times by reprocessing only changed symbols. Logging configuration:
ksp.incremental=true
ksp.incremental.log=true
Build logs in build/ksp
reveal dirty sets and outputs (kotlinlang.org, github.com).
Implementation Analysis: Hilt & Room
Hilt currently implements DI codegen through KAPT, generating Dagger components and bindings. AndroidX Hilt’s 1.1.0-alpha01 release introduced KSP support, enabling faster builds and native Kotlin pipelines (developer.android.com, stackoverflow.com).
Room generates database schemas, DAO implementations, and query adapters via KAPT. Generated sources reside in build/generated/source/kapt
, with Room’s processor utilizing Google’s Compile Testing library (geeksforgeeks.org, medium.com).
Implementation Notes & Observations
- Incremental Processing: Processor support for incremental mode requires
@IncrementalProcessorType
declaration (e.g.,Aggregating
orIsolating
) insymbol-processing
configuration (kotlinlang.org). - Multi-Module Considerations: KSP and KAPT coexistence in multi-module projects requires attention to cache corruption (issue #647) and module isolation (github.com).
- Dependency Management: KSP’s
SourceDependencies
enables explicit dependency declaration, preventing unnecessary reprocessing of unchanged files. - Performance Optimization: Annotation scanning efficiency improves through package and annotation filtering, avoiding broad
getAllFiles()
calls, and utilizinggetSymbolsWithAnnotation()
(reddit.com).
Conclusion
Kotlin’s annotation processing tools (KAPT and KSP) provide automated solutions for boilerplate code generation in DI, persistence, and utilities. KSP’s implementation offers significant build-time improvements and Kotlin-native APIs, enabling efficient integration into Android development workflows.