Saltar al contenido
AZ Tools

Analizador de Java .properties

Un archivo .properties parece el formato de configuración más simple que existe, y es el que más veces analizan mal los parsers escritos a mano. Esta página ejecuta el algoritmo que java.util.Properties.load usa de verdad —el LineReader del JDK y después load0 y loadConvert— sobre el texto que pegues o sueltes, y muestra el mapa resultante clave por clave, como JSON y como el archivo que Properties.store volvería a escribir. Señala justo las partes con las que la gente tropieza. El separador es =, : o una racha de espacios, así que "c three" es la clave c con el valor three, y "key with spaces=x" es la clave "key" con el valor "with spaces=x". El espacio alrededor del separador desaparece; el del final del valor no, y ningún editor lo muestra. Una barra invertida al final de la línea la continúa y quita la sangría de la siguiente, pero un número par de barras no continúa nada, y una línea de comentario terminada en barra tampoco. # y ! abren un comentario solo como primer carácter no blanco de la línea, de modo que el # de un fragmento de URL se queda en el valor. Los únicos escapes son \t \n \r \f y \uXXXX; cualquier otra barra invertida se descarta en silencio. El mayor problema real de este formato es la codificación. load(InputStream) —la llamada que hace casi cualquier framework— decodifica los bytes como ISO-8859-1, así que un archivo guardado en UTF-8 convierte cada carácter acentuado en dos o tres. Cuando la entrada tiene caracteres no ASCII, esta página muestra las dos lecturas y nombra las claves que cambian, para que elijas entre escapes \uXXXX y load(Reader) con un charset explícito. Todo ocurre en tu navegador y no se sube nada. Lo que la herramienta no puede decirte es cuál de dos claves duplicadas querías, ni si un valor que parece roto lo está: informa de lo que el JDK hará con esos bytes, no de lo que pretendías escribir.

Contenido del archivo .properties

Claves únicas

11

Asignaciones

12

Líneas de comentario

1

Líneas continuadas

1

Hallazgos

6

Leer los bytes como:

load(InputStream) es lo que usan casi todos los frameworks y siempre lee ISO-8859-1. Cambia entre las dos lecturas para ver qué claves cambian.

Lo que ha detectado el analizador
  • El texto no ASCII se romperá · línea 10

    load(InputStream) decodifica los bytes como ISO-8859-1, así que un archivo guardado en UTF-8 convierte cada carácter acentuado en dos o tres latinos. Escríbelos como \uXXXX o carga con un Reader y un charset explícito.

    • greeting: "Grüße" → "Grüße"
  • Separador de espacios · línea 4

    En esta línea no hay = ni :: la primera racha de espacios o tabuladores separa la clave del valor, así que "c three" es c = three.

    app.mode

  • Espacios finales conservados · línea 9

    El espacio alrededor del separador se descarta, pero el del final del valor forma parte del valor. Ningún editor lo muestra y ningún error lo menciona.

    app.token (+3)

  • Separador dentro de la clave · línea 6

    Esta clave contiene un =, : o espacio escapado, así que la línea es una sola clave. El código que corta por el primer "=" la partiría en el sitio equivocado.

    menu.label 1

  • No es un comentario · línea 14

    # y ! abren un comentario solo como primer carácter no blanco de la línea. Aquí aparece detrás de un valor, así que se queda dentro del valor.

    note

  • Clave duplicada · línea 2, 12

    La misma clave se asigna más de una vez. load conserva el último valor y no avisa de nada: las líneas anteriores son configuración muerta que sigue pareciendo viva en un diff.

    app.name → "orders-api-v2"

LíneaClaveValorSeparadorNotas
1# Spring Boot style configuration — and every trap in the formatcomentario
2app.nameorders-api=sobrescrita
3server.port8080:
4app.modeproductionespacioSeparador de espacios
5jdbc.urljdbc:postgresql://db:5432/orders?ssl=true=
6menu.label 1Save as…=Separador dentro de la clave
7–8welcome.messageHello and welcome=
9app.tokenabc123···=Espacios finales conservados
10greetingGrüße=
11greeting.escapedGrüße=
12app.nameorders-api-v2=
13pathC:\\Users\\dev\\app=
14notevalue # this is not a comment=No es un comentario

La tabla escapa los caracteres de control (\n, \t) y marca los espacios finales con · para que se vean; la vista JSON es exacta.

Cómo usar

  1. Pega el contenido en el cuadro o suelta un archivo .properties en el área de arrastre: se lee en el navegador y no se sube a ningún sitio.
  2. Lee la tabla por líneas: cada entrada con la línea de la que salió, el separador que se usó realmente y una etiqueta para lo que sea inusual.
  3. Repasa los hallazgos de arriba. Claves duplicadas, espacios finales y una continuación que se tragó la entrada siguiente son los tres que cambian el comportamiento sin avisar.
  4. Si hay caracteres no ASCII, cambia la lectura entre UTF-8 e ISO-8859-1 para ver exactamente qué le entregará load(InputStream) a tu aplicación.
  5. Copia la vista JSON para comparar configuraciones, o la vista Properties.store para obtener el mismo mapa escrito con todos los separadores y caracteres no ASCII ya escapados.

Preguntas frecuentes

¿Por qué "c three" se divide en clave y valor si no hay un signo =?
Porque en este formato el espacio en blanco también es un separador. Properties.load busca el primer =, : o carácter de espacio sin escapar, el que llegue antes, y toma como clave todo lo anterior. Por eso "c three" es la clave c con el valor three, y en "key with spaces=x" la clave es "key" y el valor "with spaces=x": el = nunca llega a actuar como separador. Si necesitas un espacio dentro de una clave tienes que escribirlo como "\ ", que es exactamente lo que hace Properties.store cuando vuelve a escribir esa clave.
Mis caracteres acentuados salen como é. ¿Qué ha pasado?
La especificación de java.util.Properties.load(InputStream) dice que el flujo se decodifica como ISO-8859-1, un byte por carácter. Un archivo guardado en UTF-8 almacena é en dos bytes, y esos dos bytes se convierten en dos caracteres latinos: el mojibake clásico. No se lanza ninguna excepción, así que la corrupción llega hasta donde se muestre el texto. Hay tres arreglos: escribir lo no ASCII como \uXXXX (lo que producen native2ascii y Properties.store), llamar a load(Reader) con un InputStreamReader en UTF-8, o usar loadFromXML, que es UTF-8 por defecto.
¿De verdad sobreviven los espacios al final de un valor?
Sí, y son el origen de todo un género de informes de error. El espacio al principio de la línea se descarta, el que rodea al separador se descarta y la sangría de una línea de continuación también, pero el espacio al final del valor se conserva tal cual, porque la línea acaba ahí y nadie lo recorta. Una contraseña o una URL con un espacio de más se analiza sin problema, no conecta con nada y parece idéntica a la versión buena en cualquier editor y en cualquier revisión de código. La tabla de esta página marca esos caracteres para que se vean.
¿Cuándo continúa una línea en la siguiente?
Cuando termina en un número impar de barras invertidas. La última barra se elimina, se añade la línea siguiente y se quita su sangría, de modo que una continuación indentada se lee bien. Un número par no es una continuación: son barras literales, a la mitad, y la entrada acaba ahí. De ahí salen dos sorpresas. Una continuación puede tragarse lo que parece la entrada siguiente: si un valor acaba en una barra perdida, el "clave=valor" de debajo pasa a formar parte de ese valor y la clave desaparece. Y una línea de comentario acabada en barra no continúa en absoluto, porque los comentarios se descartan enteros.
Dos líneas asignan la misma clave. ¿Cuál gana?
La última, en silencio. Properties es una Hashtable y load simplemente llama a put por cada entrada según avanza, así que una línea posterior sobrescribe a la anterior sin aviso, sin error y sin dejar rastro. Es fácil provocarlo sin querer cuando un archivo se ensambla a partir de varios fragmentos, o cuando una clave se define una vez con = y otra con : y las dos no se parecen a simple vista. Esta página lista cada clave duplicada con todos los números de línea que la asignan y atenúa las que pierden.
¿Es el mismo formato que un archivo .env?
No, y tratar uno como el otro es una fuente habitual de averías silenciosas. Un .env no tiene separador por espacios, usa comillas para proteger valores y para guardar cadenas de varias líneas, trata un # tras el valor como comentario en línea y a menudo admite expansión ${VAR}. Un .properties no tiene nada de eso: las comillas son caracteres corrientes que acaban dentro del valor, el # tras un valor forma parte del valor, el formato en sí no expande variables (Spring añade lo suyo por encima) y la continuación se hace con barras invertidas y no con comillas.

Herramientas relacionadas