Se o build do seu projeto Kotlin demora mais do que deveria, há uma boa chance de que o kapt (Kotlin Annotation Processing Tool) seja o vilão. A boa notícia é que o KSP — Kotlin Symbol Processing — chegou para substituí-lo com ganhos reais de performance e uma API muito mais amigável. Neste guia, vamos explorar tudo sobre KSP: o que é, como configurar, como migrar do kapt e até como criar seu próprio processador.

O que é KSP?

KSP é uma API desenvolvida pelo Google para processar símbolos Kotlin em tempo de compilação. Diferente do kapt, que precisa gerar stubs Java para funcionar, o KSP trabalha diretamente com a árvore de símbolos do compilador Kotlin. Isso significa:

  • Builds até 2x mais rápidos — sem a etapa de geração de stubs Java
  • API nativa em Kotlin — trabalha com tipos Kotlin diretamente (nullable, extension functions, etc.)
  • Multiplatform ready — funciona com Kotlin Multiplatform, diferente do kapt que é JVM-only
  • Incrementalidade — KSP suporta processamento incremental out-of-the-box

KSP vs kapt: Comparação de Performance

A diferença de performance é significativa. Em benchmarks do próprio Google com projetos reais:

MétricakaptKSPMelhoria
Build limpo120s65s~45% mais rápido
Build incremental40s18s~55% mais rápido
Uso de memóriaAltoModerado~30% menos

O motivo principal é simples: kapt precisa rodar o compilador Java (javac) sobre stubs gerados, enquanto KSP pula essa etapa completamente. Se você usa Gradle no seu projeto Kotlin, a migração para KSP é uma das otimizações com melhor custo-benefício.

Configurando KSP no Projeto

Passo 1: Adicionar o plugin KSP

No seu build.gradle.kts do projeto raiz:

plugins {
    id("com.google.devtools.ksp") version "2.1.10-1.0.29" apply false
}

Passo 2: Aplicar no módulo

No build.gradle.kts do módulo:

plugins {
    id("org.jetbrains.kotlin.jvm")
    id("com.google.devtools.ksp")
}

dependencies {
    // Exemplo com Room
    implementation("androidx.room:room-runtime:2.7.0")
    ksp("androidx.room:room-compiler:2.7.0")

    // Exemplo com Moshi
    implementation("com.squareup.moshi:moshi:1.15.2")
    ksp("com.squareup.moshi:moshi-kotlin-codegen:1.15.2")
}

Note que onde antes usávamos kapt(...), agora usamos ksp(...). A mudança na configuração é mínima.

Passo 3: Configurar argumentos do KSP (opcional)

ksp {
    arg("room.schemaLocation", "$projectDir/schemas")
    arg("room.incremental", "true")
    arg("room.generateKotlin", "true")
}

Migrando do kapt para KSP

A migração é geralmente simples, mas exige atenção. Aqui está o checklist:

1. Verifique se suas bibliotecas suportam KSP:

BibliotecaSuporte KSPDesde
Room2.4.0
Moshi1.13.0
Hilt/Dagger2.48
Glide4.14.0
Koin Annotations1.1.0
MapStruct⚠️ Parcial

2. Substitua as dependências:

// Antes (kapt)
plugins {
    kotlin("kapt")
}
dependencies {
    kapt("androidx.room:room-compiler:2.7.0")
}

// Depois (KSP)
plugins {
    id("com.google.devtools.ksp")
}
dependencies {
    ksp("androidx.room:room-compiler:2.7.0")
}

3. Remova o plugin kapt se nenhuma outra dependência precisar dele:

// Remova esta linha do plugins {}
kotlin("kapt")

4. Limpe e rebuild:

./gradlew clean build

Se você está migrando um projeto Spring Boot com Kotlin, atenção: o Dagger/Hilt já suporta KSP, mas verifique a compatibilidade com o Spring annotation processing.

Bibliotecas Populares com Suporte KSP

Room (Android)

Room foi uma das primeiras bibliotecas a adotar KSP. Com a flag room.generateKotlin = true, o código gerado é Kotlin puro:

@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun userDao(): UserDao
}

@Entity
data class User(
    @PrimaryKey val id: Int,
    val name: String,
    val email: String
)

@Dao
interface UserDao {
    @Query("SELECT * FROM user")
    suspend fun getAll(): List<User>

    @Insert
    suspend fun insert(user: User)
}

Moshi (Serialização JSON)

Moshi com KSP gera adaptadores JSON sem reflection:

@JsonClass(generateAdapter = true)
data class ApiResponse(
    @Json(name = "status_code") val statusCode: Int,
    val message: String,
    val data: List<Item>
)

Criando um Processador KSP Simples

Vamos criar um processador que gera automaticamente um método toMap() para data classes anotadas. Isso é útil para entender como KSP funciona por dentro — e é o tipo de conhecimento que impressiona em entrevistas técnicas de Kotlin.

Passo 1: Definir a anotação

// annotations/src/main/kotlin/ToMap.kt
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.SOURCE)
annotation class ToMap

Passo 2: Criar o processador

// processor/src/main/kotlin/ToMapProcessor.kt
class ToMapProcessor(
    private val codeGenerator: CodeGenerator,
    private val logger: KSPLogger
) : SymbolProcessor {

    override fun process(resolver: Resolver): List<KSAnnotated> {
        val symbols = resolver
            .getSymbolsWithAnnotation("com.example.ToMap")
            .filterIsInstance<KSClassDeclaration>()

        symbols.forEach { classDeclaration ->
            if (classDeclaration.classKind != ClassKind.CLASS) {
                logger.error("@ToMap só pode ser aplicado em classes", classDeclaration)
                return@forEach
            }
            generateToMap(classDeclaration)
        }

        return emptyList()
    }

    private fun generateToMap(classDecl: KSClassDeclaration) {
        val className = classDecl.simpleName.asString()
        val packageName = classDecl.packageName.asString()
        val properties = classDecl.getAllProperties()

        val file = codeGenerator.createNewFile(
            Dependencies(true, classDecl.containingFile!!),
            packageName,
            "${className}Extensions"
        )

        file.bufferedWriter().use { writer ->
            writer.write("package $packageName\n\n")
            writer.write("fun $className.toMap(): Map<String, Any?> = mapOf(\n")

            properties.forEach { prop ->
                val propName = prop.simpleName.asString()
                writer.write("    \"$propName\" to this.$propName,\n")
            }

            writer.write(")\n")
        }
    }
}

class ToMapProcessorProvider : SymbolProcessorProvider {
    override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
        return ToMapProcessor(environment.codeGenerator, environment.logger)
    }
}

Passo 3: Usar a anotação

@ToMap
data class Product(
    val id: Long,
    val name: String,
    val price: Double
)

// Código gerado automaticamente pelo KSP:
// fun Product.toMap(): Map<String, Any?> = mapOf(
//     "id" to this.id,
//     "name" to this.name,
//     "price" to this.price,
// )

fun main() {
    val product = Product(1, "Kotlin in Action", 49.90)
    println(product.toMap())
    // {id=1, name=Kotlin in Action, price=49.9}
}

Essa abordagem com extension functions geradas por KSP é um padrão poderoso que combina bem com DSLs em Kotlin.

Dicas e Boas Práticas

  1. Sempre prefira KSP ao kapt em projetos novos — não há razão para usar kapt em 2026
  2. Use processamento incremental — KSP suporta nativamente, mas certifique-se de declarar as dependências corretas no Dependencies(...)
  3. Teste seus processadores — use a biblioteca com.google.devtools.ksp:symbol-processing-api para testes unitários
  4. Evite misturar kapt e KSP no mesmo módulo — funciona, mas perde parte dos ganhos de performance
  5. Monitore os tempos de build — use ./gradlew --scan para verificar se a migração realmente melhorou

Quando Ainda Usar kapt?

Existem raros cenários onde o kapt ainda é necessário:

  • Bibliotecas Java-only que não migraram para KSP (cada vez mais raro)
  • Processadores que dependem de APIs específicas do javax.annotation.processing
  • Projetos legados em fase de migração gradual

Mas a tendência é clara: o kapt está em modo de manutenção e o KSP é o futuro. Se você está construindo APIs com Ktor ou projetos Kotlin Multiplatform, KSP é a escolha natural.

Conclusão

KSP não é apenas uma melhoria incremental sobre o kapt — é uma mudança de paradigma no processamento de anotações em Kotlin. Com builds mais rápidos, uma API Kotlin-native e suporte a multiplatform, não há motivo para não migrar. Se você quer mergulhar mais fundo no ecossistema Kotlin, explore nossos guias de design patterns e testes com JUnit5 e MockK.

Para quem vem de outras linguagens, vale comparar a abordagem de geração de código do Kotlin com as macros procedurais do Rust ou os geradores de código em Go com go generate. Já em Python, metaclasses e decorators cumprem um papel semelhante ao KSP para geração dinâmica de código.