O que é Annotation em Kotlin?

Annotations (anotacoes) sao metadados que você adiciona ao código para fornecer informações extras ao compilador, a ferramentas de build ou a frameworks em tempo de execução. Elas não mudam o comportamento do código diretamente, mas servem como instrucoes pra quem processa o código.

Se você já usou @Override em Java ou @Composable no Jetpack Compose, já usou annotations.

Pense em annotations como etiquetas coladas em um produto: a etiqueta não muda o produto em si, mas informa quem o manipula (o caixa, o estoquista, o transportador) sobre como trata-lo. Da mesma forma, annotations informam o compilador, frameworks e ferramentas sobre como processar determinado trecho de código.

Usando annotations existentes

class Animal {
    @Deprecated("Use falar() no lugar", ReplaceWith("falar()"))
    fun emitirSom() {
        println("Som generico")
    }

    fun falar() {
        println("Som do animal")
    }
}

fun main() {
    val animal = Animal()
    animal.emitirSom()  // Aviso: Deprecated
    animal.falar()
}

Annotations comuns do Kotlin:

  • @Deprecated – marca algo como obsoleto
  • @Suppress – suprime avisos do compilador
  • @JvmStatic – gera método estático pra interop com Java
  • @Throws – declara exceções pra interop com Java

Criando suas proprias annotations

@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class LogExecucao(val nivel: String = "INFO")

@LogExecucao(nivel = "DEBUG")
fun processarDados() {
    println("Processando...")
}

Os meta-annotations controlam o comportamento:

  • @Target – onde pode ser aplicada (classe, função, propriedade, etc.)
  • @Retention – se fica disponivel em runtime, só no binario, ou só no código fonte

Exemplo prático: validacao

@Target(AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.RUNTIME)
annotation class CampoObrigatorio

data class Formulario(
    @CampoObrigatorio val nome: String,
    @CampoObrigatorio val email: String,
    val telefone: String = ""
)

Depois, usando reflexao, da pra verificar quais campos tem @CampoObrigatorio e validar automaticamente.

Annotations com use-site targets

Em Kotlin, como propriedades geram campo, getter e setter, as vezes e necessário especificar onde a annotation vai:

class Config(
    @field:NotNull val nome: String,
    @get:JsonProperty("valor_padrao") val valorPadrao: String
)

Annotations sao fundamentais no ecossistema Kotlin, especialmente com Spring Boot, Ktor, Room e outros frameworks que dependem de metadados para funcionar.

Processando annotations com reflexao

Um uso poderoso de annotations e processa-las em tempo de execução para criar comportamentos dinâmicos. Veja como validar campos anotados automaticamente:

import kotlin.reflect.full.declaredMemberProperties
import kotlin.reflect.full.findAnnotation

@Target(AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.RUNTIME)
annotation class Obrigatorio(val mensagem: String = "Campo obrigatorio")

@Target(AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.RUNTIME)
annotation class TamanhoMaximo(val valor: Int)

data class CadastroUsuario(
    @Obrigatorio val nome: String,
    @Obrigatorio val email: String,
    @TamanhoMaximo(11) val cpf: String,
    val apelido: String = ""
)

fun <T : Any> validar(obj: T): List<String> {
    val erros = mutableListOf<String>()

    for (prop in obj::class.declaredMemberProperties) {
        val valor = prop.getter.call(obj)?.toString() ?: ""

        prop.findAnnotation<Obrigatorio>()?.let {
            if (valor.isBlank()) erros.add(it.mensagem)
        }

        prop.findAnnotation<TamanhoMaximo>()?.let {
            if (valor.length > it.valor) {
                erros.add("${prop.name} excede ${it.valor} caracteres")
            }
        }
    }
    return erros
}

Annotations para serialização e APIs

Annotations sao amplamente usadas com bibliotecas de serialização como kotlinx.serialization:

import kotlinx.serialization.Serializable
import kotlinx.serialization.SerialName
import kotlinx.serialization.json.Json

@Serializable
data class ProdutoApi(
    @SerialName("product_id") val id: Int,
    @SerialName("product_name") val nome: String,
    @SerialName("unit_price") val precoUnitario: Double
)

fun main() {
    val json = """{"product_id": 1, "product_name": "Teclado", "unit_price": 299.90}"""
    val produto = Json.decodeFromString<ProdutoApi>(json)
    println(produto.nome) // Teclado
}

Casos de Uso no Mundo Real

  • Injecao de dependência: frameworks como Koin e Hilt usam annotations como @Inject, @Module e @Singleton para configurar o grafo de dependências automaticamente.
  • Mapeamento de banco de dados: Room usa @Entity, @PrimaryKey e @ColumnInfo para mapear data classes a tabelas do banco.
  • Endpoints de API: no Spring Boot e Ktor, annotations como @GetMapping e @PostMapping mapeiam funções a rotas HTTP.
  • Testes: @Test, @BeforeEach e @ParameterizedTest do JUnit configuram a execução de testes sem código adicional.
  • Processamento em tempo de compilação: KSP processa annotations para gerar código automaticamente, eliminando boilerplate.

Boas Praticas

  • Prefira annotations com AnnotationRetention.SOURCE ou BINARY quando não precisar de reflexao em runtime, pois sao mais eficientes.
  • Use @Target para restringir onde a annotation pode ser aplicada, evitando uso incorreto.
  • Documente o comportamento esperado de annotations customizadas para que outros desenvolvedores saibam como usa-las.
  • Sempre especifique use-site targets (@field:, @get:, @param:) quando trabalhar com frameworks Java que esperam annotations em locais específicos.
  • Considere usar KSP em vez de reflexao para processar annotations, pois o processamento em tempo de compilação e mais performatico.

Erros Comuns

  • Esquecer o use-site target: ao usar annotations de frameworks Java em propriedades Kotlin, a annotation pode ir para o getter em vez do campo. Use @field: para garantir o destino correto.
  • Usar RUNTIME retention desnecessariamente: reflexao em runtime tem custo de performance. Se a annotation só e processada em compilação, use SOURCE ou BINARY.
  • Criar annotations sem @Target: sem @Target, a annotation pode ser aplicada em qualquer lugar, o que pode causar confusao. Sempre defina onde ela e válida.
  • Confundir annotation com lógica de negócio: annotations sao metadados, não devem conter lógica. A lógica deve estar no processador da annotation.

Perguntas Frequentes

Annotations afetam a performance do aplicativo? Annotations com retention SOURCE ou BINARY não afetam a performance em runtime, pois sao removidas ou não acessiveis. Apenas annotations com retention RUNTIME podem ter impacto, especialmente quando processadas via reflexao.

Posso colocar annotations em lambdas? Nao diretamente em expressoes lambda, mas você pode anotar o parametro de uma função de ordem superior que recebe a lambda, ou anotar a função que contém a lambda.

Qual a diferenca entre annotations do Kotlin e do Java? Annotations do Kotlin sao compatoveis com Java, mas Kotlin adiciona recursos como use-site targets, parametros com valor padrão e suporte nativo a arrays nos parametros da annotation.

Posso usar annotations com companion objects? Sim. Voce pode anotar membros de companion objects normalmente. Use @JvmStatic para que métodos do companion sejam acessiveis como métodos estaticos em código Java.