13.6.13

Abordando problemas de encoding, en Java

Posiblemente, los problemas derivados de un mal uso o mala comprensión del encoding, sea uno de los principales dolores de cabeza para muchos desarrolladores. Es fácil determinar que cuando en una web estamos viendo «camión» en vez de «camión», es que pasa algo con el encoding. Pero ¿qué está pasando exactamente? En realidad, no es algo tan complicado una vez se entiende todo el asunto correctamente. Para ello, recomiendo la lectura del artículo What Every Programmer Absolutely, Positively Needs To Know About Encodings And Character Sets To Work With Text. Es más, me atrevo a decir que es de lectura obligada para todo aquel que no haya sabido determinar inmediatamente, por qué podríamos ver «camión» en vez de «camión» (y no, no vale decir simplemente «algo del encoding»).

Para los que no puedan leer con fluidez la lengua de Shakespeare, resumiré los puntos más importantes:

  1. Para un ordenador, todo son números. No, miento, son ceros y unos. Bits. El que parezca que se almacenen letras y símbolos, es porque se utiliza una relación biunívoca (uno a uno) entre caracteres y bits.
  2. Un charset o juego de caracteres, es un conjunto de caracteres representables, con un código numérico asociado a cada uno. Un encoding o codificación, es la forma de representar en bytes, el código numérico del carácter.
  3. Unicode no es un encoding, es un charset. Uno muy interesante, ya que incluye todos los posibles caracteres de todos los idiomas del mundo. UTF-8 y UTF-16 son encodings de Unicode. Esta distinción es irrelevante en codificaciones donde no hay más de 256 caracteres, como ASCII o ISO-8859-1.
  4. La codificación de los primeros 127 caracteres de UTF-8, la familia ISO-8859, Windows-1252, y muchos otros, es igual que la ASCII. ASCII no incluye vocales acentuadas o la eñe. Es por eso que sólo aparecen problemas de encoding cuando usamos estos caracteres.
  5. En UTF-8, para caracteres con código mayor que 127 (hex 7F), se emplea más de un byte (hasta un máximo de 4). No todas las secuencias de bytes son válidas. Cuando la aplicación encuentra un byte conflictivo, puede mostrar el símbolo de cierre de interrogación «?» o el carácter de sustitución Unicode «�» (U+FFFD)

Para los lenguajes que usan el alfabeto latino, podemos encontrar varios sistemas de codificación diferentes. En Windows, por ejemplo, se utiliza por defecto la codificación Windows-1252 (o CP-1252). En Linux, se utiliza generalmente UTF-8. En algunos servidores no Windows podemos encontrar ISO-8859-1 ó ISO-8859-15. Cuando un texto se muestra de forma extraña, el problema siempre es el mismo: el texto está almacenado con una codificación determinada, pero la aplicación que lo muestra lo está interpretando con otra codificación.

Veamos un ejemplo. La cadena "año", se representa en UTF-8 como 61 C3 B1 6F. Es importante recordar que la 'ñ' se representa con dos bytes: C3 B1 ¿Qué ocurre si una aplicación interpreta esta secuencia de bytes como un texto codificado en CP-1252? En esta codificación, el byte C3 corresponde al caracter 'Ã' y el byte B1 al caracter '±', por lo que esa aplicación mostrará "año".

Vamos con el ejemplo contrario. La cadena "año" se representa en CP-1252 como 61 F1 6F. Si una aplicación intenta interpretar esta secuencia de bytes como si fuera UTF-8, se encontrará con que el byte F1 (11110001) es el primer byte de una secuencia de 4, pero que el siguiente byte debería estar entre 80 y BF (cumplir el patrón 10xxxxxx) y no es así. Al ser F1 un carácter «conflictivo», lo escapará con un '�', continuando con la intrepretación. De esta forma, se mostrará "a�o".

El problema puede complicarse si al cargar un dato se comete un error de codificación, y al mostrarlo se comete otro, teniendo entonces un «doble error». Imaginad, por ejemplo, una cadena "año" codificada en UTF-8, pero interpretada como ISO-8859-15. Como desde el punto de vista de una máquina, la cadena "año" es perfectamente válida, no saltaría ningún error. Si más adelante, a la hora de enviar el dato a otro sistema (por ejemplo, desde una aplicación web al navegador del usuario), se codifica como UTF-8 pero se interpreta en destino como CP-1252, tendríamos un «doble error», y veríamos "año": El motivo es que 'Ã' se representa en UTF-8 como c3 83, y '±' como c2 b1. En CP-1252, el byte c3 corresponde a 'Ã', el 83 a 'ƒ', el c2 a 'Â' y el b1 a '±'.

Con esto ya tenemos unas de reglas de andar por casa, para averiguar lo que está pasando cuando vemos textos raros:

  1. Si vemos caracteres � en vez de vocales acentuadas o eñes, la aplicación está interpretando como UTF-8 un texto que en realidad está almacenado como CP-1252 o ISO-8859.
  2. Si vemos que las eñes o vocales acentuadas son sustituidas por dos caracteres "extraños", la aplicación está interpretando como CP-1252 o ISO-8859 un texto que en realidad está codificado como UTF-8.
  3. En general, si vemos que las eñes o vocales acentuadas son sustituidas por 2n caracteres "extraños", se está cometiendo el error anterior n veces a lo largo del ciclo de vida del texto.

Las codificaciones CP-1252, ISO-8859-1 e ISO-8859-15, codifican de igual forma las vocales acentuadas y eñes. Si queremos hilar más fino y diferenciarlas, tenemos que toparnos con el símbolo del euro: '€'. En CP-1252 se codifica como 80, que en ISO-8859-1 e ISO-8859-15 corresponden a un caracter de control no imprimible. En ISO-8859-15 se codifica como A4, que en CP-1252 y en ISO-8859-1 corresponde al caracter '¤'. Y en ISO-8859-1, sencillamente no existe dicho símbolo (y por eso, es una codificación que debería abandonarse cuanto antes). Para completar, en UTF-8, el € se representa como E2 82 AC. Es decir, con 3 bytes, por lo que habrá que tenerlo en cuenta al aplicar las reglas mencionadas.

En una aplicación mínimamente compleja, desarrollada en Java, el error de codificación puede tener múltiples orígenes. Así que vamos a ir revisando poco a poco qué codificaciones se usan en cada punto, y cómo se pueden cambiar.

Como todo buen programador en Java debe saber, la plataforma utiliza internamente la codificación UTF-16 para almacenar en memoria las cadenas de texto. En realidad, a menos que necesitemos meternos en las tripas de bajo nivel de una JVM, este dato es una mera curiosidad, ya que a todos los efectos, una String es como un array de caracteres, es decir, de char. Salvo algunos métodos concretos, se opera a nivel de caracter. Así el resultado de


"1€ al año".length()

siempre será 9, sin importar la codificación, puesto que lo que nos devuelve es el número de caracteres, y no el número de bytes. Por el contrario, el número de bytes varía con la codificación, siendo 9 en ISO-8859-15, 12 en UTF-8 (el € son 3 bytes) y 18 en UTF-16 (2 bytes por caracter).

Siguiente paso: los fuentes. Un fichero .java no deja de ser un simple fichero de texto plano. Es el compilador el que leerá este fichero y generará el .class correspondiente. ¿Qué codificación usa el compilador? Pues por defecto utiliza la misma que la plataforma en la que se esté ejecutando. Esto es, si compilamos nuestros fuentes en Windows, se usará CP-1252. Si compilamos en Linux, se usará UTF-8. Es algo bastante razonable, puesto que la mayoría de editores que usemos, tendrán ese mismo comportamiento. Así, el siguiente ejemplo:


public class SillyExample {
    public static void main(String[] args) {
        System.out.println("El niño se gastó 1 € en chuches.");
    }
}

debería compilar y ejecutarse sin problemas, y mostrar en la consola lo mismo, sin importar la codificación de la plataforma:


El niño se gastó 1 € en chuches.

Process finished with exit code 0

Este comportamiento puede ser modificado, con la opción -encoding del compilador, que nos permite especificar la codificación de los fuentes. Obviamente, si la codificación de los ficheros .java no coincide con la que se use al compilar, ya tenemos un problema. Por lo general, un IDE en condiciones gestionará esto de forma transparente a nosotros, y si le decimos que use una codificación concreta, usará la misma al guardar los fuentes y en la opción del compilador.

Un primer paso para diagnósticar un problema de encoding con literales que se declaran en los fuentes, es averiguar la codificación de éstos. Para ello, lo más seguro es abrir un .java que sepamos que tiene algún carácter no ASCII, con un visor hexadecimal. Si el caracter está representado con dos bytes, el .java está en UTF-8. Si está representado con un solo byte, no.

No todas las cadenas que mostrará nuestra aplicación estarán en los .java. De hecho, eso no es una buena práctica. Lo habitual es tener los textos que se mostrarán al usuario en ficheros separados.

La forma habitual de mantener los textos que se mostrarán al usuario es mediante los ficheros .properties, que además tienen la ventaja de facilitar enormemente la internacionalización de la aplicación. Y aquí nos topamos con una limitación: la clase utilizada habitualmente para leer los ficheros .properties, ResourceBundle, interpreta siempre los ficheros como ISO-8859-1, no importa lo que hagamos. Esto puede suponer un inconveniente en algunos casos, ya que el juego de caracteres de esta codificación, no incluye el símbolo del euro. Afortunadamente, podemos especificar caracteres no incluidos en ISO-8859-1 utilizando el formato \uXXXX, donde XXXX es el código Unicode del caracter. Así, podríamos tener un .properties con la siguiente línea:


text=El niño se gastó 1 \u20AC en chuches.

Si queremos que nuestros .properties estén en otra codificación, como UTF-8, podríamos usar PropertyResourceBundle, ya que tiene un constructor público al que se le pasa un Reader, pero perderíamos las facilidades de ResourceBundle para la internacionalización. Otra alternativa es buscar funcionalidades similares en otras librerías o frameworks.

Ya que mencionamos la clase Reader, cuando leemos o escribimos un fichero con FileReader y FileWriter, se usa la codificación por defecto de la plataforma donde se está ejecutando. Esta misma codificación es la que se usa en toda conversión de caracteres a bytes y viceversa (con la excepción de los .properties leídos por ResorceBundle), como en el constructor String(byte[]) y el método byte[] getBytes() de String, o las clases InputStreamReader y OutputStreamWriter. En algunos casos, existirán variantes de los métodos en los que se les pueda especificar la codificación a usar de forma explícita, pero si no se hace, se usará la codificación por defecto de la plataforma.

Se puede modificar la codificación por defecto con la variable de sistema file.encoding, pero sólo podemos hacerlo en el arranque de la VM, con la opción -D (por ejemplo, -Dfile.encoding=UTF-8). Si lo hacemos en el código, usando System.setProperty("file.encoding", "UTF-8"), no funcionará. En cualquier caso, insisto en que esa configuración no afecta al comportamiento de ResourceBundle, que seguirá esperando los .properties en ISO-8859-1.

Así que aquí tenemos dos puntos más a revisar cuando encontramos problemas de encoding en una aplicación: la codificación de otros ficheros de texto además de los fuentes, el mecanismo utilizado para leerlos, y la codificación que está usando la JVM. Una forma sencilla de comprobar si los textos se están cargando con la codificación adecuada, es con el depurador de toda la vida. Si en un breakpoint evaluamos con un buen IDE el contenido de un String, podremos ver el número de caracteres, o mejor aun, el array de char interno. Si vemos los caracteres que deben donde deben, el problema no está en la lectura de ficheros u otros recursos.

Para ayudarnos en esta tarea, es muy útil la web FileFormat.Info, donde podemos consultar el código numérico de cualquier carácter Unicode, así como sus diferentes representaciones y más información útil. Echad un vistazo, por ejemplo, a nuestra querida «ñ».

Vamos ahora a entrar en el mundo de las aplicaciones web, que es donde suelen producirse estos errores (posiblemente porque Java tenga más presencia en la web que en el escritorio, y porque se introducen capas adicionales donde cometer errores). Una posibilidad es que el HTML se esté enviando por la red con una codificación, y el navegador la esté interpretando con otra.

Para determinar la codificación que debe emplear un navegador a la hora de mostrar el HTML de una response HTTP, se utiliza la cabecera HTTP Content-Type. Esta cabecera puede establecerse en la configuración del propio servidor (que variará dependiendo de qué utilicemos), en el código Java mediante los métodos setContentType(String) o setCharacterEncoding(String) del objeto ServletResponse (no son exactamente equivalentes, y hay peculiaridades que conviene consultar en la documentación), o mediante el atributo contentType de la directriz JSP page. Por ejemplo:


<%@ page contentType="text/html;charset=UTF-8"%>

Si esta cabecera no aparece, o no indica la codificación, el navegador busca una etiqueta meta presente en el HTML de la página, que a su vez puede ponerse como


<meta http-equiv='Content-Type' content='text/html; charset=utf-8'>

o como


<meta charset='utf-8'>

Y si tampoco encuentra esta etiqueta, el navegador utilizará normalmente la codificación ISO-8859-1. Dado que esta codificación es anterior al euro, y no tiene el símbolo «€» en su juego de caracteres, si usamos esta codificación, nos veremos obligados a usar la entidad HTML &euro; para representarlo. De hecho, podemos limitarnos a usar únicamente caracteres ASCII, y emplear entidades HTML para los caracteres no ASCII, pero eso no soluciona todos los problemas, e introduce una incomodidad innecesaria a día de hoy.

Aunque se puede especificar la codificación del response sólo con los métodos de ServletResponse, si usamos JSPs debemos especificar la codificación también en la directiva page. Recordemos que una JSP se traduce internamente en un .java que luego se compila. La implementación concreta depende del servidor utilizado, pero puede ocurrir (y de hecho ocurre con Tomcat 6) que el encoding utilizado para generar el .java y compilarlo, dependa de esa directiva, de forma que si no aparece, se utilice una codificación diferente a la que deseamos (Tomcat 6 parece usar ISO-8859-1, independientemente de lo que especifiquemos en la propiedad file.encoding).

Otra fuente de error son los formularios. Cuando se hace submit de un formulario, el navegador utiliza en la request la misma codificación que haya usado para mostrar la página. Este comportamiento se puede modificar mediante el atributo accept-charset de la etiqueta form, indicando el encoding a usar (sólo el encoding, no todo el Content-Type). Sin embargo, no funciona en todos los navegadores, por lo que no es conveniente utilizarlo.

En el lado del servidor, la clase ServletRequest tiene un método getCharacterEncoding(), que debería devolvernos la codificación de la request. ¿Por qué digo «debería». Pues porque lo único que puede hacer es obtener dicho valor de la cabecera HTTP correspondiente. Los navegadores no suelen enviar esta información, por lo que en la mayoría de los casos, una llamada a este método devolverá un null. Es decir, no podemos confiar en dicho método para saber la codificación de la request. Así que debemos usar una de forma consistente en el response, y asumir que las request nos llegan con esa misma codificación.

Hay una gran excepción a esta regla (encoding de la request = encoding de la página del formulario), que sucede cuando usamos Ajax. Ajax usa por defecto UTF-8 en la request. Si no tenemos esto en cuenta, en una aplicación web en la que se use ISO-8859-15, por ejemplo, recibiremos una request UTF-8, que el servidor interpretará incorrectamente.

Una solución es especificar de forma explícita en nuestra aplicación, que la request viene codificada como UTF-8. Esto se puede hacer con el método setCharacterEncoding(String) de la clase ServletRequest. Este método simplemente establece con qué codificación se debe leer la request. Es decir, no realiza ninguna transformación de encoding. Si la request nos viene con una codificación, y establecemos una diferente con este método, los valores de los parámetros se leerán incorrectamente. Obviamente, hay que llamar a setCharacterEncoding(String) antes de leer algún parámetro de la request.

Una aplicación web que use una codificación en unas request, y otra diferente en otras, es muy mala idea. También es mala idea usar codificaciones diferentes para la request y el response. Lo mejor es usar la misma codificación de forma coherente en toda la aplicación. En JEE es muy sencillo establecer la codificación de request y response en un filtro (una clase que implemente Filter) y configurarlo en el web.xml. Como en las JSPs que usemos, debemos especificar en la directiva page esa misma codificación, podemos tener una JSP únicamente con esa directiva, e incluirla en todas las demás (o usar algún sistema de plantillas, para asegurarnos que todas incluyan la misma directiva).

Y ahora la gran pregunta: ¿qué codificación usar para toda nuestra aplicación? Mi elección personal es UTF-8, ya que al ser una codificación Unicode, tenemos acceso a todos los caracteres posibles. Además, puesto que Ajax utiliza esa misma codificación por defecto a la hora de hacer las request, evitamos tener que complicarnos con ese tema. El problema son los .properties, que si los vamos a leer con ResourceBundle, deben ser ISO-8859-1. Si nuestro IDE lo permite, podemos configurarlo para que todos los ficheros del proyecto estén en UTF-8 menos esos, o si usamos algún framework, buscar si incluye algún sistema alternativo que soporte UTF-8. También podemos optar por usar la clase PropertyResourceBundle, que nos permite usar un Reader, de forma que tengamos control de la codificación a usar, y si necesitamos multilenguaje, implementar nosotros mismos el comportamiento de ResourceBundle en ese caso (que básicamente es añadir al nombre el Locale utilizado, antes de la extensión, e ir eliminando fragmentos del mismo hasta encontrar el recurso).

32 comentarios:

  1. Hola, muchas gracias por este artículo tan completo. Me ha servido para aprender algunas cosas y para confirmar alguna otra que ya sabía.
    Sólo me gustaría comentar que al realizar en Windows el ejemplo de prueba que tú indicas ("SillyExample"), compilando con el comando "javac" y ejecutando con el comando "java", obtengo esta salida por consola:

    El ni±o se gast¾ 1Ç en chuches.

    Para que el ejemplo me funcione (es decir, para que en la salida por consola se muestren los caracteres correctamente)necesito realizar dos pasos adicionales:
    1)Establecer como tipo de letra en las propiedades del intérprete de comandos (el conocido "Símbolo de sistema de Windows") "Consolas" o "Lucida Console".
    2)Cambiar la "página de códigos" utilizada empleando el comando chcp. En concreto, para utilizar la codificación de caracteres CP-1252 hay que teclear: chcp 1252.

    De esta forma se me muestra la salida correctamente.

    Un saludo.

    ResponderEliminar
  2. Gracias por la aclaración. Se me pasó comentar algo que parece de perogrullo, pero que también puede dar problemas: el tipo de letra utilizado, debe tener los símbolos que queremos ver (no todos tienen el �, por ejemplo, y he tenido que ajustar los fonts del blog para este post).

    Lo que no sabía era que la codificación por defecto de la consola de Windows (en castellano) es CP-850. Extraña decisión, teniendo en cuenta que la del sistema en sí es CP-1252. Supongo que será por alguna cuestión de compatibilidad hacia atrás.

    ResponderEliminar
  3. Gracias por el artículo.

    Nos ha venido más que al pelo para aclarar algunos temas sobre los que estábamos discutiendo en el proyecto en el que estoy ahora y donde se mezclan desarrollos en Eclipse sobre Windows, despliegues sobre WebLogic en Unix, Java, JavaScript, XML, HTML, ...

    Una pequeña pesadilla si no se tienen claras las ideas (y no las teníamos del todo)

    Lo dicho, gracias por las aclaraciones.

    Saludos.

    ResponderEliminar
  4. Me alegro mucho que os haya ayudado este post, Loren. Objetivo conseguido :-)

    ResponderEliminar
  5. Saludos!

    He encontrado esta página a raíz de un problema que tengo con el tema, que me lleva de cabeza hace mucho. Si alguien fuera tan amable de ayudarme, se lo agradecería eternamente...

    La cosa es tan sencilla como esto: tengo dos páginas jsp, una contiene un formulario y la otra simplemente imprime un parámetro recibido a través del formulario de la primera. La historia es que en la segunda se me imprimen los caracteres mal, tipo "año" en vez de "año".

    Por lo que he entendido de este post, la historia es que mi servidor está descodificando con ISO o CP algo que está codificado en UTF-8, y no hay manera de cambiar eso. Ambas páginas contienen las directivas/etiquetas:

    <%@page contentType="text/html" pageEncoding="UTF-8"%>

    (pongo m... en vez de meta)

    Y además, indico con un scriptlet que los datos que llegan se deben de interpretar como UTF-8: <% request.setCharacterEncoding("UTF-8"); %>

    Pero ni por esas... ¿Alguna idea? Muchas gracias de antemano

    ResponderEliminar
    Respuestas
    1. Me ha eliminado una línea con la etiqueta meta, pero bueno, es la etiqueta que define que uso UTF-8

      Eliminar
  6. Bueno, para quien le pueda interesar, he encontrado la solución. Por lo visto los servidores JEE (al menos Glassfish v4 y Tomcat por lo que he podido leer por ahí) cuando extraen los parámetros de la petición los interpretan por defecto como ISO-8859-1, aunque en la petición HTTP se especifique otra codificación (ridículo, pero cierto).

    Mi intento de arreglarlo con request.setCharacterEncoding("UTF-8"); no funciona porqué según dice la API:

    "This method must be called prior to reading request parameters or reading input using getReader(). Otherwise, it has no effect. "

    Por tanto, muy probablemente hay alguna clase de java (no mía, tal vez manejada por el container) que está haciendo lecturas previamente a que yo haga la 1ª, y ello hace que no funcione.

    Mi solución ha consistido en volver a codificar el ISO y decodificar posteriormente en UTF-8, tal que así:

    nuevoParam = new String(antiguoParam.getBytes("ISO-8859-1"), "UTF-8");

    Cutre, pero funciona. Esta tarde probaré si usando un filtro consigo que funcione request.setCharacterEncoding("UTF-8"); Ya os contaré.

    ResponderEliminar
  7. Optimus, ¿estás usando algún tipo de framework para la vista web (Spring, Struts, o similar)? Puede que sea alguna clase de ese framework el que esté leyendo parámetros de la request, antes de que se ejecute tu código. Si es así, tal vez se pueda configurar de alguna forma.

    Otra posibilidad, para asegurarte de que la request se interpreta con el encoding que tú quieres, es llamar a request.setCharacterEncoding("UTF-8") en un filtro, esto es, una clase que implemente javax.servlet.Filter y que configures en el web.xml (si no has usado filtros, hay una introducción en la web de Oracle: http://www.oracle.com/technetwork/java/filters-137243.html). Por lo general, los filtros deberían ejecutarse antes que el resto del código. Si el framework que uses los utiliza, tendrías que ver cómo se configura para que el tuyo se ejecute antes que los demás (e incluso puede que ya tenga un filtro para el encoding implementado, como Spring con su CharacterEncodingFilter)

    ResponderEliminar
  8. Pues no, empleo NetBeans tal cual como viene cuando lo descargas, y implemento el Servlet extendiendo directamente de HttpServlet. Además, mis clases son todas POJOs.

    Sí, lo del Filtro es algo que, como he dicho ahí arriba, tengo pendiente de probar. Sería una solución más deseable que la que uso ahora mismo.

    ResponderEliminar
  9. Por cierto, no lo he comentado antes, pero la decodificación incorrecta también ocurre cuando leo los parámetros de la request desde un Servlet, antes de pasarlo a algún jsp y/o base de datos.

    ResponderEliminar
  10. Claro, en cuanto se lee algún parámetro de la request, ya no sirve de nada el llamar a setCharacterEncoding(). El filtro es la mejor solución precisamente porque se ejecuta antes que cualquier servlet o JSP.

    ResponderEliminar
  11. Bueno, pues he probado lo del filtro y NO me funciona. No comprendo muy bien qué es lo que pasa. Es como si el IDE me introdujera algún tipo de framework que lee de la request antes que yo, imagino que porqué debe de haber algún filtro que desconozco que se ejecuta antes que el mío...

    ResponderEliminar
  12. Pues sí que es raro. Yo probaría a poner un breakpoint en el filtro y ejecutar en modo de depuración. Así, te aseguras de que el filtro se está ejecutando, y puedes inspeccionar la request. También probaría con algún monitor de red (fiddler es muy sencillo de usar), para ver qué está viajando exactamente en la request a nivel de byte, qué cabeceras HTTP tiene, etc.

    ResponderEliminar
  13. Hola!

    El filtro se ejecuta seguro, porqué en medio tengo metida una llamada al logger, la cual visualizo por consola sin problemas.

    En cuanto a ver la petición...hombre podría mirarlo de hacer. De todas maneras, yo creo que es obvio que la petición HTTP se envía en UTF-8 y el servidor la decodifica en iso-8859-1. La prueba más fehaciente de ello es que cuando en el programa codifico en iso y posteriormente decodifico en UTF-8 me está dando los resultados correctos.

    Nada, que a seguir investigando :) En cualquier caso, muchas gracias por la ayuda, y si tengo algún proyecto os lo comunicaré!!

    ResponderEliminar
  14. Finalmente he encontrado la solución a este problema, espero que esto pueda ayudar a alguien...

    El problema es Glassfish. No sé porqué motivo, pero Glassfish ignora todos mis intentos de cambiar la codificación de la request, tanto si lo intento en filtros como en Servlets como en un JSP.

    La solución ha consistido a añadir en glassfish-web.xml lo siguiente:



    Y voilà! todo arreglado. Por lo visto, glassfish por defecto interpreta los parámetros del formulario enviados por POST como iso-8859-1.

    Insisto, espero que le sirva a alguien...

    Saludos y gracias por la ayuda!!!

    ResponderEliminar
  15. Fantástico, la línea interesante ha sido filtrada xD

    lo que he añadido es esto: parameter-encoding default-charset="UTF-8", encerrado entre "menor que" y "mayor que". Supongo que se entiende

    ResponderEliminar
    Respuestas
    1. Gracias. Después de dar 2.000 vueltas buscando una solución, me ha funcionado metiendo esa línea en el descriptor de GlassFish. Mi problema era enviar un parámetro String a un Web Service SOAP. Ya le llegaba "distorsionado" al servidor y al enviar la respuesta lo mismo. Ahora funciona perfectamente.

      Eliminar
  16. Estimados:
    Disculpen la molestia. Tengo una aplicación web elaborada netamente en jsp (sin framework), y se ha presentado el inconveniente con las tildes y ñ's. Asimismo, la base de datos con la que trabajo es Oracle y está configurada con el character set US-ASCII. He intentado con el cambio de encoding en las jsp's, tal como ISO-8859-1, windows-1252 y UTF-8, pero no ha generado ningún resultado bueno. Les agradecería me puedan ayudar.

    ResponderEliminar
    Respuestas
    1. Si la base de datos está en US-ASCII, ahí tienes el problema. No vas a ser capaz de guardar eñes o vocales acentuadas en las tablas.

      Eliminar
  17. Gracias por este post!! Me ha ayudado a aclarar diversos aspectos acerca de la codificación, campo en el que estaba muy verde jeje

    ResponderEliminar
  18. Gracias por lo explicado, Alfonso, ha sido muy instructivo No obstante queria preguntarte una cosa. Desde una aplicacion java obtengo una pagina web y extraigo del codigo fuente cierta palabra que me interesa, pero al momento de imprimirla por la consola, la pone mal; los acentos y eñes los pone con dos caracteres. Supongo que es porque los datos vienen en formato utf-8 (ademas en la etiqueta meta de la pagina pone charset=utf-8) y al estar en windows, intenta ponerlo en codificacion 1252. Utilizo HttpURLConnection BufferedReaded y InputStreamReader a la hora de ir cogiendo linea a linea del codigo fuente obtenido. El caso es que no sé como puedo convertirlo, adaptarlo, para que transforme ese formato utf-8 al de windows y se lea bien. ¿ Podrias ayudarme ?. Te estaría muy agradecido.

    ResponderEliminar
    Respuestas
    1. Entiendo que estás envolviendo el InputStream que te devuelve el HttpURLConnection en un InputStreamReader, y a su vez, eso lo envuelves con un BufferedReaded. ¿Estás usando el constructor de InputStreamReader al que le puedes pasar un segundo argumento (el encoding) o el de un sólo argumento?

      Eliminar
    2. ¡Ops! Quería decir BufferedReader, claro.

      Eliminar
  19. ... en CP-1252 y en ISO-8859-1 corresponde al caracter '¤'. Y en ISO-8859-1, sencillamente no existe... en cual iso no existe?, gracias por la info

    ResponderEliminar
    Respuestas
    1. En la ISO-8859-1 no existe el símbolo del euro.

      Lo que quería decir es que el byte A4, que corresponde al símbolo del euro en la ISO-8859-15, en la CP-1252 y en la ISO-8859-1 corresponde al caracter ¤.

      Es decir:

      En CP-1252, el caracter del euro corresponde al hex 80.

      En ISO-8859-15 el caracter del euro corresponde al hex A4.

      En ISO-8859-1 el caracter del euro no existe.

      Eliminar
  20. Muchas gracias por la información. Muy útil para los que nos frustramos con los mil y un problemas que da el encoding. Añado una referencia que me ha sido de mucha utilidad a la hora de configurar Tomcat y App Java:
    http://stackoverflow.com/questions/138948/how-to-get-utf-8-working-in-java-webapps

    ResponderEliminar
    Respuestas
    1. Gracias a tí, Jesús, por el enlace a StackOverflow. Muy completo e interesante.

      Eliminar
  21. Muchas gracias! Gracias a esto pude dar en la tecla con un inconveniente que tenía y no estaba pudiendo resolver!!! :)

    ResponderEliminar
  22. Fantástico, parecería increíble pero el articulo aun es válido

    ResponderEliminar
  23. La verdad es que pueden correr ríos de tinta con este tema...
    Yo tengo un proceso batch que coge los datos de un csv y los inserta en una base de datos.
    Pues madre mia...
    Hago el programa con eclipse y me funciona correctamente, exporto a un jar el programa y me van mal las tildes.
    Después de 3 horas buscando cosas por internet poniendo encoding de todas las maneras posibles, no hay manera...
    Finalmente desesperado le cambio el encoding al csv y me va mal en el eclipse pero me va bien el jar...
    Y ya lo flipo...
    Si alguien le puede encontrar la lógica...
    base de datos = UTF-8 Siempre:
    csv= UTF-8 -> funciona en eclipse pero no el jar,
    csv = ISO-8859-1 funciona el jar pero no el eclipse.
    La consola de windows mal siempre unas veces me pone la ó como Ë y otras como ó.
    En fin...

    ResponderEliminar