El puerto, la ruta de la base de datos o una clave de API no deberían ir escritos en el código. Las variables de entorno los separan del programa, y Kotlin te ayuda a leerlas bien: null si faltan, valores por omisión con ?: y conversiones que no explotan.
También te puede interesar
Por qué variables de entorno
El puerto, la ruta de la base de datos o una clave de API cambian entre tu computadora, el servidor de pruebas y producción. Si van escritos en el código, hay que recompilar para cambiarlos, y las claves acaban en el repositorio. Las variables de entorno las pone quien ejecuta el programa. En Linux y macOS:
export APP_NOMBRE=Tienda export APP_PUERTO=8080 export APP_BD_RUTA=/datos/tienda.db java -jar app.jar
En PowerShell de Windows se escribe $env:APP_PUERTO = "8080". Todos los ejemplos de esta página se ejecutaron con esas tres variables definidas.
Leer una variable
fun main() {
val nombre: String? = System.getenv("APP_NOMBRE")
val noExiste: String? = System.getenv("APP_NO_EXISTE")
println("APP_NOMBRE = $nombre")
println("APP_NO_EXISTE = $noExiste")
println("Usuario del sistema: ${System.getenv("USER") ?: "(sin USER)"}")
}salidaAPP_NOMBRE = Tienda APP_NO_EXISTE = null Usuario del sistema: (sin USER)
System.getenv(nombre) devuelve el valor como texto, o null si la variable no existe. Por eso aquí la guardamos en un String?: es lo honesto, porque nadie te garantiza que esté definida. Fíjate en USER: en tu terminal casi siempre existe, pero en un contenedor o un servicio de systemd puede no estar.
Valores por omisión con ?:
fun main() {
val nombre = System.getenv("APP_NOMBRE") ?: "Mi app"
val modo = System.getenv("APP_MODO") ?: "desarrollo"
val puerto = System.getenv("APP_PUERTO")?.toIntOrNull() ?: 8000
println("$nombre en modo $modo, puerto $puerto")
}salidaTienda en modo desarrollo, puerto 8080
El operador Elvis ?: da un valor cuando lo de la izquierda es null. Y como todo llega como texto, para un número se encadena toIntOrNull(), que devuelve null si el texto no es un número válido, en lugar de lanzar una excepción como toInt().
Cargar y validar toda la configuración
En un programa real conviene leer todo en un solo sitio, al arrancar, y fallar enseguida con un mensaje claro si algo está mal. Es mucho mejor que descubrirlo a medianoche, cuando el programa intenta abrir una base de datos que no existe:
import kotlin.system.exitProcess
data class Config(val nombre: String, val puerto: Int, val rutaBd: String, val depurar: Boolean)
fun cargarConfig(env: Map<String, String> = System.getenv()): Config {
val errores = mutableListOf<String>()
val puertoTexto = env["APP_PUERTO"] ?: "8000"
val puerto = puertoTexto.toIntOrNull()
if (puerto == null || puerto !in 1..65535) errores += "APP_PUERTO debe ser un número entre 1 y 65535 (vale «$puertoTexto»)"
val rutaBd = env["APP_BD_RUTA"]
if (rutaBd.isNullOrBlank()) errores += "falta APP_BD_RUTA"
if (errores.isNotEmpty()) {
System.err.println("Configuración no válida:")
errores.forEach { System.err.println(" - $it") }
exitProcess(2)
}
return Config(
nombre = env["APP_NOMBRE"] ?: "Mi app",
puerto = puerto!!,
rutaBd = rutaBd!!,
depurar = env["APP_DEPURAR"].toBoolean(),
)
}
fun main() {
val config = cargarConfig()
println(config)
}salidaConfig(nombre=Tienda, puerto=8080, rutaBd=/datos/tienda.db, depurar=false) código de salida: 0 --- con APP_PUERTO=abc y sin APP_BD_RUTA Configuración no válida: - APP_PUERTO debe ser un número entre 1 y 65535 (vale «abc») - falta APP_BD_RUTA código de salida: 2
Con la configuración correcta, el programa imprime la data class. Con un puerto que no es número y sin la ruta de la base, junta todos los errores (no solo el primero), los escribe en la salida de errores y termina con código 2, que los scripts y systemd interpretan como fallo. cargarConfig recibe el mapa como parámetro, con System.getenv() por omisión: en las pruebas le pasas un mapOf(...) y no tienes que tocar el entorno de verdad.
Un detalle: "cualquier cosa".toBoolean() da false salvo que el texto sea «true» (sin importar mayúsculas), y en Kotlin funciona incluso sobre un String? nulo. Así, APP_DEPURAR sin definir equivale a false.
Todas las de tu aplicación
fun main() {
System.getenv()
.filterKeys { it.startsWith("APP_") }
.toSortedMap()
.forEach { (clave, valor) -> println("$clave=$valor") }
}salidaAPP_BD_RUTA=/datos/tienda.db APP_NOMBRE=Tienda APP_PUERTO=8080
Sin argumentos, System.getenv() devuelve un mapa con todas. Un prefijo común (APP_) evita chocar con las del sistema y permite listarlas así. Nunca imprimas así los secretos en un registro.
El error típico: olvidar que puede faltar
fun main() {
val token = System.getenv("APP_TOKEN")
println("El token tiene ${token.length} caracteres")
}salida con errorException in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because "token" is null
at PlataformaKt.main(plataforma.kt:3)Esto compila sin un aviso. System.getenv está escrito en Java, que no dice si puede devolver null, así que Kotlin le da un tipo de plataforma (String!): te deja usarlo como String y como String?, y la responsabilidad es tuya. Si la variable no existe, el resultado es el NullPointerException de toda la vida. La costumbre que te protege es la de los ejemplos anteriores: declara el tipo como String? o usa ?: en cuanto lo lees, y el compilador vuelve a cuidarte. Lo explicamos a fondo en la lección de null safety.
- ParaEscribe
- Leer una
System.getenv("APP_PUERTO")(puede sernull) - Con valor por omisión
System.getenv("X") ?: "valor" - Como número
?.toIntOrNull() ?: 8000 - Como booleano
System.getenv("X").toBoolean() - Todas
System.getenv()(unMap) - Salir con error
exitProcess(2)tras explicar el problema enSystem.err
No necesitas bibliotecas: kotlinc config.kt -include-runtime -d config.jar y java -jar config.jar. En Android, en cambio, no se usan variables de entorno: la configuración va en el build.gradle.kts (buildConfigField) o en recursos.
Todo el código de esta página se compiló con Kotlin 2.4.21 y se ejecutó en la JVM de Temurin 25.0.5 en un contenedor limpio y sin red antes de publicarla; las salidas y los mensajes del compilador están copiados de esa ejecución.