El reto

Actualizar Java puede afectar a toda la cadena tecnológica

El cambio de versión de Java no siempre consiste simplemente en cambiar el JDK. Dependencias, frameworks, APIs, servidores de aplicaciones y configuraciones pueden introducir incompatibilidades que deben identificarse antes de abordar la actualización.

El salto de Java 8 a una versión con soporte arrastra el sistema de módulos, la retirada de APIs que antes venían en el JDK y, con frecuencia, el propio servidor de aplicaciones. Por eso el inventario va primero y la estimación después.

Qué se analiza

Qué entra en el análisis de compatibilidad

  • Análisis de la versión Java actual
  • Identificación de incompatibilidades
  • Revisión de dependencias Maven
  • Actualización de frameworks
  • Adaptación de código afectado
  • Compatibilidad con servidores de aplicaciones
  • Validación de aplicaciones existentes
  • Planificación de actualizaciones progresivas

Tecnologías relacionadas

Las piezas del stack que condicionan la versión de Java

  • Java
  • JDK
  • Maven
  • Spring
  • Spring Boot
  • Hibernate
  • Java EE
  • Jakarta EE
  • WildFly

Qué cambia de verdad al salir de Java 8

Cambiar el JDK es la parte rápida. Lo que consume el tiempo son las tres cosas que arrastra.

La primera es el sistema de módulos, que entró en Java 9. No obliga a modularizar la aplicación, pero sí cierra el acceso por reflexión a las tripas del JDK. A partir de Java 16 esa encapsulación es estricta, y en Java 17 desapareció la opción --illegal-access=permit que permitía convivir con el problema. Cualquier librería que manipulara clases internas deja de funcionar: hay que subirla de versión o sustituirla.

La segunda es la retirada de APIs que antes venían incluidas. JAXB, JAX-WS y compañía salieron del JDK en Java 11. El código compila igual y revienta en ejecución, porque esas clases simplemente ya no están. Son dependencias que pasan a declararse de forma explícita en el pom.xml.

La tercera es el servidor de aplicaciones, que suele ser el que marca el techo real. De poco sirve que el código esté listo para Java 17 si la versión de JBoss EAP que hay en producción solo certifica Java 8. Eso convierte la actualización en dos proyectos, y conviene saberlo antes de comprometer una fecha, no a mitad.

Qué se retiró del JDK y por qué dependencia se sustituye

Estas son las ausencias que más aparecen al pasar de Java 8 a una versión con soporte. Todas se resuelven declarando la dependencia a mano.

APIs eliminadas del JDK, última versión que las incluía y sustituto
APIPaqueteÚltima versión del JDKSustituto
JAXBjavax.xml.bindJava 10jakarta.xml.bind-api y una implementación
JAX-WSjavax.xml.wsJava 10jakarta.xml.ws-api y una implementación
Activationjavax.activationJava 10jakarta.activation-api
Common Annotationsjavax.annotationJava 10jakarta.annotation-api
CORBAjavax.rmi.CORBAJava 10sin sustituto dentro del JDK
Nashornjdk.nashornJava 14motor externo o GraalVM

Todas salieron con el mismo movimiento: sacar del JDK lo que pertenecía a Java EE. Es el mismo cambio de fondo que la migración de Java EE a Jakarta EE, y por eso los sustitutos ya llevan el prefijo jakarta.

Los errores que aparecen el primer día del cambio de JDK

Ninguno es un problema del código propio: son síntomas de que algo del entorno se quedó en la versión anterior.

NoClassDefFoundError: javax/xml/bind/JAXBContext

Causa: JAXB salió del JDK en Java 11. El proyecto compila si alguna dependencia arrastra la API, pero en ejecución no hay implementación que cargar.

Qué se hace: Declarar de forma explícita jakarta.xml.bind-api y una implementación, y comprobar que no conviven a la vez la variante javax y la jakarta en el classpath.

InaccessibleObjectException: module java.base does not opens java.lang

Causa: Una librería llama a setAccessible() sobre clases internas del JDK. Hasta Java 15 eso salía como aviso; desde Java 16 es un error.

Qué se hace: Subir la librería a una versión que no dependa de ello. Abrir el módulo con --add-opens funciona, pero es una tirita: deja el arranque atado a una bandera que nadie recuerda seis meses después.

UnsupportedClassVersionError: class file version 61.0

Causa: Un artefacto se compiló con un JDK más moderno que el que lo ejecuta. Aparece casi siempre cuando el entorno de construcción y el de producción no van sincronizados.

Qué se hace: Fijar la versión con maven.compiler.release en lugar de source y target, que no comprueban que las APIs utilizadas existan en la versión de destino.

NoSuchMethodError en una librería que no se ha tocado

Causa: Hay dos versiones de la misma dependencia en el classpath. Al subir de JDK cambian las versiones de medio árbol y una transitiva gana donde antes perdía.

Qué se hace: Mirar el árbol real con mvn dependency:tree y fijar la versión en la gestión de dependencias del proyecto, no en cada módulo por separado.

A qué versión de bytecode corresponde cada versión de Java

Sirve para leer de un vistazo un UnsupportedClassVersionError y saber qué JDK compiló el artefacto que falla.

Versión de Java y número de versión del fichero class
Versión de JavaVersión de classEstado
Java 852.0sin actualizaciones públicas gratuitas
Java 1155.0LTS
Java 1761.0LTS
Java 2165.0LTS

Las versiones intermedias suben de una en una: Java 12 es 56.0, Java 13 es 57.0 y así sucesivamente.

En qué orden se aborda una actualización de versión

  1. Fijar el techo antes que el objetivo

    La versión de destino no la elige el gusto: la eligen el servidor de aplicaciones y las dependencias que no se pueden sustituir. Se mira eso primero, porque puede convertir el salto a Java 21 en un salto a Java 11.

  2. Construir la red de pruebas

    Si no hay pruebas que demuestren que el comportamiento actual se mantiene, se escriben antes de tocar el JDK. Sin esa red no hay forma de distinguir un cambio correcto de uno que rompe algo que nadie estaba mirando.

  3. Compilar con el JDK nuevo, ejecutar con el viejo

    Se sube el compilador y se deja el entorno de ejecución donde está. Aísla los problemas de compilación de los de ejecución, que son los caros, y permite avanzar por tandas sin bloquear los despliegues.

  4. Subir las dependencias por tandas

    Primero las que bloquean, después el resto. Una tanda por commit, con las pruebas pasando en cada punto. Una actualización masiva en un único commit es imposible de revertir cuando falla en producción.

  5. Cambiar el entorno de ejecución

    Ya en la versión nueva se revisan las banderas de arranque, el recolector de basura y el comportamiento en memoria. Es donde aparecen las sorpresas que no se ven en un entorno de pruebas vacío.

El inventario que se hace antes de tocar nada

  • Versión exacta de Java en construcción y en ejecución, que no siempre coinciden.
  • Servidor de aplicaciones y versión certificada por el fabricante.
  • Dependencias directas y transitivas, con la fecha de su última versión publicada.
  • Uso de APIs retiradas del JDK y de clases internas por reflexión.
  • Plugins de Maven o Gradle, que suelen ser lo último en actualizarse y lo primero en romperse.
  • Cobertura real de pruebas sobre los flujos críticos de negocio.
  • Integraciones que consumen la aplicación, y con qué contrato lo hacen.

De ahí sale una matriz de riesgos ordenada por impacto y un plan por fases. Es la misma base con la que se aborda una modernización completa de la aplicación, solo que acotada al cambio de versión.

Preguntas frecuentes

Dudas frecuentes al subir de versión de Java

¿Se puede pasar de Java 8 a Java 21 directamente?

Técnicamente sí, y a veces es lo más eficiente, porque el trabajo duro es salir de Java 8 y no ir de 17 a 21. Lo que decide es el servidor de aplicaciones y las dependencias: si una pieza crítica solo certifica hasta Java 11, el destino es Java 11 aunque el código aguantara más.

¿Hay que cambiar de servidor de aplicaciones para subir de versión de Java?

Depende de la combinación concreta. Las versiones recientes de WildFly y Tomcat soportan las LTS actuales; las versiones antiguas de JBoss EAP no. Forma parte de la matriz de compatibilidad que sale del análisis inicial, y conviene resolverlo antes de comprometer un calendario.

¿Cuánto tarda una actualización de versión de Java?

Depende del inventario, y por eso el inventario va primero. Lo que marca el ritmo no son las líneas de código propias, sino cuántas dependencias de terceros hay que sustituir porque ya no publican versiones compatibles. Cualquier cifra dada antes de mirar el árbol de dependencias es un número inventado.

¿Por qué compila bien y falla al arrancar?

Porque la compilación solo comprueba que las clases existan en el classpath de compilación. Las APIs que salieron del JDK en Java 11 suelen seguir presentes ahí, arrastradas por alguna dependencia, y no estar en ejecución. Compilar con maven.compiler.release en lugar de source y target evita buena parte de estos casos.

¿Vale la pena si la aplicación funciona?

El argumento no suele ser el rendimiento, sino que Java 8 ya no recibe actualizaciones públicas gratuitas y que las librerías que necesitáis han dejado de publicar versiones compatibles. Llega un punto en que cada dependencia nueva se convierte en una negociación, y ese es el coste real de quedarse.

¿Se puede hacer sin parar el desarrollo de nuevas funcionalidades?

Sí, si se hace por fases desplegables en lugar de en una rama larga. Compilar con el JDK nuevo manteniendo el entorno de ejecución antiguo permite avanzar sin bloquear las entregas. Una rama de migración abierta durante seis meses acaba siendo imposible de fusionar.

¿Tienes este reto?

¿En qué versión de Java está tu aplicación?

Cuéntame la versión actual, el servidor de aplicaciones y qué os está bloqueando. Con eso se puede decir si el salto es de semanas o de meses.

Pedir diagnóstico gratuito

Llamada de 30 minutos, sin coste. Respondo en menos de 48 horas.

En la llamada: versión de Java, framework, servidor y qué duele hoy. Si no puedo ayudarte, te lo digo. Qué incluye el diagnóstico