Spring Boot y Spring Cloud
Configuración en Spring Boot: properties, YAML, perfiles y @ConfigurationProperties
Principiante10 min de lecturaActualizado
Esta página se tradujo del inglés con ayuda de IA y se cotejó con el original. Leer la versión en inglés
Sabrás configurar un servicio Spring Boot 3 para varios entornos sin recompilarlo: elegir entre properties y YAML, entender qué fuente gana cuando los valores chocan, activar perfiles correctamente, vincular la configuración a clases inmutables y validadas, y mantener los secretos fuera del jar.
Lo que aprenderás
- Ordenar las fuentes de propiedades de Spring Boot y predecir qué valor gana cuando la misma clave se define dos veces
- Escribir configuración específica de cada perfil con ficheros application-{profile} y YAML multidocumento
- Vincular la configuración a records @ConfigurationProperties inmutables y validados
- Usar el relaxed binding para que variables de entorno, claves YAML y argumentos de línea de comandos apunten a la misma propiedad
- Importar ficheros de configuración externos y mantener las credenciales fuera del artefacto empaquetado
Un servicio que hay que recompilar para cambiar un puerto, una URL de base de datos o un feature flag no se puede promocionar de staging a producción como el mismo artefacto, y desplegar ese mismo artefacto es justo la razón de ser de un pipeline de despliegue. La respuesta de Spring Boot es la configuración externalizada: un único jar inmutable y muchas fuentes de propiedades apiladas unas sobre otras al arrancar, con un orden definido para resolver los conflictos. Esta página cubre ese orden, los formatos de fichero, los perfiles, el binding tipado y los hábitos que evitan que la configuración se convierta en una sesión de depuración.
De dónde salen las propiedades
Boot construye un único Environment a partir de muchos PropertySource. Cuando la misma clave aparece en varios, gana el de mayor precedencia. La lista siguiente va de menor a mayor precedencia e incluye las fuentes que te encontrarás en la práctica (la documentación de referencia tiene la lista completa):
- Propiedades por defecto definidas por código con
SpringApplication.setDefaultProperties - Anotaciones
@PropertySourceen clases@Configuration - Ficheros de config data:
application.properties/application.yml, y después sus variantes específicas de perfil - Un
RandomValuePropertySource(random.int,random.uuid) - Variables de entorno del sistema operativo
- Propiedades de sistema de Java (
-Dserver.port=9090) - Atributos JNDI y parámetros del servlet context y del servlet config
SPRING_APPLICATION_JSON(JSON en línea dentro de una variable de entorno o una propiedad de sistema)- Argumentos de línea de comandos (
--server.port=9090) propertiesen@SpringBootTest,@TestPropertySourcey@DynamicPropertySourceen los tests- Ajustes globales de DevTools en la máquina del desarrollador
Hay dos consecuencias que conviene interiorizar. La primera: todo lo que se define en la línea de comandos o en el entorno se impone a lo que viene dentro del jar, que es justo lo que necesita quien opera el servicio. La segunda: @PropertySource se carga tan tarde que no puede influir en decisiones tempranas como la configuración del logging o spring.profiles.active; es una funcionalidad de compatibilidad, no el mecanismo principal.
Dentro de la capa de config data, los ficheros también tienen un orden. Boot busca application.* en estas ubicaciones, y las posteriores sobrescriben a las anteriores:
- la raíz del classpath
- el paquete
/configdel classpath - el directorio de trabajo actual
- un subdirectorio
/configdel directorio de trabajo, y sus hijos inmediatos
Así, un fichero colocado junto al jar en ./config/application.yml sobrescribe el que va empaquetado dentro, sin ningún flag. spring.config.name cambia el nombre base y spring.config.location sustituye por completo la ruta de búsqueda (spring.config.additional-location la amplía). Ambos deben indicarse pronto, como argumentos de línea de comandos o variables de entorno, porque se leen antes que los ficheros.
Properties frente a YAML
Los dos formatos expresan el mismo árbol. Compara:
server.port=8081
spring.datasource.url=jdbc:postgresql://localhost:5432/shop
spring.datasource.username=shop
spring.jpa.open-in-view=false
app.allowed-origins[0]=https://shop.example.com
app.allowed-origins[1]=https://admin.example.com
server:
port: 8081
spring:
datasource:
url: jdbc:postgresql://localhost:5432/shop
username: shop
jpa:
open-in-view: false
app:
allowed-origins:
- https://shop.example.com
- https://admin.example.com
YAML gana en legibilidad en cuanto aparecen anidamientos y listas, y permite varios documentos en un mismo fichero separados por ---. A cambio, es sensible a la indentación y da alguna sorpresa al parsear: yes/no/on/off sin comillas se convierten en booleanos en algunos parsers YAML, 01 puede leerse como un número octal y un valor que contenga dos puntos debe ir entre comillas. Boot usa SnakeYAML y lo necesita en el classpath; los starters lo incluyen. Ten en cuenta también que los ficheros YAML no se pueden cargar mediante @PropertySource.
Los placeholders funcionan en ambos: app.base-url=https://${app.host}:${server.port} se resuelve contra todo el Environment, y ${MY_VAR:default} aporta un valor de respaldo.
Perfiles
Un perfil es una porción con nombre de configuración y beans. Activa uno o varios con spring.profiles.active=prod (como propiedad, con la variable de entorno SPRING_PROFILES_ACTIVE o con --spring.profiles.active=prod). Cuando no hay ningún perfil activo, lo está el perfil default.
Los ficheros específicos de perfil siguen la convención de nombres application-{profile}.yml, viven en las mismas ubicaciones que application.yml y lo sobrescriben. Si hay varios perfiles activos, gana el último de la lista. Se consigue el mismo efecto dentro de un único fichero YAML:
spring:
application:
name: shop
jpa:
open-in-view: false
---
spring:
config:
activate:
on-profile: local
datasource:
url: jdbc:h2:mem:shop
logging:
level:
org.hibernate.SQL: DEBUG
---
spring:
config:
activate:
on-profile: prod
datasource:
url: jdbc:postgresql://db.internal:5432/shop
server:
shutdown: graceful
La clave spring.config.activate.on-profile sustituyó a la antigua clave spring.profiles en Boot 2.4. Se admiten expresiones de perfil: on-profile: "prod & eu" o "!local".
Los perfiles también condicionan los beans. @Profile("local") en una @Configuration o un @Bean lo registra solo cuando ese perfil está activo; @Profile("!prod") es la forma habitual de mantener una pasarela de pago falsa fuera de producción. Úsalo con moderación: un servicio cuyo grafo de beans cambia mucho de un entorno a otro es un servicio que en realidad no has probado antes de desplegarlo.
Los grupos de perfiles permiten que un perfil se expanda en varios: spring.profiles.group.prod=prod-db,prod-mq significa que activar prod activa también los otros dos. spring.profiles.include añade perfiles de forma incondicional. Ninguno de los dos puede aparecer en un documento específico de perfil, por la cuestión de orden que se explica en las FAQ.
Una organización habitual y sensata: un application.yml base con todo lo compartido, un fichero de perfil pequeño por entorno con solo lo que cambia, todo bajo control de versiones, y los secretos aportados desde fuera (ver más abajo). Nombres de entorno como local, test, staging y prod son convenciones, no palabras reservadas; a Boot solo le importa la cadena.
Binding tipado con @ConfigurationProperties
@Value("${app.timeout}") sirve para uno o dos ajustes. Más allá de eso, esparce claves en forma de cadena por todo el código, sin validación ni ayuda del IDE. @ConfigurationProperties vincula un prefijo entero a un objeto:
package com.example.shop.config;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Positive;
import java.time.Duration;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.convert.DurationUnit;
import org.springframework.validation.annotation.Validated;
@Validated
@ConfigurationProperties(prefix = "app")
public record AppProperties(
@NotEmpty List<String> allowedOrigins,
@Positive int maxPageSize,
Duration requestTimeout, // "30s", "2m", "500ms" all bind
Catalog catalog) {
public record Catalog(boolean cacheEnabled, Duration cacheTtl) {}
}
Para registrarlo basta con una de estas dos anotaciones en una clase de configuración: @EnableConfigurationProperties(AppProperties.class) si quieres control explícito, o @ConfigurationPropertiesScan en la clase de la aplicación para recoger todas las clases anotadas del árbol de paquetes. Después inyecta AppProperties donde lo necesites, como cualquier otro bean.
Los records te dan el binding por constructor y la inmutabilidad sin coste. En Boot 3, un record, o una clase con un único constructor con parámetros, se vincula automáticamente a través de su constructor; la anotación explícita @ConstructorBinding de Boot 2 solo hace falta cuando una clase tiene varios constructores. Un JavaBean mutable con setters sigue funcionando cuando necesitas valores por defecto que dependen de otros valores, pero los records deberían ser la opción por defecto.
@Validated activa Jakarta Bean Validation sobre el objeto vinculado, de modo que si falta allowedOrigins el arranque falla con un mensaje claro que indica el valor rechazado y la restricción. Fallar pronto es mejor que descubrir una errata en el nombre de una propiedad con la primera petición en producción.
Añade spring-boot-configuration-processor como dependencia opcional (annotationProcessor en Gradle) y el build genera META-INF/spring-configuration-metadata.json, que es lo que te da autocompletado y documentación de tus propias claves en IntelliJ IDEA, Eclipse y VS Code. El Javadoc de los componentes del record se convierte en la descripción que muestra el IDE.
Relaxed binding
Boot normaliza los nombres de las claves para que una misma propiedad pueda escribirse con el estilo natural de cada fuente:
| Fuente | Forma de escribir app.max-page-size |
|---|---|
| Properties / YAML | app.max-page-size (kebab-case, la forma canónica), también app.maxPageSize o app.max_page_size |
| Variable de entorno | APP_MAXPAGESIZE (en mayúsculas, los puntos pasan a guiones bajos y los guiones se eliminan) |
| Propiedad de sistema / línea de comandos | app.max-page-size o app.maxPageSize |
Para las listas en variables de entorno, los índices se convierten en segmentos numéricos: APP_ALLOWEDORIGINS_0=https://shop.example.com. El relaxed binding se aplica a @ConfigurationProperties; @Value es más estricto y espera el nombre canónico exacto.
Conversión
El binding entiende duraciones (10s, PT10S, o un simple número con @DurationUnit), tamaños de datos (10MB, @DataSizeUnit), periodos, enums (sin distinguir mayúsculas de minúsculas), File, Resource, InetAddress y cualquier tipo con un constructor String o un método valueOf. Las conversiones propias se registran mediante un bean Converter anotado con @ConfigurationPropertiesBinding.
Importar configuración y gestionar secretos
spring.config.import incorpora fuentes adicionales a la capa de config data, que se procesan después del fichero que las declara:
spring.config.import=optional:file:./overrides.properties,optional:configtree:/run/secrets/
file:yclasspath:importan ficheros properties o YAML normales.optional:evita que el arranque falle si la ubicación no existe, que es lo que quieres para un fichero de sobrescritura solo para desarrollo.configtree:lee un directorio en el que el nombre de cada fichero es la clave y su contenido el valor, que es la estructura que generan Kubernetes y Docker al montar secretos. Un fichero/run/secrets/db-passwordse convierte en la propiedaddb-password.configserver:(con Spring Cloud Config) yvault:(con Spring Cloud Vault) obtienen la configuración de un almacén central.
Nada de esto debe ir dentro del jar: ni contraseñas, ni claves de API, ni certificados privados. Aporta todo eso mediante variables de entorno, ficheros de secretos montados o un servidor de configuración, y referéncialo desde application.yml con placeholders como password: ${DB_PASSWORD}. Si no te queda más remedio que guardar un valor cifrado en un fichero, existen el prefijo {cipher} de Spring Cloud Config y las integraciones con Jasypt, pero un gestor de secretos es la mejor herramienta.
Configurar el logging mediante propiedades
El logging es la configuración que casi todo el mundo toca primero, y Boot expone los casos habituales como propiedades, así que rara vez hace falta un fichero de configuración de logback:
logging.level.root=INFO
logging.level.com.example.shop=DEBUG
logging.level.org.springframework.web=DEBUG
logging.file.name=/var/log/shop/app.log
logging.pattern.console=%d{HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n
logging.logback.rollingpolicy.max-file-size=50MB
logging.file.name y logging.file.path sustituyeron a las claves logging.file y logging.path anteriores a la 2.2; los nombres antiguos siguen apareciendo en tutoriales y ya no funcionan. Logback es la implementación por defecto detrás de los starters; pasar a Log4j2 consiste en excluir spring-boot-starter-logging y añadir spring-boot-starter-log4j2. Como el logging se inicializa antes que la mayor parte del contexto, las propiedades logging.* deben estar en un fichero de config data, en una variable de entorno o en la línea de comandos; un @PropertySource llega demasiado tarde.
Errores comunes
Suponer que el fichero de perfil añade elementos a la lista en lugar de sustituir el valor. Las listas se sustituyen enteras: un app.allowed-origins con dos entradas en application.yml y una en application-prod.yml da una sola entrada en producción, no tres. Los mapas, en cambio, se fusionan clave a clave.
Definir spring.profiles.active en application.yml y preguntarse por qué no se puede sobrescribir. Sí se puede: un argumento de línea de comandos gana. El problema real es que un perfil fijado dentro del artefacto es una cosa más que quien opera el servicio tiene que acordarse de sobrescribir. Déjalo sin definir en el fichero empaquetado y fíjalo desde el entorno.
Usar una clase interna sin static para las propiedades. El binder no puede instanciar una clase interna no estática; los grupos anidados deben ser clases anidadas estáticas o records.
Una errata en el nombre de una propiedad. Boot ignora en silencio las claves desconocidas: server.prot=9090 no hace nada. Activa ignore-unknown-fields = false en tus propios @ConfigurationProperties, apóyate en los metadatos del IDE para las claves de Boot y prefiere pocos prefijos con buenos nombres.
Pasar secretos mediante SPRING_APPLICATION_JSON o la línea de comandos. Ambos son visibles para cualquier proceso capaz de listar argumentos o de leer el entorno de la JVM; en hosts compartidos, los ficheros montados con permisos restringidos son más seguros.
Buenas prácticas
- Versiona
application.ymly todos los ficherosapplication-{profile}.yml; trátalos como código, con revisiones e historial. Mantén el fichero base completo y los de perfil al mínimo. - Agrupa los ajustes relacionados en un record
@ConfigurationPropertiespor responsabilidad, valídalo e inyecta el record en lugar de campos@Valuesueltos. - Usa la forma canónica en kebab-case para las claves de los ficheros; es la que emplean los metadatos y la documentación.
- Documenta cada clave propia con Javadoc para que los metadatos generados la expliquen en el IDE.
- Mantén pocos perfiles y con un significado evidente; si un perfil solo se activa junto con otro, usa un grupo en lugar de pedir a todo el mundo que recuerde la pareja.
- Cuando el comportamiento cambia entre entornos, prefiere una propiedad que lea un único bean antes que intercambiar beans con
@Profile.
Puntos clave
- Boot fusiona muchas fuentes de propiedades en un único
Environment; los argumentos de línea de comandos y las variables de entorno sobrescriben los ficheros de dentro del jar, y los ficheros situados junto al jar sobrescriben los empaquetados. - Properties y YAML son equivalentes; YAML añade ficheros multidocumento y legibilidad a cambio de la indentación y de algunas trampas al parsear.
- Los perfiles seleccionan configuración y beans; los ficheros específicos de perfil sobrescriben el fichero base, y
spring.profiles.activedebería aportarse desde fuera del artefacto. - Los records
@ConfigurationPropertiescon@Validatedofrecen una configuración inmutable, tipada y asistida por el IDE que falla rápido ante valores incorrectos. - El relaxed binding hace corresponder
app.max-page-size,app.maxPageSizeyAPP_MAXPAGESIZEcon la misma clave. spring.config.importconoptional:yconfigtree:es la forma limpia de incorporar sobrescrituras y secretos montados sin tocar el jar.
Fuentes y lecturas recomendadas
Preguntas frecuentes
- ¿Uso application.properties o application.yml?
- Los dos tienen soporte completo y ofrecen las mismas funcionalidades. YAML se lee mejor cuando hay claves anidadas y listas, y admite varios documentos en un mismo fichero, algo práctico para las secciones específicas de un perfil. Los ficheros properties son más planos, más difíciles de estropear con la indentación y mejor integrados en algunas herramientas. Elige uno por proyecto; si existen los dos con el mismo nombre, el fichero .properties tiene prioridad.
- ¿Por qué mi variable de entorno no sobrescribe application.yml?
- Revisa primero el nombre: SPRING_DATASOURCE_URL se corresponde con spring.datasource.url gracias al relaxed binding, pero los segmentos deben unirse con un único guion bajo y hay que quitar los guiones del nombre de la propiedad. Revisa después el alcance: la variable tiene que ser visible para el propio proceso de la JVM, no solo para tu shell o para un paso de build de Docker. Por último, recuerda que los argumentos de línea de comandos y las propiedades de test tienen más prioridad que las variables de entorno.
- ¿Puedo definir spring.profiles.active dentro de un fichero específico de un perfil?
- No. Boot rechaza spring.profiles.active y spring.profiles.include en los ficheros específicos de un perfil y en los documentos YAML activados con spring.config.activate.on-profile, porque un perfil que activa otros mientras se está cargando haría que el resultado dependiera del orden. Usa en su lugar spring.profiles.group en el documento principal para definir un perfil que se expanda en varios.
- ¿Cómo veo el valor final de una propiedad en tiempo de ejecución?
- Con Actuator, expón el endpoint env y consulta /actuator/env/your.property.name; lista todas las fuentes de propiedades que contienen la clave e indica cuál ganó. En Boot 3 los valores se enmascaran por defecto; define management.endpoint.env.show-values=when-authorized (o always, solo en un entorno protegido) para verlos. Para una comprobación puntual, inyecta Environment y registra getProperty en un CommandLineRunner.