Guía

Cómo hacer el inventario de dependencias antes de migrar

El inventario es lo que convierte una migración de una apuesta en un plan. Estos son los comandos que lo producen y qué mirar en cada salida.

Qué responde un inventario y qué no

Un inventario responde tres preguntas: qué hay realmente en el classpath, qué de eso ha dejado de mantenerse, y qué va a romperse al cambiar de versión. No responde cuánto va a costar: eso sale después, cuando se cruza con lo que la aplicación necesita hacer.

La diferencia con leer el fichero de construcción es que ahí solo están las dependencias declaradas. Las que causan problemas suelen ser transitivas, es decir, dependencias de dependencias que nadie eligió y que aparecen en el classpath sin figurar en ninguna parte del proyecto.

Todo lo que sigue son comandos que se ejecutan sobre el proyecto tal y como está, sin modificar nada.

Los comandos y qué mirar en cada salida

  1. El árbol completo de dependencias

    mvn dependency:tree imprime el árbol resuelto, que es lo que de verdad acaba en el classpath. Interesa buscar la misma dependencia apareciendo dos veces con versiones distintas: eso es el origen de la mayoría de los NoSuchMethodError. Añadiendo -Dverbose se ven también las que fueron descartadas y por qué.

  2. Quién trae una dependencia concreta

    Cuando ya se sabe qué librería molesta, mvn dependency:tree -Dincludes=grupo:artefacto recorta el árbol a las ramas que llevan hasta ella. Es la forma rápida de saber a quién hay que excluir o actualizar.

  3. Qué versiones hay disponibles

    mvn versions:display-dependency-updates lista, para cada dependencia, la versión actual y la última publicada. Lo importante no es el número, sino la fecha: una librería cuya última versión es de hace cinco años es una decisión pendiente, no una dependencia.

  4. Qué plugins se han quedado atrás

    mvn versions:display-plugin-updates hace lo mismo con los plugins de construcción. Suelen ser lo último que alguien actualiza y lo primero que falla al cambiar de JDK, porque muchos dependen de detalles internos del compilador.

  5. Qué código usa APIs internas del JDK

    jdeps --jdk-internals --multi-release 17 target/la-aplicacion.jar analiza el bytecode y señala qué clases dependen de APIs internas. Es el comando que anticipa los InaccessibleObjectException antes de que aparezcan, y funciona igual sobre las dependencias que sobre el código propio.

  6. Qué hay declarado y no se usa

    mvn dependency:analyze distingue entre dependencias declaradas sin usar y usadas sin declarar. Las segundas son las peligrosas: funcionan por casualidad, porque otra dependencia las arrastra, y desaparecen el día que esa otra cambia de versión.

Qué significa cada hallazgo

La salida de los comandos anteriores se traduce en decisiones. Esta es la correspondencia.

Hallazgo del inventario, riesgo asociado y decisión que implica
Lo que apareceQué significaQué se decide
La misma librería en dos versionesEl classpath resuelve una de las dos de forma impredecibleFijar la versión en la gestión de dependencias del proyecto
Última publicación hace añosNo habrá versión compatible con el JDK de destinoSustituir, o aislar su uso tras una interfaz propia
Uso de APIs internas del JDKFallará en ejecución a partir de Java 16Actualizar la librería, y solo si no hay otra, abrir el módulo
Dependencia usada sin declararFunciona por arrastre y desaparecerá sin avisoDeclararla de forma explícita antes de tocar nada más
Plugin de construcción desactualizadoEl build fallará antes que la aplicaciónActualizarlo primero, separado del resto del trabajo

Con esta tabla rellenada ya se puede ordenar el trabajo por riesgo, que es lo que convierte el inventario en el plan por fases del servicio de modernización.

Lo que ningún comando va a decir

El inventario automático cubre el classpath. El resto hay que mirarlo a mano, y suele pesar más en el calendario.

  • Qué versión de Java certifica el servidor de aplicaciones que hay en producción.
  • Qué configuración vive fuera del repositorio y solo existe en el servidor.
  • Qué sistemas consumen la aplicación y con qué contrato lo hacen.
  • Qué partes del código no tienen pruebas que comprueben comportamiento.
  • Qué reglas de negocio no están documentadas en ningún sitio salvo en el propio código.

Un inventario que solo cubre las dependencias da una falsa sensación de control: es la mitad más fácil del problema.

Preguntas frecuentes

Preguntas sobre el inventario

¿Sirve esto igual con Gradle?

El planteamiento es idéntico y los comandos cambian: el árbol se obtiene con la tarea de dependencias del propio Gradle, y jdeps funciona igual porque analiza bytecode, no ficheros de construcción.

¿Cuánto se tarda en hacer un inventario?

Los comandos son minutos. Interpretar la salida y cruzarla con lo que la aplicación necesita es el trabajo real, y depende del tamaño del árbol de dependencias más que del código propio.

¿Hace falta el inventario si la migración parece sencilla?

Es justo cuando parece sencilla cuando más conviene: una migración que se estima en dos semanas sin mirar el árbol de dependencias es una estimación sin base. El inventario cuesta poco y es lo que evita la sorpresa a mitad.

¿Se puede hacer sin acceso al código?

Parcialmente. Con el artefacto empaquetado se puede analizar el bytecode y ver las APIs internas, pero no se obtiene el árbol de dependencias resuelto ni se puede distinguir lo declarado de lo arrastrado.

JavaEvolve

¿Te has encontrado con esto en tu aplicación?

Si el caso concreto no encaja con lo que hay aquí, cuéntamelo y te digo por dónde lo abordaría.

Escríbeme