Currículo de um desenvolvedor Kotlin
O que se espera de um engenheiro Kotlin no backend: a linguagem e a plataforma JVM, Spring Boot, persistência, concorrência e operação.
Publicada em · 17 min de leitura
Kotlin chegou ao backend pelo JVM. Ele roda sobre o mesmo ecossistema do Java (Spring Boot, Hibernate, Micrometer, Gradle), e por isso um engenheiro Kotlin precisa dominar duas coisas ao mesmo tempo: a linguagem em si e a plataforma que ela usa por baixo. A sintaxe mais enxuta, a null safety verificada pelo compilador e as coroutines são os motivos mais citados para a adoção. Em muitas equipes, o Kotlin já é a linguagem padrão de serviços novos, mesmo quando o legado continua em Java.
A sintaxe se aprende em semanas. O que leva mais tempo é o idiomatismo, ou seja, escrever o que a linguagem oferece e não o que se trazia de outra. Quem traz hábitos de Java escreve código que compila, mas não aproveita a linguagem: usa !! em todo lugar, bloqueia threads dentro de coroutines, cria GlobalScope para “resolver” escopo e trata data class como entidade de banco sem pensar no que acontece com equals e hashCode. Esse é o equivalente do “Java em Go” que a trilha de Go aponta como red flag. Este currículo existe para evitar esses vícios desde o começo.
Esta nota define o que se espera de um engenheiro Kotlin backend em processos seletivos e no dia a dia de uma equipe, organizado em fases que se apoiam umas nas outras. A stack de referência é a do projeto: Spring Boot com Gradle, com starters em todos os serviços, PostgreSQL com Liquibase, MongoDB e Azure Service Bus para mensageria. A ordem importa: sem entender null safety e coroutines, a parte de APIs vira cópia de exemplo sem compreensão.
O mapa do currículo
| Fase | Domínio técnico | Habilidades centrais | Entrega prática recomendada |
|---|---|---|---|
| 1 | Kotlin idiomático e sistema de tipos | Null safety, data class, sealed, when exaustivo, funções de escopo, interop com Java | Modelo de domínio de pedidos com estados sealed e nenhum !! |
| 2 | Orientação a objetos, genéricos e coleções | Delegação, variância, reified, coleções imutáveis, Sequence, tratamento funcional de erros | Leitor de arquivos grandes em streaming, sem carregar tudo em memória |
| 3 | Internals da JVM e performance | Bytecode, heap e GC, JIT, boxing, startup, profiling com JFR | Diagnóstico de alocação excessiva em um serviço com carga simulada |
| 4 | Coroutines e concorrência estruturada | suspend, CoroutineScope, Dispatchers, cancelamento, Flow, Channel, Mutex | Worker com limite de concorrência, timeout e cancelamento limpo |
| 5 | APIs, frameworks e mensageria | Spring Boot com starters, validação, ProblemDetail, JWT, Azure Service Bus | API REST com erros padronizados, autenticação e consumidor de mensagens |
| 6 | Persistência e dados | JDBC, HikariCP, JPA/Hibernate, N+1, Liquibase, Spring Data MongoDB | Repositório Postgres com changelogs versionados e prova de ausência de N+1 |
| 7 | Arquitetura, testes e operação | Gradle multi-módulo, hexagonal, Kotest, MockK, Testcontainers, Micrometer, OpenTelemetry, Docker | Serviço completo com testes de integração, métricas e imagem enxuta |
Os 7 pilares técnicos fundamentais
1. Kotlin idiomático e sistema de tipos
A maior parte do valor de Kotlin está em tornar erros de programação visíveis para o compilador. Um engenheiro que domina a linguagem usa o sistema de tipos como primeira linha de defesa contra erros.
Competências exigidas:
- Null safety: distinguir tipos nuláveis (
String?) de não nuláveis, usar?.,?:eletcom intenção, e entender por que!!é um sinal de modelagem fraca. Saber também como o compilador trata tipos vindos do Java (os chamados platform types), que podem ser nulos sem aviso. - Tipos de dados:
data classpara transportar valores,enum classpara conjuntos fechados,sealed classesealed interfacepara modelar estados, comwhenexaustivo que dispensa oelsee faz o compilador avisar quando um caso novo é adicionado. value classpara envolver tipos primitivos (umOrderIdque não pode ser confundido com umUserId) sem custo de alocação na maior parte dos casos.- Funções de escopo (
let,run,apply,also,with): saber qual escolher pelo papel que a função tem, transformar, configurar ou observar, e reconhecer quando o encadeamento deixa o código menos legível. - Expressões em vez de instruções:
if,whenetryproduzem valores, e um engenheiro idiomático os usa para inicializar variáveis comval. - Interop com Java:
@JvmStatic,@JvmOverloads, como a classeOptionale as exceções verificadas do Java aparecem no Kotlin e como expor APIs Kotlin de forma amigável para quem ainda chama pelo Java.
Entrega sugerida: modelar um fluxo de pagamento com estados Pending, Approved, Declined e Refunded usando sealed interface, sem nenhum !! e com um when que cubra todos os casos.
2. Orientação a objetos, genéricos e coleções
Kotlin mantém a orientação a objetos do JVM, mas incentiva composição, imutabilidade e funções de ordem superior. Um engenheiro sênior sabe quando cada estilo serve.
Competências exigidas:
- Interfaces com implementação padrão e delegação com
by, usadas para compor comportamento sem herança profunda. - Classes abertas exigem decisão consciente: classes são
finalpor padrão. Saber quando liberar comopene por que frameworks como Spring e Hibernate dependem de classes e métodos não finais (o que os pluginskotlin-springekotlin-jparesolvem). - Variância de genéricos (
ineout), a diferença entreList<out T>eMutableList<T>, e funçõesinlinecom parâmetrosreifiedpara acessar o tipo em tempo de execução. - Coleções imutáveis por padrão.
Listé somente leitura;MutableListé a exceção. Saber a diferença entreListeSequence:Listavalia cada etapa de uma vez e aloca coleções intermediárias, enquantoSequenceprocessa elemento por elemento. - Tratamento de erros funcional:
runCatching,Resulte, quando o projeto já usa, a biblioteca Arrow comEither. Saber também quando exceções continuam sendo a escolha certa, como em erros de infraestrutura. - Custo de lambdas: entender que funções
inlineeliminam a alocação do lambda, e quando isso importa de fato.
Entrega sugerida: uma biblioteca que lê CSV com Sequence a partir de um BufferedReader, com teste que comprova o processamento em streaming em um arquivo de vários gigabytes.
3. Internals da JVM e performance
Um desenvolvedor que escreve Kotlin para servidores precisa entender o que o compilador gera e o que a máquina virtual faz com esse código. Sem isso, diagnósticos de latência e memória viram tentativa e erro.
Competências exigidas:
- Kotlin compila para bytecode JVM. Saber ler o resultado com
javape com o decompilador do IntelliJ, e identificar o que vira objeto, o que vira método estático e o que é gerado para propriedades e lambdas. - Modelo de memória: heap, pilha de threads e a diferença entre objetos alocados e objetos que sobrevivem a uma coleta. Entender como a análise de escape do compilador JIT (C2) remove alocações quando consegue provar que um objeto não sai do método.
- Garbage collector: G1 como padrão, ZGC e Shenandoah para baixa latência, e como ler pausas em logs de GC. Saber que a escolha de coletor muda o perfil de latência, não o volume de trabalho.
- Boxing:
Intpode virarjava.lang.Integerem coleções genéricas e em tipos nuláveis, o que cria alocações escondidas em laços quentes. - Startup e footprint: o custo de carregamento de classes e de inicialização do Spring em contêineres e serverless, e como reduzi-lo com CDS, AOT e GraalVM Native Image, com as trocas que cada opção impõe.
- Profiling com JFR (Java Flight Recorder), async-profiler e análise de heap dump com o Eclipse MAT ou o JDK Mission Control.
Entrega sugerida: um serviço com alocação excessiva em um endpoint quente, diagnosticado com JFR, corrigido e medido antes e depois com um benchmark simples de JMH.
4. Coroutines e concorrência estruturada
Este é o pilar que mais separa o engenheiro Kotlin júnior do sênior no backend. Coroutines são uma forma de escrever código assíncrono como se fosse sequencial, mas o modelo só funciona bem quando o engenheiro entende o ciclo de vida que ele cria.
Competências exigidas:
- Funções
suspende a transformação que o compilador faz em máquina de estados (continuation). Saber que uma funçãosuspendnão cria thread por si só. CoroutineScopee concorrência estruturada: um pai espera pelos filhos e propaga cancelamento e exceções para cima. Diferença entrecoroutineScope(falha tudo quando um filho falha) esupervisorScope(isola falhas entre filhos).Dispatchers:Defaultpara CPU,IOpara bloqueio, elimitedParallelismpara controlar concorrência. Nunca bloquearDispatchers.Default, porque isso trava os poucos threads que ele tem.- Cancelamento cooperativo: uma coroutine só para em um ponto de suspensão ou quando verifica
isActiveou chamaensureActive(). EngolirCancellationExceptionem umcatchgenérico quebra o cancelamento, efinallycomwithContext(NonCancellable)é o caminho para limpeza. Flowfrio (cada coletor recebe a sequência desde o início) e fluxos quentes (StateFlow,SharedFlow). Saber quando usarChannelpara comunicar produtores e consumidores e quandoMutexeSemaphoresão a ferramenta certa para proteger um recurso.- Virtual threads do Java 21 como alternativa ao modelo de coroutines em código bloqueante. Saber onde cada um se encaixa e que os dois podem coexistir no mesmo serviço.
- Testes de coroutines com
runTeste tempo virtual, semThread.sleepe sem testes intermitentes.
Erros que um revisor procura de imediato: GlobalScope para tarefas que deveriam pertencer a uma requisição, runBlocking dentro de um handler HTTP, chamadas JDBC bloqueantes dentro de Dispatchers.Default e launch sem nenhum tratamento de exceção.
Entrega sugerida: um worker que consome uma fila, limita a concorrência com um Semaphore, aplica timeout por tarefa e termina sem deixar coroutines órfãs, com testes em runTest.
5. APIs, frameworks e comunicação
Na stack do projeto, o framework é o Spring Boot, e todos os serviços partem dos starters. O foco desta fase é usar bem o que a equipe já adotou: entender o que cada starter configura, como o Spring conecta os componentes e onde ele esconde custos que precisam ser conhecidos.
Competências exigidas:
- Spring Boot 3 ou superior com Gradle: starters como base de cada serviço, configuração por ambiente com
application.yml, profiles e@ConfigurationProperties. - Controllers, injeção de dependências, validação com Bean Validation e tratamento centralizado de erros.
- Erros padronizados com
ProblemDetail(RFC 9457) em vez de respostas improvisadas. Saber mapear exceções de domínio para status HTTP sem vazar detalhes internos. - Controllers
suspendno Spring MVC e a diferença entre Spring MVC com coroutines e Spring WebFlux, que tem seu próprio modelo reativo. - Serialização com Jackson e o módulo Kotlin, que é o padrão do Spring. Saber como ele trata valores nulos e campos ausentes, e quando
kotlinx.serializationé usado em partes do código que não são Spring. - Segurança com Spring Security: resource server com JWT, validação de claims e autorização por método.
- Documentação com OpenAPI pelo springdoc, WebSockets e Server-Sent Events para streaming.
- Mensageria com Azure Service Bus: filas e tópicos com assinaturas, modos de recebimento (peek lock e receive and delete), finalização explícita das mensagens, dead letter queue e reprocessamento. O consumidor precisa ser idempotente, porque o Service Bus entrega cada mensagem pelo menos uma vez.
- Idempotência como regra de projeto: chave de deduplicação, registro do processamento no banco e comportamento previsível quando a mesma mensagem chega duas vezes.
Entrega sugerida: uma API REST de pedidos com validação, erros em ProblemDetail, autenticação JWT e um consumidor do Azure Service Bus que ignora mensagens já processadas.
6. Persistência e dados
Serviços Kotlin no backend passam a maior parte do tempo falando com banco de dados. Saber o que cada camada faz por baixo evita problemas que só aparecem em produção.
Competências exigidas:
- JDBC e HikariCP: tamanho do pool, tempo máximo de espera, detecção de vazamento de conexão e a relação entre threads, conexões e o limite do banco.
- JPA com Hibernate: carregamento lazy, o problema N+1 e as soluções com
fetch joine@EntityGraph, e o controle de transações com@Transactionale seu proxy. - Entidades JPA e
data class: umadata classgeraequals,hashCodeetoStringa partir de todos os campos, o que pode disparar carregamento lazy e recursão em relacionamentos bidirecionais. Entidades pedemclasscomum com identidade explícita. - Migrations com Liquibase: changelogs, changesets, identificadores únicos, checksums e o cuidado de nunca editar um changeset já aplicado em ambientes compartilhados. Saber também usar
contextspara separar dados de teste de dados de produção. - MongoDB com Spring Data MongoDB: documentos,
@Document,MongoTemplatepara consultas complexas e a diferença de modelagem em relação ao relacional. Embutir ou referenciar documentos é uma decisão que depende de como os dados são lidos e atualizados. - Índices no MongoDB e análise de consultas com
explain, para não deixar listagens fazendo varredura completa da coleção. - Transações no MongoDB, que exigem replica set, e a escolha consciente entre o que fica no Postgres e o que fica no MongoDB em um mesmo serviço.
- Cache com Spring Cache, com política de expiração e invalidação consistente.
Entrega sugerida: um repositório Postgres com changelogs do Liquibase e um teste de integração que comprova, pelo log de SQL, que a listagem de pedidos executa uma única consulta. Em seguida, um repositório MongoDB com índices definidos e uma consulta verificada com explain.
7. Arquitetura, testes e operação
Um serviço que vai durar anos precisa de estrutura clara, testes que confiam no que testam e observabilidade desde o primeiro dia.
Competências exigidas:
- Gradle com Kotlin DSL, projetos multi-módulo e catálogo de versões (
libs.versions.toml). - Arquitetura hexagonal ou Clean Architecture com domínio sem dependência de framework, verificada por regras automáticas com ArchUnit.
- Testes com JUnit 5, Kotest para asserções e testes de propriedade, MockK para dublês em Kotlin (que lida melhor com
finale funções de extensão do que o Mockito) e Testcontainers para Postgres e MongoDB reais. Para o Azure Service Bus, o teste de integração precisa de uma estratégia própria, seja um dublê da fila ou um ambiente de teste dedicado. - Qualidade estática com ktlint, detekt e cobertura com Kover. A meta de cobertura importa menos do que o que os testes realmente verificam.
- Observabilidade com Micrometer para métricas, OpenTelemetry para traces e logs estruturados em JSON com SLF4J e Logback.
- Resiliência com Resilience4j: circuit breaker, retry com backoff exponencial e jitter, e limites de concorrência por dependência.
- Empacotamento: imagens com JRE mínima,
jlink, camadas do Spring Boot para acelerar builds de contêiner e, quando faz sentido, GraalVM Native Image.
Entrega sugerida: um serviço completo com testes de integração contra Postgres e MongoDB reais via Testcontainers, métricas expostas, logs estruturados e uma imagem Docker de tamanho controlado.
Roteiro prático de aprendizado (4 a 6 meses)
Para quem já programa em backend, o cronograma abaixo leva ao nível pleno e aponta o caminho para o sênior. Quem vem de Java pode acelerar a fase 1, mas não deve pular a 4.
Mês 1: fundamentos da linguagem. Reescreva um serviço Java simples em Kotlin, sem !!, com data class, sealed e when exaustivo. Ao final, peça a alguém que revise a diferença entre o código antigo e o novo.
Mês 2: coleções, genéricos e JVM. Implemente o leitor de CSV com Sequence, leia o bytecode gerado por algumas funções e rode um profiling simples com JFR para ver alocações reais.
Mês 3: coroutines. Construa o worker com limite de concorrência, timeout e cancelamento, e escreva os testes com runTest. Refaça o mesmo worker com Flow e compare.
Mês 4: APIs e persistência. Monte a API de pedidos com Spring Boot, validação, ProblemDetail, JWT, Postgres com Liquibase e um repositório MongoDB com índices. Provoque um N+1 de propósito, observe no log e corrija.
Mês 5: mensageria e resiliência. Adicione um consumidor do Azure Service Bus idempotente e um cliente HTTP com circuit breaker e retry com jitter. Simule a queda de uma dependência e observe o comportamento.
Mês 6: operação. Adicione métricas, traces, logs estruturados e Testcontainers nos testes de integração. Gere uma imagem Docker com camadas e meça o tamanho.
Portfólio técnico: o que chama a atenção em vagas
Recrutadores descartam CRUDs sem regra de negócio e projetos que só mostram o tutorial do framework. Um repositório que demonstre engenharia de verdade tem mais peso:
Projeto 1: serviço de pedidos com máquina de estados. Modelagem com sealed interface, transições validadas no domínio, persistência com Postgres e Liquibase, eventos publicados no Azure Service Bus e testes de transição com Kotest. O diferencial está em mostrar que estados inválidos não compilam.
Projeto 2: pipeline de ingestão de eventos. Consumidor do Azure Service Bus com concorrência controlada por coroutines, idempotência, dead letter queue e métricas de atraso, gravando o resultado em MongoDB. O diferencial é um teste de carga com resultado documentado e um gráfico de GC antes e depois de uma otimização.
Projeto 3: biblioteca ou CLI de processamento de arquivos. Leitura em streaming com Sequence, benchmark com JMH e relatório de alocações. Serve para provar domínio de linguagem e de JVM ao mesmo tempo.
O que os tech leads procuram e sinais de alerta
Em entrevistas e revisões de código, o que se avalia é se a pessoa escreve Kotlin ou Java com sintaxe de Kotlin.
Sinais positivos: modelagem com tipos que impedem estados inválidos, cancelamento tratado como parte do fluxo, escolha consciente entre Flow, Channel e Mutex, e respostas de erro previsíveis para quem consome a API.
Sinais de alerta que causam descarte:
!!espalhado pelo código, usado para silenciar o compilador em vez de modelar o caso nulo.GlobalScopeourunBlockingem código de produção, principalmente dentro de handlers HTTP.- Chamadas bloqueantes dentro de
Dispatchers.Default, o que trava a CPU inteira do serviço. catch (e: Exception)que engoleCancellationExceptione quebra o cancelamento.data classcomo entidade JPA, comequals,hashCodeetoStringque disparam carregamento lazy ou recursão.- Testes com
Thread.sleeppara esperar coroutines, em vez derunTeste tempo virtual.
Leituras canônicas e referências essenciais
Livros
- Kotlin in Action, por Dmitry Jemerov e Svetlana Isakova (Manning). Base sólida da linguagem, escrita pelos próprios projetistas.
- Effective Kotlin, por Marcin Moskała. Recomendações práticas de idiomatismo, uma leitura para depois do primeiro mês.
- Kotlin Coroutines: Deep Dive, por Marcin Moskała. Aprofundamento no modelo de coroutines, com foco em cancelamento e concorrência estruturada.
- Spring in Action, por Craig Walls (Manning). Referência do Spring Boot, com exemplos de APIs e persistência.
- Java Concurrency in Practice, por Brian Goetz. Não é sobre Kotlin, mas explica o modelo de threads, visibilidade de memória e sincronização sobre o qual as coroutines rodam.
Documentação e recursos
- Documentação oficial do Kotlin em kotlinlang.org/docs, com a seção de coroutines e o guia de null safety.
- Koans oficiais do Kotlin, para praticar a linguagem com exercícios guiados.
- Guias oficiais do Spring em spring.io/guides, especialmente os de Kotlin e de Spring Data, incluindo Spring Data MongoDB.
- Documentação oficial do Liquibase, com a referência de changesets e de rollback, e da Azure Service Bus, com a parte de sessões, filas mortas e modos de recebimento.
- Blog oficial do Kotlin, que documenta as mudanças da linguagem, incluindo o compilador K2 usado por padrão desde o Kotlin 2.0.
Navegação interna e tópicos complementares no vault
Para aprofundar os conceitos de backend que este currículo cobre e que já têm nota no vault, consulte as notas de Go em Curriculo de um Desenvolvedor Go e Trilha de Go, que tratam de concorrência, persistência e observabilidade de forma comparável. A trilha de Kotlin correspondente está em Trilha de Kotlin.