Como se mencionó previamente en Revisando Java 8, Java 8 llegó con muchas nuevas mejoras como Lambdas, Streams y una nueva Date API. Pero, también fueron añadidos nuevos comandos como es el caso de JDeps, del cual hablaremos en esta oportunidad.

Durante diferentes desarrollos en algunas ocasiones nos ha tocado hacer uso de algunas de las clases ubicadas en los paquetes sun.*. Las clases ubicadas en estos paquetes son de uso interno del JDK, es decir, Oracle se encarga de su implementación y hacer uso de ellas no garantiza el funcionamiento en plataformas compatibles ni en futuras versiones de la misma plataforma. Algunas clases ubicadas en estos paquetes son por ejemplo sun.misc.BASE64Decoder, sun.misc.BASE64Encoder las cuales a partir de Java 8 se pueden reemplazar por java.util.Base64.

JDeps es un comando que nos permite analizar las dependencias de librerías (jar) y clases. En este caso, cuando hablamos de dependencias, nos estamos refiriendo al uso de las clases internas del JDK, es decir, aquellas clases que se encuentran ubicadas en los paquetes sun.*.

¿Y por qué no debemos usar esas clases? Estas clases son de uso interno del JDK y son las clases que Oracle da soporte. Hacer uso de estas clases en una implementación de java de otro proveedor podría traer consecuencias en la aplicación, incluso podríamos obtener un ClassNotFoundError.

¿Por que debemos usar jdeps? La respuesta es muy sencilla, porque nos permite detectar el uso de clases internas del JDK que en algún momento pueden ser eliminadas o modificadas. La recomendación es usar la implementación pública.

La manera de ejecutar este comando es sencilla, solo debemos asegurar de tener la versión 8 de java. Algunas opciones que usaremos serán -jdkinternals, -profile.

El formato para hacer uso de este comando es el siguiente:

jdeps [opciones] [clases]

Para saber más sobre las opciones que ofrece, revisar la documentación de jdeps.

A continuación, crearemos una clase llamada BASE64Ejemplo.java y usaremos el siguiente código:

import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import java.io.IOException;

public class BASE64Ejemplo {

    public static void main(String[] args) throws IOException {
        BASE64Encoder encoder = new BASE64Encoder();
        String encode = encoder.encode("eddu".getBytes());
        System.out.println(encode);

        BASE64Decoder decoder = new BASE64Decoder();
        byte[] bytes = decoder.decodeBuffer(encode);
        System.out.println(new String(bytes));
    }

}

Procederemos a compilar la clase y veremos las advertencias que se muestran en nuestro terminal, haciéndonos saber que en esta clase estamos usando clases internas del API.

javac BASE64Ejemplo.java
BASE64Ejemplo.java:1: warning: BASE64Decoder is internal proprietary API and may be removed in a future release
import sun.misc.BASE64Decoder;
               ^
BASE64Ejemplo.java:2: warning: BASE64Encoder is internal proprietary API and may be removed in a future release
import sun.misc.BASE64Encoder;
               ^
BASE64Ejemplo.java:9: warning: BASE64Encoder is internal proprietary API and may be removed in a future release
        BASE64Encoder encoder = new BASE64Encoder();
        ^
BASE64Ejemplo.java:9: warning: BASE64Encoder is internal proprietary API and may be removed in a future release
        BASE64Encoder encoder = new BASE64Encoder();
                                    ^
BASE64Ejemplo.java:13: warning: BASE64Decoder is internal proprietary API and may be removed in a future release
        BASE64Decoder decoder = new BASE64Decoder();
        ^
BASE64Ejemplo.java:13: warning: BASE64Decoder is internal proprietary API and may be removed in a future release
        BASE64Decoder decoder = new BASE64Decoder();
                                    ^
6 warnings

Mientras compilamos el código nos podemos dar cuenta de los avisos que nos da el compilador de java. Ahora haremos un análisis de la clase compilada BASE64Ejemplo.class haciendo uso de jdeps y mostrará que se hace uso de JDK internal API.

jdeps BASE64Ejemplo.class
BASE64Ejemplo.class -> /Library/Java/JavaVirtualMachines/jdk1.8.0_31.jdk/Contents/Home/jre/lib/rt.jar
   <unnamed> (BASE64Ejemplo.class)
      -> java.io
      -> java.lang
      -> sun.misc                                           JDK internal API (rt.jar)

Ahora, haciendo uso de jdeps y la opción -profile.

jdeps -profile BASE64Ejemplo.class
BASE64Ejemplo.class -> /Library/Java/JavaVirtualMachines/jdk1.8.0_31.jdk/Contents/Home/jre/lib/rt.jar (compact1)
   <unnamed> (BASE64Ejemplo.class)
      -> java.io                                            compact1
      -> java.lang                                          compact1
      -> sun.misc                                           JDK internal API (rt.jar)

Finalmente, haciendo uso de jdeps y la opción -jdkinternals.

jdeps -jdkinternals BASE64Ejemplo.class
BASE64Ejemplo.class -> /Library/Java/JavaVirtualMachines/jdk1.8.0_31.jdk/Contents/Home/jre/lib/rt.jar
  BASE64Ejemplo (BASE64Ejemplo.class)
      -> sun.misc.BASE64Decoder                             JDK internal API (rt.jar)
      -> sun.misc.BASE64Encoder                             JDK internal API (rt.jar)

Ahora, las clases sun.misc.BASE64Decoder y sun.misc.BASE64Encoder serán reemplazadas por la clase java.util.Base64 que viene a partir de Java 8, se puede apreciar las diferencias al compilar y analizar la clase.

import java.util.Base64;

public class Base64Ejemplo {

    public static void main(String[] args) {
        byte[] encode = Base64.getEncoder().encode("eddu".getBytes());
        System.out.println(new String(encode));

        byte[] bytes = Base64.getDecoder().decode(encode);
        System.out.println(new String(bytes));
    }

}

Como resultado de compilar la clase haciendo uso de java.util.Base64 se puede apreciar que el compilar no arroja ninguna advertencia.

jdeps -jdkinternals Base64Ejemplo.class
Base64Ejemplo.class -> /Library/Java/JavaVirtualMachines/jdk1.8.0_31.jdk/Contents/Home/jre/lib/rt.jar

Por último, haciendo un análisis de una de las librerías más populares en el ecosistema java: spring-core-4.2.4.RELEASE.jar.

jdeps -P -jdkinternals spring-core-4.2.4.RELEASE.jar
spring-core-4.2.4.RELEASE.jar -> /Library/Java/JavaVirtualMachines/jdk1.8.0_31.jdk/Contents/Home/jre/lib/rt.jar (Full JRE)
spring-core-4.2.4.RELEASE.jar -> not found
   org.springframework.objenesis.instantiator.sun.UnsafeFactoryInstantiator (spring-core-4.2.4.RELEASE.jar)
      -> sun.misc.Unsafe                                    JDK internal API (rt.jar)

Se puede ver en el análisis, sun.misc.Unsafe es la clase interna usada dentro de spring-core-4.2.4.RELEASE.jar y según las estadísticas de Oracle esta es la segunda clase interna más utilizada.

NOTA: El JDK 9 EA (Early Access, por sus siglas en inglés) provee una versión de jdeps con mejoras y varios bugs resueltos.


Eddú Meléndez

Java Software Engineer, Open Source Contributor