W3docs

Clase Properties de Java

Carga y almacena pares clave-valor de cadenas en Java con la clase Properties, incluyendo archivos .properties.

Properties es el contenedor del JDK para la configuración de cadena a cadena — ajustes de aplicación, anulaciones de entorno, mensajes localizados, parámetros de conexión JDBC. Extiende Hashtable<Object, Object> (una decisión histórica que ahora lamentamos, pero con la que convivimos) y agrega tres cosas encima: un formato de archivo .properties con lector y escritor, un lector y escritor XML, y el concepto de un objeto predeterminado de propiedades que se consulta cuando una clave no se encuentra localmente.

System.getProperties() devuelve un Properties. Cada llamada a System.getProperty("user.home") pasa por él. Una vez que conoces la clase, la ves en todas partes.

El contrato: cadenas en ambos lados

Aunque la clase hereda un método put(Object, Object) de Hashtable, la única API segura es el par tipado con cadenas:

Properties config = new Properties();
config.setProperty("server.port", "8080");
config.setProperty("server.host", "localhost");
String port = config.getProperty("server.port");          // "8080"
String log  = config.getProperty("log.level", "INFO");    // default fallback

Si evitas setProperty y llamas a put("server.port", 8080) con un Integer, la entrada aún cae en la tabla, pero has plantado una mina: stringPropertyNames() la filtra silenciosamente, y los métodos de escritura de archivos (store, storeToXML) lanzan ClassCastException en el momento en que intentan convertir ese Integer a String. Trata Properties como Properties<String, String> aunque los genéricos no lo digan.

El formato de archivo .properties

Texto plano, orientado a líneas, key=value. Se permiten espacios alrededor de =. Las líneas que comienzan con # o ! son comentarios. La barra invertida al final continúa el valor en la siguiente línea. Las secuencias de escape Unicode (\uXXXX) son compatibles, pero desde Java 9 la sobrecarga load(Reader) lee UTF-8 de forma nativa, por lo que raramente se necesitan.

# server.properties — last edited 2026-05-12
server.host = localhost
server.port = 8080
server.path = /api/v1
greeting    = Welcome, \
              user!

load y store trabajan con este formato. loadFromXML y storeToXML trabajan con el formato XML equivalente definido por properties.dtd — existe, a veces es útil, pero casi nunca se prefiere sobre el formato de texto.

Carga y almacenamiento

Properties config = new Properties();
try (var in = Files.newBufferedReader(Path.of("server.properties"))) {
  config.load(in);              // UTF-8 text
}

config.setProperty("server.port", "9090");

try (var out = Files.newBufferedWriter(Path.of("server.properties"))) {
  config.store(out, "edited by setup script");   // comment becomes the first line
}

El método store escribe un comentario con marca de tiempo después del comentario del usuario, no ordena nada (las entradas llegan en orden de iteración de Hashtable) y escapa los caracteres especiales (=, :, #, espacios al inicio) para el ida y vuelta. La salida es portable entre JVM.

Para recursos incluidos con tu aplicación, carga desde el classpath en lugar del sistema de archivos:

try (var in = MyApp.class.getResourceAsStream("/app.properties")) {
  config.load(in);              // load(InputStream) defaults to ISO-8859-1
}

load(InputStream) es la sobrecarga histórica y usa ISO-8859-1 (Latin-1) con escapes \u. load(Reader) usa el conjunto de caracteres con el que se abrió el lector. Prefiere la forma de lector cuando controlas la codificación.

Propiedades predeterminadas: configuración por capas

El getProperty(key, default) de dos argumentos devuelve un valor de reserva cuando la clave está ausente. El constructor Properties(Properties defaults) hace lo mismo pero a nivel de objeto — el segundo Properties se consulta cuando el primero no contiene la clave:

Properties base = new Properties();
base.setProperty("server.port", "8080");
base.setProperty("log.level",   "INFO");

Properties override = new Properties(base);   // base is the defaults
override.setProperty("log.level", "DEBUG");   // override wins

override.getProperty("server.port");          // "8080"  (from base)
override.getProperty("log.level");            // "DEBUG" (from override)

Ese es el patrón estándar para "valores predeterminados incluidos con la aplicación, el usuario puede anular por entorno". Dos capas es el caso más común; puedes encadenar más.

Propiedades de System y flags -D

La JVM tiene una instancia global de Properties accesible mediante System.getProperties() y System.getProperty(key). Las claves estándar incluyen java.version, user.home, user.dir, os.name, file.separator y line.separator. El flag -Dkey=value en la línea de comandos de la JVM lo añade antes de que se ejecute main:

java -Dserver.port=9090 -Dlog.level=DEBUG -jar app.jar
String port = System.getProperty("server.port", "8080");

Esta es la "configuración por línea de comandos" más sencilla que puedes darle a un programa Java. Para configuraciones más grandes, la convención es un archivo .properties incluido con la aplicación, combinado al inicio con las propiedades del sistema (que actúan como anulaciones).

Lo que Properties no es

  • No es un mapa de tipos arbitrarios. Solo cadenas. Analiza Integer.parseInt(config.getProperty("port")) por tu cuenta.
  • No es jerárquico. Las claves como db.primary.host son solo cadenas; los puntos son convencionales, no estructurales. Si necesitas jerarquía real, usa una biblioteca de configuración YAML/JSON.
  • No es seguro para hilos en operaciones compuestas. Cada método está sincronizado (heredado de Hashtable), pero las operaciones de verificar-luego-actuar siguen siendo propensas a condiciones de carrera. La misma advertencia que la clase padre.
  • No es un reemplazo de ResourceBundle para i18n. PropertyResourceBundle es un ResourceBundle respaldado por un archivo .properties y añade búsqueda de configuración regional; esa es la herramienta correcta para cadenas traducidas.

Un ejemplo práctico: cargar valores predeterminados, anular por entorno, escribir de vuelta

El programa a continuación construye una configuración por capas (valores predeterminados dentro del JAR, archivo de entorno fuera), lee una propiedad del sistema para actuar como una anulación -D, aplana el resultado para que pueda escribirse de vuelta a un buffer .properties para inspección, y demuestra la trampa de store con valores no string.

Una sutileza que el ejemplo maneja deliberadamente: store escribe solo las entradas propias de un objeto Properties — nunca recorre la cadena de defaults heredada. Por eso, para obtener un archivo completo y procesable en ida y vuelta, copiamos cada clave resuelta en un Properties plano antes de almacenar, en lugar de llamar a store en un objeto que depende de su defaults padre.

java— editable, runs on the server

Qué extraer de la ejecución:

  • La configuración de tres capas (valores predeterminados → archivo de entorno → anulaciones -D) se resuelve correctamente. Los valores predeterminados completan lo que nadie anuló; el archivo de entorno cambia log.level y feature.beta; el flag -D gana para server.port.
  • store produjo un texto .properties portable con un comentario y una marca de tiempo en la parte superior, con las cuatro claves resueltas. Podrías pasar ese archivo directamente a load y obtener el mismo mapa — porque aplanamos las capas primero.
  • setProperty("age", 30) no compilaría (requiere un String). put("age", 30) sí compila, la entrada cae en la tabla, y stringPropertyNames la filtra — pero store no la omite silenciosamente: lanza ClassCastException en cuanto intenta convertir el Integer a String. La lección: nunca uses put con un valor no string en un Properties — usa siempre setProperty.

Qué sigue

Properties fue el último capítulo de "estructuras de datos" en esta parte. Los capítulos restantes tratan sobre operaciones en colecciones: recorrerlas (Iterators y ListIterator), comparar elementos (Comparable and Comparator), y las utilidades estáticas para ordenar, buscar y envolver (the Collections class). El próximo capítulo comienza con la base — la interfaz Iterator que cada bucle for-each usa en secreto.

Práctica

Práctica
Escribes `props.put('port', 8080)` (un `Integer`) en un objeto `Properties` y luego llamas a `props.store(out, null)`. ¿Qué ocurre?
Escribes `props.put('port', 8080)` (un `Integer`) en un objeto `Properties` y luego llamas a `props.store(out, null)`. ¿Qué ocurre?
Was this page helpful?