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.
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.
Las versiones intermedias suben de una en una: Java 12 es 56.0, Java 13 es 57.0 y así sucesivamente.
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.