Trilha de Kotlin · parte 38 de 59
Starters e configuração por ambiente
Aula da fase 6 da trilha de Kotlin: Como os starters do Spring Boot montam um serviço, e como `application.yml`, profiles e `@ConfigurationProperties` separam configuração de código?
intermediárioPublicada em · 15 min de leitura
Pense numa receita de bolo com três camadas de instruções. A primeira é a receita padrão, escrita pelo confeiteiro para qualquer bolo. A segunda é uma anotação da casa, que ajusta o forno para o forno de cada cozinha. A terceira é um pedido específico do cliente, como “sem glúten”, que vence as outras duas. Um mesmo bolo pode ser feito em cozinhas diferentes sem que a receita seja reescrita a cada uma. Um serviço Spring funciona de modo parecido: há uma configuração base, uma configuração por ambiente e valores que chegam de fora do código, e a ordem entre elas decide qual valor prevalece. Esta aula trata de duas peças. A primeira são os starters, que montam as dependências de um serviço. A segunda é a configuração, que separa os valores que mudam entre ambientes do código que não muda.
Um starter, no Spring Boot, é um descritor de dependências que traz um conjunto de bibliotecas que funcionam juntas. Um serviço web com Spring MVC não precisa escolher separadamente cada peça, porque o starter spring-boot-starter-webmvc já traz um conjunto combinado, com o Tomcat como servidor embutido. A configuração por ambiente é o mecanismo que permite ao mesmo código rodar em desenvolvimento, homologação e produção com endereços, senhas e limites diferentes. Sem essa separação, o código acaba com if (ambiente == "prod") espalhado pelo projeto, e esse é o problema que a aula quer evitar.
o mapa da aula
A aula começa pelos starters, porque eles determinam quais bibliotecas existem no classpath antes de qualquer configuração ser lida. Depois explica a ordem de precedência das fontes, com as citações da documentação oficial, e mostra como os arquivos base e os perfis se combinam. Um teste com a aplicação rodando mostra a precedência na prática, incluindo o caso de um perfil digitado errado. Em seguida vem a ligação de propriedades a uma data class pelo Binder, com as saídas de quatro casos, dois deles com erro. Por fim, a aula registra o que foi lido da documentação e o que continua sem verificação.
Versões desta aula: Spring Boot 4.1.1, com Spring Framework 7.0.9 e Kotlin 2.3.21 gerenciados pelo BOM. O binder foi compilado e executado com o spring-boot 4.1.1. Os testes de precedência rodaram com a aplicação de verificação iniciada por java -jar, em 20/10/2026.
1. starters como descritores de dependência
A documentação do Spring Boot define os starters como “a set of convenient dependency descriptors that you can include in your application”. Na prática, um starter é uma dependência Maven ou Gradle que puxa outras dependências. O starter de Spring MVC com Tomcat aparece na documentação como spring-boot-starter-webmvc, e o spring-boot-starter-web está marcado como deprecated em favor dele. A escolha do starter decide quais bibliotecas entram no projeto, e isso vem antes de qualquer configuração. Quando um comportamento parecer automático, o primeiro passo é conferir quais dependências entraram no projeto.
Os starters também resolvem o problema de versões. A documentação diz que “Each release of Spring Boot provides a curated list of dependencies that it supports. In practice, you do not need to provide a version for any of these dependencies in your build configuration, as Spring Boot manages that for you.” Essa lista fica no BOM spring-boot-dependencies. Foi desse arquivo que vieram as versões de Spring Framework, Kotlin e Jackson usadas nesta série de aulas. Um projeto que escreve versões fixas à mão para uma dependência gerenciada abre mão dessa combinação, que o Spring Boot já publica.
Esta aula não montou um projeto Gradle. A configuração de build com Kotlin DSL e a organização multi-módulo ficam para a aula Gradle Kotlin DSL e multi-módulo. Para a leitura de configuração, o que importa é entender que o starter define o classpath e que o classpath define o que a configuração automática pode criar.
2. a ordem das fontes de configuração
Um serviço lê valores de vários lugares: arquivos do projeto, variáveis do sistema, argumentos da linha de comando e código. A documentação define uma ordem, e a regra central é esta: “Later property sources can override the values defined in earlier ones.” A fonte que vem depois vence a que vem antes quando ambas definem a mesma chave.
A documentação lista as três primeiras fontes da ordem. São as propriedades padrão definidas com SpringApplication.setDefaultProperties(Map), as anotações @PropertySource em classes @Configuration e os arquivos de configuração do Spring Boot, como application.properties e application.yaml. A página lista outras fontes depois dessas, e a consulta desta revisão não leu essa parte. A figura abaixo mostra as camadas que a aula verificou.
A figura mostra as camadas da mais fraca para a mais forte. As propriedades padrão perdem para qualquer outra fonte, e o arquivo do perfil ativo vence o arquivo base. Os três resultados à direita vêm de um teste real: sem perfil ativo, o valor é base; com prod ativo, é prod; com um perfil digitado errado, é base. A seta rosa tracejada mostra o caso do perfil que não existe, que é o ponto em que o cuidado precisa ser maior.
3. arquivos base e perfis
Um perfil é um nome que agrupa configurações de um ambiente. Ele é ativado por spring.profiles.active, e o arquivo correspondente segue a convenção application-{profile}. A regra de precedência entre o arquivo base e o do perfil foi testada com dois arquivos: application.yaml com app.nome: base e application-prod.yaml com app.nome: prod. O endpoint de teste lê app.nome com @Value, e as três execuções produziram:
[sem perfil] {"app.nome":"base"}
No active profile set, falling back to 1 default profile: "default"
[--spring.profiles.active=prod] {"app.nome":"prod"}
The following 1 profile is active: "prod"
[--spring.profiles.active=prodd] {"app.nome":"base"}
The following 1 profile is active: "prodd"
Sem perfil ativo, o Spring usa o perfil default, e o arquivo do perfil prod não entra. Com prod ativo, o arquivo do perfil sobrescreve a chave. O terceiro caso é o de um nome digitado errado. O Spring aceitou prodd como perfil ativo, não encontrou arquivo para ele e usou o valor do arquivo base, sem aviso e sem falha na subida. A documentação consultada não diz se haveria aviso, e o teste mostra que, nesta versão, não há. Um teste de inicialização que confira o perfil ativo deixa esse erro visível antes do deploy.
A documentação também diz que o padrão existe: “The Environment has a set of default profiles (by default, [default]) that are used if no active profiles are set.” O teste acima confirma esse comportamento.
Pergunta de entrevistaQual a ordem de precedência das fontes de configuração no Spring Boot?
A regra central da documentação é que fontes posteriores podem sobrescrever valores definidos em fontes anteriores. Na lista, vêm primeiro as propriedades padrão definidas com SpringApplication.setDefaultProperties, depois as anotações @PropertySource e depois os arquivos application.properties ou application.yaml. A documentação lista outras fontes depois dessas, que a aula não detalhou. Na prática, o arquivo do perfil ativo vence o arquivo base, e isso permite ter valores seguros por padrão e trocar só o necessário em cada ambiente. O cuidado é que um perfil digitado errado não gera erro: na aula, o valor ficou o do arquivo base. Por isso confiro o perfil ativo na inicialização.
4. propriedades ligadas a objetos
Ler loja.limite-itens com environment.getProperty funciona, mas espalha strings e conversões pelo código. A alternativa do Spring Boot é ligar um grupo de propriedades a um objeto com @ConfigurationProperties. Com Kotlin, a combinação mais limpa é uma data class com propriedades val. A documentação diz que “@ConfigurationProperties when used in combination with constructor binding supports data classes with immutable val properties”. Um objeto imutável carregado uma vez na inicialização é mais fácil de raciocinar do que um conjunto de leituras espalhadas.
A mesma página registra uma limitação: “Due to the limitations of their interoperability with Java, support for value classes is limited. In particular, relying upon a value class’s default value will not work with configuration property binding. In such cases, a data class should be used instead.” A aula não testou esse caso específico. Ela registra apenas o que a documentação diz.
O código abaixo usa o Binder do Spring Boot, que é a peça que o @ConfigurationProperties usa por baixo. Ele não sobe um contexto, mas executa o mesmo mecanismo de conversão e de construtor sobre um mapa de propriedades. A data class tem três parâmetros: nome obrigatório, limiteItens com valor padrão 50 e urlPagamento nulável.
package aula.teste.v53
import org.springframework.boot.context.properties.bind.Binder
import org.springframework.boot.context.properties.bind.Bindable
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource
data class LojaProperties(val nome: String, val limiteItens: Int = 50, val urlPagamento: String?)
fun ligar(nome: String, props: Map<String, String>): String {
val fonte = MapConfigurationPropertySource(props)
return try {
val resultado = Binder(fonte).bind("loja", Bindable.of(LojaProperties::class.java)).get()
resultado.toString()
} catch (e: Exception) {
"ERRO ${e::class.simpleName}: ${e.message?.lineSequence()?.first()}"
}
}
fun main() {
println("[completo] " + ligar("completo", mapOf("loja.nome" to "Mercadinho", "loja.limite-itens" to "20", "loja.url-pagamento" to "https://pay.exemplo.com")))
println("[sem limite e sem url] " + ligar("x", mapOf("loja.nome" to "Mercadinho")))
println("[limite invalido] " + ligar("x", mapOf("loja.nome" to "Mercadinho", "loja.limite-itens" to "vinte")))
println("[sem nome] " + ligar("x", mapOf("loja.limite-itens" to "20")))
}
[completo] LojaProperties(nome=Mercadinho, limiteItens=20, urlPagamento=https://pay.exemplo.com)
[sem limite e sem url] LojaProperties(nome=Mercadinho, limiteItens=50, urlPagamento=null)
[limite invalido] ERRO BindException: Failed to bind properties under 'loja.limite-itens' to int
[sem nome] ERRO BindException: Failed to bind properties under 'loja' to aula.teste.v53.LojaProperties
O primeiro caso mostra a ligação completa. A chave está em kebab-case, loja.limite-itens, e o campo Kotlin se chama limiteItens. O Spring Boot faz essa tradução. O segundo caso omite limiteItens e urlPagamento, e o binder usa o valor padrão do parâmetro (50) e deixa o campo nulável como null. O terceiro mostra uma conversão falha, em que vinte não vira int, e a mensagem aponta a chave exata. O quarto omite nome, que é obrigatório, e a mensagem aponta o prefixo loja. Essa mensagem é menos precisa que a do terceiro caso.
A figura abaixo mostra a decisão que o binder toma para cada parâmetro do construtor.
Os quatro casos de entrada entram pelo mesmo binder. O primeiro losango pergunta se a chave existe. Quando existe, o segundo losango confere se o texto converte para o tipo do parâmetro, e vinte para Int cai no caminho de erro. Quando a chave não existe, o terceiro losango decide entre o padrão, se o parâmetro tem valor padrão ou é nulável, e a falha, se não tem. A data class com padrões explícitos fica previsível porque cada ausência tem uma regra escrita no construtor.
Os dois casos de erro mostram um comportamento que vale guardar. Um campo obrigatório ausente e um valor de tipo errado falham na ligação, e não quando alguém usa o valor depois. Em configuração, isso é o que se quer: um serviço que não sobe com configuração inválida é melhor que um serviço que sobe e erra em produção.
5. detalhes e bordas
Os valores de configuração podem ser sensíveis, como senhas e chaves de API. A boa prática é tratá-los como dados que chegam de fora do código, e não como constantes no arquivo versionado. Esta aula não testou nenhuma integração com cofres de segredos, porque isso depende da infraestrutura de cada equipe.
Outra borda é a sobreposição de nomes. Como as fontes se sobrepõem, o valor efetivo de uma chave só se entende olhando a ordem completa. Quando um valor não bate com o esperado, a pergunta certa é qual fonte o definiu por último. O teste da seção 3 mostra esse raciocínio: o valor base veio do arquivo base, e não de um perfil que não existia.
A documentação de configuração externa também diz que o Spring Boot “attempts to validate @ConfigurationProperties classes whenever they are annotated with Spring’s @Validated annotation”, e que, para validar propriedades aninhadas, o campo precisa de @Valid. A aula não testou essa validação. Ela aparece aqui como referência para quem for usá-la.
A parte final da página de configuração externa, com as fontes depois das três primeiras (variáveis de ambiente, argumentos da linha de comando e as demais), não foi lida nesta revisão. Por isso a aula não afirma a posição exata dessas fontes na ordem.
Pergunta de entrevistaComo você lê configuração de forma tipada no Spring Boot com Kotlin?
Em vez de espalhar environment.getProperty e conversões de string pelo código, ligo um grupo de propriedades a um objeto com @ConfigurationProperties. Em Kotlin, a combinação mais limpa é uma data class com propriedades val e binding por construtor. Na aula, a chave loja.limite-itens, em kebab-case, vira o campo limiteItens. Se a chave é omitida, o binder usa o valor padrão do parâmetro ou deixa o campo nulável como null. Uma conversão inválida, como vinte para int, falha com uma mensagem que aponta a chave. A documentação avisa que o suporte a value classes é limitado e que o valor padrão delas não funciona no binding, então nesses casos uso uma data class.
6. erros comuns e boas práticas
O erro mais frequente é ler a configuração com System.getenv ou environment.getProperty no meio da regra de negócio. Isso espalha o contrato de configuração pelo código, impede testes sem variáveis reais e faz o erro de digitação aparecer só quando o trecho roda. A alternativa é ligar as propriedades a uma data class uma vez, na inicialização, e passar o objeto para quem precisa.
O segundo erro é usar um valor padrão que mascara a falta de configuração. Um limiteItens = 50 faz sentido quando a equipe decidiu que 50 é o padrão. Se o valor só existe para o serviço subir, o padrão esconde a configuração que faltou em produção. Um parâmetro obrigatório, sem padrão, é mais honesto nesse caso, e a falha acontece cedo.
O terceiro erro é colocar um valor de produção no application.yaml base. Esse valor vale também para o desenvolvimento, e a sobrescrita por perfil só funciona quando o perfil escreve a chave. O arquivo base deve trazer valores seguros para qualquer ambiente, e cada perfil deve trazer só o que muda.
O quarto erro é confiar que um perfil digitado errado vai aparecer. Como o teste da seção 3 mostrou, o Spring aceitou prodd sem aviso e usou o base. Um teste de inicialização que confere o perfil ativo e os valores críticos torna esse erro visível.
A boa prática, em resumo, é ter uma data class por grupo de propriedades, valores padrão só quando eles são a regra de negócio, arquivo base seguro, perfis que sobrescrevem apenas o necessário e falha na inicialização para configuração inválida. A aula Resiliência e empacotamento trata de empacotamento e da imagem Docker, onde essa configuração encontra o ambiente de execução.
7. exercícios
- Crie uma
data classConfigPagamentocomchave: String,timeoutMs: Long = 3000emodoTeste: Boolean = true. Rode oBindercom três conjuntos de propriedades e anote o que cada um produz. - Adicione uma propriedade com um valor que não converte para
Boolean, como"talvez", e leia a mensagem de erro. Diga com suas palavras qual parte da mensagem permite encontrar a chave errada. - Suba a aplicação com um perfil que não existe e um perfil que existe, e compare os valores. Depois leia a página de configuração externa a partir da seção sobre variáveis de ambiente e diga em que posição elas ficam na ordem. Qual fonte você usaria para um segredo, e por quê?
8. glossário
Starter: descritor de dependências do Spring Boot que traz um conjunto de bibliotecas que funcionam juntas.
BOM: arquivo que lista as versões compatíveis de um conjunto de dependências, usado para que o projeto não precise escrever as versões à mão.
PropertySource: fonte de propriedades do Spring, como um arquivo, um mapa ou as variáveis do sistema.
Perfil: nome que agrupa configurações de um ambiente, ativado por spring.profiles.active.
Binding: processo que copia valores de propriedades para os campos de um objeto, com conversão de tipos.
Constructor binding: forma de ligação em que os valores são passados ao construtor de um objeto imutável, em vez de preenchidos depois por setters.
Kebab-case: convenção de nomes com palavras separadas por hífen, como limite-itens, que o Spring Boot aceita para campos em camelCase.
fontes
Consultadas em 20/10/2026:
- Spring Boot, construção de projetos, com a definição de starters, o starter
spring-boot-starter-webmvc, ospring-boot-starter-webmarcado como deprecated e o BOMspring-boot-dependencies: https://docs.spring.io/spring-boot/reference/using/build-systems.html - Spring Boot, configuração externa, com a ordem das fontes (três primeiras), a regra de sobrescrita, o perfil padrão
[default]e a seção de@Validated. A parte posterior da página não foi lida: https://docs.spring.io/spring-boot/reference/features/external-config.html - Spring Boot, página de Kotlin, com o suporte a data classes imutáveis com constructor binding e a limitação de value classes: https://docs.spring.io/spring-boot/reference/features/kotlin.html
- BOM do Spring Boot 4.1.1 (
spring-boot-dependencies-4.1.1.pom), para as versões gerenciadas: https://repo1.maven.org/maven2/org/springframework/boot/spring-boot-dependencies/4.1.1/spring-boot-dependencies-4.1.1.pom
Verificações locais, feitas em 20/10/2026: o binder foi compilado com Kotlin 2.3.21 contra spring-boot 4.1.1 e executado sem servidor. A precedência de perfis foi verificada com a aplicação de verificação iniciada por java -jar, com application.yaml e application-prod.yaml de teste. As saídas das seções 3 e 4 são as reais.
Não verificado nesta aula: a ordem das fontes depois de application.yaml (variáveis de ambiente, argumentos da linha de comando e as demais), o comportamento de @Validated em @ConfigurationProperties, e o comportamento de value class com valor padrão, citado da documentação e não testado.