6.4.16

SQLException ORA-01843 al migrar a Java 7

Hace mucho que no escribo nada aquí, lo que es una buena señal, ya que eso quiere decir que no me he encontrado con problemas que merezcan contarse. Hasta ahora.

Recientemente, tuve que migrar una aplicación web que estaba ejecutándose con Java 5, a un servidor con Java 7. Uno podría pensar que debe ser una tarea trivial. Después de todo, se supone que uno de los eslóganes de Java es el famoso WORA, y además, Java 7 debería ser compatible hacia atrás (no introduce nuevas palabras clave, como sucedió con Java 5). Sin embargo, tras arrancar la aplicación y navegar un poco por ella, en seguida saltó un error: Una java.sql.SQLException con el siguiente mensaje:


ORA-01843: not a valid month

Obviamente, la aplicación utilizaba una base de datos Oracle. Pero la base de datos seguía siendo la misma. Es más, el servidor de aplicaciones (un Weblogic 10.3) también era el mismo. La máquina física (un servidor Solaris) era la misma. Todo era exactamente igual que antes, salvo la versión del JDK. ¿Qué estaba ocurriendo?

Buscando en la documentación de Oracle, es fácil averiguar que el error en cuestión indica un problema en el formato de la fecha, cuando se ejecuta una sentencia contra la base de datos. Concretamente, cuando la parte correspondiente al mes no es un literal correcto. Buceando en el código, pude comprobar que, efecticamente, el error solo aparecía cuando había fechas involucradas en alguna query.

Hay que explicar que la aplicación era bastante vieja, y que no utilizaba ningún tipo de ORM como Hibernate, ni nada parecido. Los accesos a base de datos se hacían «a pelo», usando directamente el API JDBC, sin ninguna librería de apoyo. Las queries estaban definidas dentro del código, si bien, al menos se había tenido la prudencia de que fueran parametrizadas (es decir, no se construia la query concatenando directamente variables). En algunos casos, cuando había fechas involucradas, se utilizaban las funciones TO_CHAR y TO_DATE para formatear el dato, con un patrón específico dentro de la query. Pero en otros, se confiaba ciegamente en que el formato por defecto era DD/MM/YYYY. Y en esas queries era donde saltaba la excepción.

Así que el motivo el problema era bastante evidente. El formato por defecto de las fechas, ya no era el esperado. Pero ¿por qué? Como ya he dicho, sólo había cambiado el JDK. La aplicación (código y librerías) era la misma, el servidor era el mismo, la base de datos era la misma...

Lo que ocurría era lo siguiente: En Java 7, la determinación del Locale por defecto, cambia con respecto a versiones anteriores. Está reportado como bug, pero parece ser que en realidad es una feature (como los grumos del Cola Cao). Lo que importa es que al arrancar la JVM, el Locale por defecto quedaba establecido a inglés americano, en vez de a castellano español. Eso era lo que había cambiado el formato por defecto de la fecha.

Por supuesto, una solución era modificar todas las queries afectadas, y explicitar un formato de fecha concreto con TO_DATE o TO_CHAR, pero eso suponía mucho trabajo (era una aplicación enorme), y además se quería evitar tocar código si era posible (era un dato del problema). Así que la única solución era establecer el locale por defecto de la JVM a castellano español, que era como estaba antes.

En un servidor Unix, normalmente se hace estableciendo la variable de entorno LANG. Pero eso no funciona con la Java 7 (al menos, no con un Weblogic 10 o un Tomcat 7). Así que lo que hay que hacer es establecer las propiedades de sistema user.language y user.country a nuestro gusto, pasándoselas en el arranque de la aplicación. En mi caso, tuve que añadir lo siguiente en el script de arranque:


-Duser.language=es -Duser.country=ES

Es muy importante hacer notar que hay que proporcionar ambas propiedades. Uno podría estar tentado de indicar únicamente el lenguaje, pero si no se hace lo mismo con el país, el locale no se establece correctamente.