DEV Community

vmodal_ai
vmodal_ai

Posted on

Kotlin Compiler Plugins & KSP: Building Code Generation Tools

Kotlin Compiler Plugins & KSP: Building Code Generation Tools

Large Kotlin applications often contain repetitive code for serialization, dependency injection, adapters, database mappings, and API models.

Code generation can automate this work while keeping generated code consistent.

This tutorial introduces Kotlin Symbol Processing (KSP), explains its relationship with compiler plugins, and demonstrates the architecture of an annotation-driven generator.

KSP vs Compiler Plugins

KSP is designed for inspecting Kotlin symbols and generating source code.

Compiler plugins operate deeper in the Kotlin compilation pipeline and can perform transformations at compiler levels.

Use KSP for:

  • Annotation processing
  • Source generation
  • Symbol inspection
  • Generated adapters
  • Generated registrations

Use compiler plugins when deeper compiler or IR transformations are required.

Example Annotation

Create an annotation:

@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.SOURCE)
annotation class AutoMapper
Enter fullscreen mode Exit fullscreen mode

A developer can then write:

@AutoMapper
data class User(
    val id: Long,
    val name: String
)
Enter fullscreen mode Exit fullscreen mode

The processor can generate mapping code automatically.

Processor Structure

A typical processor project:

processor/
├── AutoMapperProcessor.kt
├── AutoMapperProcessorProvider.kt
└── resources/

app/
└── models/
Enter fullscreen mode Exit fullscreen mode

Processor Provider

A provider creates the processor:

class AutoMapperProcessorProvider :
    SymbolProcessorProvider {

    override fun create(
        environment: SymbolProcessorEnvironment
    ): SymbolProcessor {
        return AutoMapperProcessor(
            environment.codeGenerator,
            environment.logger
        )
    }
}
Enter fullscreen mode Exit fullscreen mode

Processing Symbols

A processor can find declarations carrying the annotation:

class AutoMapperProcessor(
    private val codeGenerator: CodeGenerator,
    private val logger: KSPLogger
) : SymbolProcessor {

    override fun process(
        resolver: Resolver
    ): List<KSAnnotated> {

        val symbols = resolver.getSymbolsWithAnnotation(
            AutoMapper::class.qualifiedName!!
        )

        symbols
            .filterIsInstance<KSClassDeclaration>()
            .forEach { generateMapper(it) }

        return emptyList()
    }
}
Enter fullscreen mode Exit fullscreen mode

Inspecting Properties

A class declaration exposes its properties:

val properties = declaration.getAllProperties()
Enter fullscreen mode Exit fullscreen mode

The processor can inspect property names, types, annotations, visibility, and nullability.

Generating Code

KSP provides CodeGenerator for writing source files.

For larger generators, KotlinPoet can simplify safe Kotlin source generation.

Generated output might look like:

object UserMapper {

    fun map(user: User): UserDto {
        return UserDto(
            id = user.id,
            name = user.name
        )
    }
}
Enter fullscreen mode Exit fullscreen mode

Incremental Processing

Large builds benefit from incremental processing.

A good processor should:

  • Have predictable inputs
  • Generate predictable outputs
  • Avoid unnecessary file-system scanning
  • Clearly define dependencies

This helps prevent unrelated source changes from triggering expensive regeneration.

Diagnostics

Give developers useful errors:

logger.error(
    "AutoMapper can only be applied to classes",
    declaration
)
Enter fullscreen mode Exit fullscreen mode

Good diagnostics are essential for custom build tools.

Generated Code Quality

Generated source should be readable.

Prefer:

generated/
└── mapper/
    ├── UserMapper.kt
    └── OrderMapper.kt
Enter fullscreen mode Exit fullscreen mode

over massive generated files containing unrelated code.

Compiler Plugins

A compiler plugin becomes appropriate when the requirement involves deeper transformations such as:

  • Kotlin IR
  • Compiler-generated behavior
  • Language-level extensions
  • Bytecode-related transformations

Compiler plugins are more complex and can be more sensitive to compiler version changes.

Testing

Test processors with small source examples:

Input Kotlin
     ↓
KSP Processor
     ↓
Generated Kotlin
     ↓
Compilation
Enter fullscreen mode Exit fullscreen mode

Verify both generated source and successful compilation.

Design Principles

A good code-generation tool should be:

  • Deterministic
  • Fast
  • Incremental
  • Testable
  • Version-aware
  • Independent of runtime business logic

Conclusion

KSP is an excellent choice for advanced Kotlin developers who need source generation without implementing a full compiler transformation.

When deeper compiler behavior is required, compiler plugins provide additional power, but they also introduce greater complexity and maintenance requirements.

Useful Links

SDK Flutter: https://github.com/v-modal/vmodal_sdk_flutter

SDK Android: https://github.com/v-modal/vmodal_sdk_android

Discord: https://discord.gg/K72z28KUx

Top comments (0)