Decompiladores a Java
1. Qué es y por qué existe
Un APK no lleva dentro código fuente. Lleva classes*.dex: bytecode Dalvik, un juego de
instrucciones basado en registros que describe qué hace el programa pero no cómo se escribió.
Decompilar a Java es el intento de recorrer ese camino al revés — de las instrucciones a un
texto que un humano pueda leer como si fuera el original.
La palabra clave es intento. La compilación de Java o Kotlin a DEX es una función que
pierde información a propósito: descarta nombres, aplana construcciones del lenguaje,
reordena instrucciones y borra tipos. Un decompilador no invierte esa función porque no es
invertible; produce un programa distinto que se comporta igual. Cuando el decompilador
acierta, la diferencia es cosmética y el lector no la nota. Cuando falla, el resultado va
desde un método que no compila hasta —el caso peligroso— un método que compila, parece
razonable y no dice lo que la aplicación hace.
Este documento cubre las herramientas que hacen ese trabajo y, sobre todo, dónde fallan.
Para el nivel de abajo —el bytecode que se está decompilando— está
Bytecode Dalvik. Para el nivel intermedio, que es
fiel en lugar de reconstruido, está
Desensambladores smali.
2. Qué se pierde, y por qué no vuelve
Antes de comparar herramientas conviene fijar el techo. Ninguna de las de este documento puede recuperar lo que sigue, porque no está en el fichero.
2.1 Los genéricos
La JVM implementa los genéricos por borrado de tipos (type erasure): en tiempo de
ejecución List<String> y List<Integer> son la misma clase. Parte de la información
sobrevive en el atributo Signature, que D8 propaga al DEX como la anotación
dalvik.annotation.Signature, y los decompiladores buenos la leen. Pero es un atributo
opcional: si R8 lo descarta —lo hace cuando nadie lo consulta por reflexión y no se ha
pedido -keepattributes Signature—, la firma genérica desaparece sin dejar rastro y el
decompilador solo puede escribir el tipo crudo.
Consecuencia práctica: un Map<String, List<Usuario>> puede salir como Map. El programa
sigue siendo correcto; la intención del autor, no.
2.2 El azúcar sintáctico
Todo lo que el compilador de Java o de Kotlin traduce a construcciones más simples se pierde como forma, aunque se conserve como comportamiento:
| En el fuente | En el DEX |
Qué recupera el decompilador |
|---|---|---|
for (X x : lista) |
Bucle con Iterator explícito |
El for extendido, casi siempre |
switch sobre String |
Dos switch: uno sobre hashCode(), otro sobre el índice |
A veces; si no, dos switch anidados sin sentido aparente |
| Lambda / referencia a método | Clase sintética, o invoke-custom con invoke-polymorphic |
Depende del decompilador; es de lo más frágil |
try-with-resources |
try/finally anidados con addSuppressed |
Rara vez; suele salir el finally expandido |
| Autoboxing | Integer.valueOf(...) |
Se ve la llamada, no el azúcar |
Concatenación con + |
StringBuilder, o invokedynamic con StringConcatFactory |
Normalmente sí |
| Clases internas | Clases separadas con this$0 y accesores sintéticos |
Se reconstruyen, con cicatrices |
enum |
Clase con campos estáticos y array $VALUES |
Normalmente sí |
Nada de esto es un fallo del decompilador. Es que la información no viaja.
2.3 Los nombres de las variables locales
Los nombres de parámetros y de variables locales viven en debug_info_item, dentro del
code_item de cada método. Es una estructura opcional. Una compilación de release con
R8 la elimina casi siempre, y cuando no la elimina R8, la elimina el flag --no-debug-info
de quien haya reempaquetado la aplicación por el camino.
Sin debug_info, el decompilador tiene que inventar nombres. jadx los deriva del tipo y del
número de registro (str, i, list2, obj), lo cual es legible pero no es el nombre
original y no debe citarse como tal. Los nombres de campos y de métodos son otra cosa: esos
sí están en las tablas del DEX, salvo que R8 los haya ofuscado — y entonces el problema
ya no es la decompilación sino la ofuscación, que trata
R8 y ProGuard.
Puede comprobarse sobre el ejemplo mínimo de la sección 5 de
Desensambladores smali: compilado con javac -g, el
debug_info conserva los nombres; compilado sin -g, los registros aparecen sin nombre.
2.4 El orden real del código
R8 no se limita a renombrar. Hace inlining de métodos pequeños, outlining de secuencias
repetidas a un método sintético compartido, elimina ramas muertas, fusiona clases y mueve
código entre métodos. Lo que sale del decompilador es el programa después de esas
transformaciones.
Esto tiene un efecto que sorprende al que llega: un método que en el fuente tenía diez líneas puede aparecer con doscientas porque le han inyectado el cuerpo de cinco llamadas; y un método que en el fuente existía puede no aparecer en absoluto. Buscar «el método que hace X» en la salida de un decompilador y no encontrarlo no prueba que la aplicación no haga X.
2.5 El salto de modelo
El bytecode de la JVM está basado en pila; el de Dalvik, en registros. Los decompiladores de
Java —CFR, Vineflower, Procyon, Fernflower— están escritos para el primero. Para usarlos sobre
un APK hace falta una traducción previa de registros a pila (dex2jar), y esa traducción es
otra fuente de pérdida y de error, encadenada a la primera. jadx evita el rodeo porque lee
DEX de forma nativa; es su ventaja estructural.
3. jadx
3.1 Ficha
| Dato | Valor |
|---|---|
| Repositorio | https://github.com/skylot/jadx |
| Licencia | Apache-2.0 |
| Lenguaje | Java |
| Versión estable | 1.5.6, publicada el 10 de julio de 2026 |
| Último push al repositorio | 5 de agosto de 2026 |
| Creado | 18 de marzo de 2013 |
| Tracción | 50.030 ★ · 5.719 forks |
| Issues abiertas | 441 |
| Archivado | No |
Consultado el 12 de agosto de 2026 vía la API de GitHub. Activo y con cadencia sostenida: las últimas releases son 1.5.4 (13 de febrero de 2026), 1.5.5 (25 de febrero de 2026) y 1.5.6 (10 de julio de 2026). Trece años de historia, licencia permisiva y el mayor número de estrellas de todo el instrumental de este bloque. Es la referencia del sector y no está cerca de dejar de serlo.
Las 441 issues abiertas no son señal de abandono sino de superficie: un decompilador falla por definición en algún porcentaje de los métodos, y cada fallo es un caso concreto que alguien reporta.
3.2 La arquitectura, en cuatro etapas
jadx lee DEX directamente y no pasa por .class. El recorrido conceptual es:
┌──────────────┐ plugin de entrada
│ classes.dex │ dex-input, smali-input, java-convert, apk-input
└──────┬───────┘
▼
┌──────────────────────────────────────────────┐
│ 1. Carga: tablas del DEX a un modelo de │
│ clases, campos, métodos e instrucciones │
└──────┬───────────────────────────────────────┘
▼
┌──────────────────────────────────────────────┐
│ 2. IR: grafo de bloques básicos, forma SSA e │
│ inferencia de tipos sobre los registros │
└──────┬───────────────────────────────────────┘
▼
┌──────────────────────────────────────────────┐
│ 3. Reconstrucción de control de flujo: del │
│ grafo de saltos a regiones anidadas — │
│ if, bucles, switch, try/catch │
└──────┬───────────────────────────────────────┘
▼
┌──────────────────────────────────────────────┐
│ 4. Generación de código Java │
└──────────────────────────────────────────────┘
La etapa 3 es donde se juega todo. El DEX no tiene bucles ni condicionales: tiene saltos.
Recuperar una estructura anidada a partir de un grafo arbitrario no siempre es posible con
las construcciones del lenguaje Java, y ese es el origen directo de los modos de fallo de la
sección 3.5.
⚠️ sin verificar: los nombres internos de las pasadas de jadx y el detalle exacto de su representación intermedia no se han contrastado contra el código fuente del proyecto en esta revisión. Las cuatro etapas describen el recorrido que la documentación del proyecto y la estructura de sus módulos hacen evidente, no una lista de clases de su implementación.
3.3 jadx-gui
El proyecto distribuye dos binarios: jadx (línea de órdenes) y jadx-gui (interfaz
gráfica). La interfaz no es un envoltorio de la CLI: decompila bajo demanda, clase a clase, lo
que la hace utilizable sobre aplicaciones donde la decompilación completa tardaría minutos.
Aporta además búsqueda global por texto, clase, método o campo; navegación por referencias
(«quién llama a esto»); renombrado interactivo con persistencia; y la vista del smali del
método al lado del Java, que es la forma práctica de comprobar si el Java es fiel.
Esa vista lado a lado es la respuesta operativa a todo lo de la sección 2: cuando el Java no
cuadra, se mira el smali, que no miente.
3.4 Plugins
Desde la rama 1.5 jadx tiene un sistema de plugins con gestión desde la CLI. Los de entrada determinan qué formatos acepta:
| Plugin | Qué añade |
|---|---|
dex-input |
.dex y .apk, con verificación opcional de checksum |
java-convert |
.class, .jar y .aar, convirtiéndolos a DEX con dx o d8 |
smali-input |
Ficheros .smali, con nivel de API configurable |
kotlin-metadata |
Lee la anotación kotlin.Metadata para mejorar la salida de Kotlin |
kotlin-smap |
Usa SourceDebugExtension para renombrar alias de clases |
rename-mappings |
Aplica ficheros de mapping externos |
La gestión se hace con el subcomando plugins: -l/--list lista los instalados,
-a/--available los del marketplace, -i/--install <locationId> instala y --uninstall <pluginId> desinstala.
El kotlin-metadata importa más de lo que parece. Una parte grande del Android moderno es
Kotlin, y sin esa anotación el decompilador escribe el Java equivalente —con null checks
inyectados, clases Companion, funciones de extensión convertidas en estáticas— que es
correcto y bastante ilegible.
3.5 Los modos de fallo, y qué significa cada uno
Es la parte más útil de conocer, porque el lector se los va a encontrar.
Sobre los marcadores literales. Circulan por foros y por documentación de terceros dos cadenas que no existen en jadx:
// decompilation failedy// JADX WARNING: inconsistent code. Las reales son las de abajo, verificadas contra el árbol del proyecto. El catálogo de Diagnóstico de fallos explica en su sección 6 por qué la segunda no se puede encontrar grepeando el repositorio: se compone en ejecución.
/* JADX ERROR: <mensaje> */ — con dos espacios tras /*, que es como lo escribe
codegen/utils/CodeGenUtils.java. La etapa 3 no consiguió estructurar el método. Si hay una
causa asociada, la traza de pila Java completa va incrustada dentro del comentario. Según
la configuración, debajo aparece el smali embebido. El método no está decompilado en
absoluto: lo que se lee es el desensamblado. Es el fallo honesto, porque se ve.
/* JADX WARN: <mensaje> */ (y sus hermanos JADX INFO: y JADX DEBUG:). jadx sí
produjo Java, pero detectó que su propia reconstrucción no es coherente con el grafo de
partida. Mensajes habituales tras el prefijo: «Multi-variable type inference failed», «Code
restructure failed», «Removed duplicated region». Por defecto --comments-level es info,
así que WARN e INFO se emiten y DEBUG no.
Code decompiled incorrectly, please refer to instructions dump. — acompañado de
To view partially-correct add '--show-bad-code' argument. Este es el peligroso: hay
código Java plausible en pantalla que puede no corresponder al comportamiento real. Por
defecto jadx oculta estos métodos; --show-bad-code los muestra. El aviso está para leerse,
no para ignorarse.
Class decompilation failed es otra cosa: es el mensaje de una JadxRuntimeException del
modo --single-class, no un comentario del código generado. Confundirlas lleva a buscar en la
salida algo que solo aparece en la consola.
Métodos que salen como smali embebido. Consecuencia del primero. jadx cae al modo
fallback para ese método concreto. Se puede forzar globalmente con
-m fallback, que renuncia a decompilar y vuelca la representación de bajo nivel de toda la
aplicación — útil cuando la estructura importa menos que no perder nada.
Nombres p0, i, str2, C0123a. No es un fallo: es la sección 2.3 más la ofuscación.
Los identificadores que empiezan por C seguidos de dígitos son la convención de jadx para
clases cuyo nombre original era inválido o colisionaba.
OutOfMemoryError y cuelgues. Habitual en aplicaciones grandes. La documentación del
proyecto recomienda bajar el número de hilos con -j, excluir paquetes que no interesan, usar
la caché en disco de jadx-gui, y subir el heap de la JVM (-Xmx8g en vez de -Xmx4g).
Este último punto tiene una consecuencia de arquitectura para quien quiera embeber jadx en una herramienta propia: conviene lanzarlo siempre en subproceso aislado con límite de memoria y timeout, nunca por JNI, porque un fallo de memoria del decompilador se llevaría por delante al proceso que lo hospeda.
3.6 --deobf y mapping.txt: dos cosas distintas
Se confunden constantemente y no tienen nada que ver.
--deobf es heurístico. Detecta identificadores que parecen ofuscados —por longitud, con
los umbrales --deobf-min y --deobf-max— y les asigna nombres generados y estables dentro
de una misma ejecución. No recupera nada: cambia a.b.c por algo pronunciable. Sirve para
poder hablar del código y para que las referencias cruzadas se sigan; no para saber cómo se
llamaba.
--mappings-path es determinista. Carga un fichero de correspondencias y aplica los
nombres reales. El plugin rename-mappings acepta, entre otros formatos, el mapping.txt de
ProGuard y R8, además de Tiny, Enigma, SRG, XSRG, JAM, CSRG, TSRG, migración de IntelliJ,
Recaf y el JOBF propio de jadx. --mappings-mode controla si los cambios se autoguardan.
La regla de lectura: si hay mapping.txt, los nombres son los originales; si solo hay
--deobf, son invención. Confundirlos es cómo se escriben informes falsos.
4. La ruta alternativa: dex2jar más un decompilador de Java
4.1 El rodeo
classes.dex ──dex2jar──▶ classes.jar ──CFR / Vineflower / Procyon / Fernflower──▶ .java
Se traduce el bytecode de registros a bytecode de pila, y luego se usa cualquiera de los
decompiladores de Java de los últimos quince años. Dos pasos, dos oportunidades de fallar.
4.2 dex2jar
| Dato | Valor |
|---|---|
| Repositorio | https://github.com/pxb1988/dex2jar |
| Licencia | Apache-2.0 |
| Versión estable | 2.4, publicada el 3 de octubre de 2023 |
| Último push | 21 de julio de 2024 |
| Tracción | 13.132 ★ · 2.186 forks · 379 issues abiertas |
| Archivado | No |
Consultado el 12 de agosto de 2026. Estado real: mantenimiento mínimo. Dos años sin
release y más de dos años sin commits, con 379 issues abiertas. No está archivado y no ha
sido declarado muerto, pero tampoco se mueve. Existen forks activos que empaquetan
correcciones; su vigencia no se ha verificado en esta revisión.
⚠️ sin verificar: no se ha comprobado qué fork de dex2jar está vivo hoy ni con qué
cobertura.
El dato que importa para elegir ruta está medido: en el estudio de la sección 6, dex2jar
falló al convertir 394 de las 13.601 aplicaciones de Google Play y 33 de las 24.553
muestras de malware, frente a 0 de las 3.018 de F-Droid. Es decir, la ruta alternativa
empieza perdiendo aproximadamente un 2,9 % de las aplicaciones comerciales antes de que el
decompilador de Java llegue a intervenir.
4.3 Los decompiladores de Java
| Herramienta | Repositorio | Licencia | Última versión | Último push | Estado |
|---|---|---|---|---|---|
| CFR | leibnitz27/cfr |
MIT | 0.152 (11-12-2021) | 04-06-2026 | Repositorio vivo, sin release desde 2021 |
| Vineflower | Vineflower/vineflower |
Apache-2.0 | 1.12.0 (29-04-2026) | 11-07-2026 | Activo |
| Procyon | mstrobel/procyon |
Otra (no reconocida por GitHub) | 0.6.0 (21-02-2022) | 12-06-2022 | Parado desde 2022 |
| Fernflower | Dentro de JetBrains/intellij-community |
Apache-2.0 ⚠️ | — | — | Vive como componente de IntelliJ |
Consultado el 12 de agosto de 2026 vía la API de GitHub.
CFR es el más sólido de los clásicos y el que mejor sigue las versiones modernas del
lenguaje Java. Su anomalía de mantenimiento merece leerse con cuidado: el repositorio recibió
commits el 4 de junio de 2026, pero la última release etiquetada es la 0.152, de diciembre
de 2021, y la web oficial del autor sigue anunciando esa misma versión. Hay desarrollo; no
hay publicación. Quien lo use en producción está usando un binario de hace casi cinco años o
compilando de master.
Vineflower es el sucesor real de Fernflower: se declara en su propia descripción como «fork of the Fernflower decompiler», con énfasis en la calidad de salida. Es el único de los cuatro con release reciente (1.12.0, abril de 2026) y desarrollo continuo. Si hay que elegir un decompilador de Java hoy, es este.
Procyon está parado: última release en febrero de 2022, último commit en junio de 2022. Conserva valor histórico y sigue siendo interesante por su tratamiento de ciertas construcciones, pero no recibe correcciones.
Fernflower es el decompilador que JetBrains integra en IntelliJ IDEA y en Android Studio
— cuando se abre una clase compilada en el IDE, es él quien la muestra. No se distribuye como
proyecto independiente con releases propias; vive dentro del árbol de intellij-community.
⚠️ sin verificar: no se ha podido confirmar en esta revisión la licencia exacta ni el estado
del subdirectorio de Fernflower dentro del repositorio de JetBrains; la API de GitHub devolvió
403 a las consultas de contenido.
4.4 Cuándo esta ruta gana y cuándo pierde
Gana en un caso concreto y real: cuando jadx falla en un método determinado. El estudio de
la sección 6 lo cuantifica — en aproximadamente el 96 % de los casos en que jadx falla al
decompilar un método, al menos otro decompilador lo consigue. Si el objetivo es leer ese
método y jadx ha escrito JADX ERROR, probar CFR o Vineflower sobre el .jar de
dex2jar es exactamente lo que hay que hacer.
También gana cuando el objetivo no es un APK sino un .jar suelto, donde el rodeo
desaparece.
Pierde en todo lo demás:
dex2jarpuede fallar antes de empezar, y falla en un 2,9 % de las aplicaciones de Play.- Las tasas de fallo por método son entre 50 y 1.200 veces peores que las de jadx (sección 6).
- No entiende de recursos: no decodifica el
AndroidManifest.xmlnires/. - No sabe nada de Kotlin metadata ni de las convenciones de Android.
- Son dos herramientas encadenadas en vez de una.
La conclusión operativa es que no es una alternativa a jadx: es un segundo intento para los métodos que jadx no ha podido.
5. Las comerciales y las suites
5.1 JEB (PNF Software)
Plataforma comercial y de código cerrado de PNF Software, descrita por el fabricante como una plataforma de ingeniería inversa para desensamblado, decompilación, depuración y análisis. Lo que la distingue del resto de este documento:
- Un decompilador de Dalvik propio, independiente de jadx, con reputación de aguantar mejor el código muy ofuscado.
- Decompiladores nativos en el mismo entorno: x86/x86-64, ARM/ARM64, MIPS/MIPS64, RISC-V, además de WebAssembly, EVM de Ethereum, PLC Simatic S7 y kernels SASS de Nvidia. Cubre en un solo producto lo que aquí se reparte entre este documento y Análisis binario y estático.
- API de scripting en Java y Python para automatizar, con plugins de ejemplo publicados en GitHub aunque el núcleo sea propietario.
- Depurador que se acopla tanto al Dalvik como al nativo.
Ediciones: Community Edition y demo gratuitas; JEB Pro y JEB Android comerciales. La página del producto no publica precios y remite a la sección de compra. Versión observada en la página del fabricante: JEB 5.36, de enero de 2026. Consultado el 12 de agosto de 2026.
El ancla de precio que sí está documentada es la de la licencia OEM para servidor —8.000 $ por servidor y año—, entre los precios que el fabricante publica en su página de compra y que recoge Mapa del ecosistema.
El punto ciego: es de pago, es cerrado y no se puede auditar. Para trabajo reproducible o para una cadena de herramientas verificable, eso es un problema real.
5.2 Bytecode Viewer
| Dato | Valor |
|---|---|
| Repositorio | https://github.com/Konloch/bytecode-viewer |
| Licencia | GPL-3.0 |
| Última versión | 2.13.2 (1 de enero de 2026), etiquetada «2026 Release (In-Dev)» |
| Último push | 17 de julio de 2026 |
| Tracción | 15.590 ★ · 1.271 forks · 103 issues abiertas |
Consultado el 12 de agosto de 2026. Su propia descripción: «A Java 8+ Jar & Android APK Reverse Engineering Suite (Decompiler, Editor, Debugger & More)».
Lo que aporta es agregación: ejecuta varios decompiladores a la vez sobre la misma clase y
los muestra en paneles paralelos. Eso convierte el hallazgo del 96 % de la sección 4.4 en una
operación de un clic en lugar de un montaje manual de dos herramientas. Añade además editor,
buscador y visor de bytecode.
El punto ciego: es una interfaz gráfica. No hay modo desatendido utilizable en CI, que es exactamente la enfermedad del sector que cuantifica la sección 4.2 de Comparativa y selección. Y la licencia GPL-3.0 condiciona su integración en productos con núcleo permisivo.
6. La comparación honesta: qué dicen las mediciones
No he podido medir esto localmente: jadx no está instalado en la máquina de referencia y las reglas de este trabajo prohíben instalarlo. Lo que sigue son mediciones publicadas, con la fuente identificada.
La referencia es A Large-Scale Empirical Study of Android App Decompilation, de Noah Mauthe (Universidad del Sarre), Ulf Kargén y Nahid Shahmehri (Universidad de Linköping), presentado en SANER 2021. Es el primer estudio sistemático a gran escala de tasas de fallo de decompilación en Android. Tres conjuntos:
| Conjunto | Aplicaciones | Media de métodos por aplicación |
|---|---|---|
| F-Droid (código abierto) | 3.018 | 13.532 |
| Google Play (crawl reciente) | 13.601 | 63.748 |
| Malware | 24.553 | 5.142 |
6.1 Tasa media de fallo por método
Tabla VI del estudio — porcentaje medio de métodos fallidos por aplicación, incluyendo los
timeouts como fallo total y excluyendo las aplicaciones en que dex2jar falló:
| Conjunto | CFR | Fernflower | jadx | Procyon |
|---|---|---|---|---|
| F-Droid | 0,741 % | 5,966 % | 0,005 % | 1,522 % |
| Google Play | 1,032 % | 58,418 % | 0,019 % | 11,006 % |
| Malware | 1,838 % | 9,726 % | 0,044 % | 1,672 % |
| Media ponderada | 1,204 % | 24,703 % | 0,023 % | 4,733 % |
Excluyendo también los timeouts (tabla V), la media ponderada queda en CFR 1,094 %, Fernflower 1,024 %, jadx 0,021 % y Procyon 0,564 %. La diferencia entre las dos tablas para Fernflower —del 24,7 % al 1,0 %— dice que su problema principal no es decompilar mal sino agotar el tiempo: el estudio registra un número muy grande de timeouts para él, frente a solo 5 para jadx.
Lectura: jadx es entre 25 y 1.200 veces mejor que la ruta alternativa, según el conjunto. No es una mejora marginal.
6.2 El dato que de verdad importa
Tabla VII — porcentaje de aplicaciones en las que todos los métodos se decompilaron sin un solo fallo:
| Conjunto | Solo jadx | Ensemble de los cuatro | Los cuatro a la vez |
|---|---|---|---|
| F-Droid | 74,52 % | 74,69 % | 8,65 % |
| Google Play | 21,02 % | 21,03 % | 0,15 % |
| Malware | 78,81 % | 79,68 % | 0,60 % |
Aquí está la conclusión que hay que interiorizar: solo una de cada cinco aplicaciones de Google Play decompila entera sin un solo método fallido. Y combinar los cuatro decompiladores no arregla nada a nivel de aplicación — sube del 21,02 % al 21,03 %.
La explicación que da el propio estudio es de aritmética: las aplicaciones de Play tienen 63.748 métodos de media, así que con una tasa de fallo por método de 0,019 % la probabilidad de que ninguno falle es baja aunque el decompilador sea excelente. No es que jadx sea peor en Play; es que hay muchos más métodos donde equivocarse.
El corolario incómodo: si nadie te dice qué métodos fallaron, no sabes qué parte de lo que estás leyendo no es lo que la aplicación hace. Hoy esos fallos son silenciosos, y una cobertura de decompilación explícita —cuántos métodos se decompilaron y cuántos no— es justamente lo que ninguna de estas herramientas reporta de serie.
6.3 Y una conclusión que consuela
El estudio también responde a si la ofuscación está rompiendo la decompilación a propósito, y la respuesta es que no: los autores no encontraron ni un caso de ofuscación de control de flujo con ramas falsas hacia posiciones inválidas, la técnica «irrompible» habitual en código nativo o JVM de escritorio. Los fallos se deben mayoritariamente a limitaciones técnicas de los decompiladores —control de flujo muy complejo, anidamiento profundo, agotamiento de recursos en métodos grandes— y no a defensas deliberadas. La conclusión de los autores es que la decompilación casi perfecta de Android es alcanzable con mejoras de implementación.
6.4 Límites de estos datos
Hay que decirlo con claridad:
- Es un estudio de 2021. Cita jadx con una referencia de 2020, sin fijar número de versión; la versión actual es la 1.5.6, de julio de 2026. Las cinco ramas de mejora desde entonces no están reflejadas. Las cifras absolutas son casi con seguridad mejores hoy; el orden de magnitud entre herramientas es lo que conserva valor.
- Mide fallo declarado, no corrección. Un método que decompila «bien» puede producir Java que no refleja el comportamiento — los propios autores lo advierten.
- No incluye Vineflower, que no existía como tal, ni JEB, que es de pago.
- Las tablas III y IV del estudio documentan que parte de los fallos contabilizados son artefactos de la medición: coincidencias superfluas por clases internas y métodos irreconciliables por genéricos. Para jadx, el 34,0 % de los fallos en Google Play entran en esta segunda categoría.
⚠️ sin verificar: no existen mediciones públicas equivalentes y recientes —de 2025 o 2026— sobre las versiones actuales de estas herramientas. No se ha encontrado ninguna en esta revisión, y las cifras de arriba son lo mejor disponible.
7. Recetas de jadx CLI
⚠️ No ejecutado localmente: jadx no está instalado en la máquina de referencia. La sintaxis procede del README oficial del proyecto y de su wiki de resolución de problemas, consultados el 12 de agosto de 2026. No se ha comprobado ninguna salida.
Decompilación completa a un directorio:
jadx -d salida/ app.apk
Solo el código, sin decodificar recursos — mucho más rápido, y suficiente si solo se va a leer lógica:
jadx -d salida/ --no-res app.apk
Solo los recursos, sin decompilar:
jadx -d salida/ --no-src app.apk
Con deofuscación heurística y umbrales de longitud:
jadx -d salida/ --deobf --deobf-min 3 --deobf-max 64 app.apk
Con mapping.txt real, que es lo que de verdad recupera nombres:
jadx -d salida/ --mappings-path mapping.txt app.apk
Mostrando los métodos marcados como incoherentes, para poder auditarlos:
jadx -d salida/ --show-bad-code app.apk
Renunciando a estructurar y volcando la representación de bajo nivel:
jadx -d salida/ -m fallback app.apk
Una sola clase, útil cuando la aplicación es enorme:
jadx --single-class com.ejemplo.MiClase -d salida/ app.apk
Control de recursos, que es el problema práctico en aplicaciones grandes:
JAVA_OPTS="-Xmx8g" jadx -j 4 -d salida/ app.apk
Gestión de plugins:
jadx plugins --list
jadx plugins --available
jadx plugins --install <locationId>
Códigos de salida. ⚠️ sin verificar: no se ha podido confirmar la tabla de códigos de
salida de la CLI de jadx. La consulta al fichero JadxCLI.java en la API de GitHub devolvió
403 y no se ha ejecutado la herramienta. Lo único que se puede afirmar sin riesgo es que jadx
no devuelve error por métodos que fallan: la decompilación parcial es su comportamiento
normal, no una condición de error. Quien automatice jadx en un pipeline no debe apoyarse en el
código de salida para saber si el resultado es completo; tiene que contar los marcadores de
fallo en la salida generada.
8. Cómo elegir, en una tabla
| Situación | Qué usar |
|---|---|
Leer código de un APK, caso general |
jadx |
| Leer código, exploración interactiva | jadx-gui, con la vista de smali al lado |
jadx escribió JADX ERROR en el método que me importa |
dex2jar + Vineflower, o CFR; o Bytecode Viewer para probarlos a la vez |
Un .jar o .class suelto, sin APK |
Vineflower o CFR directamente |
| Código muy ofuscado y hay presupuesto | JEB |
| Necesito el comportamiento exacto, sin reconstrucción | No decompiles: smali |
| Automatizar en CI | jadx en subproceso, contando marcadores de fallo |
Y la regla que resume el documento: cuando el Java y el smali discrepan, el smali tiene
razón.
Fuentes
- jadx — repositorio oficial — https://github.com/skylot/jadx
Consultado el 12 de agosto de 2026 vía
https://api.github.com/repos/skylot/jadx. De aquí salen la licencia Apache-2.0, la fecha de creación, el recuento de estrellas, forks e issues, y la fecha del último push de la ficha de la sección 3.1. - jadx — releases — https://api.github.com/repos/skylot/jadx/releases Consultado el 12 de agosto de 2026. De aquí salen la versión 1.5.6 y su fecha de publicación, y el historial de releases citado en la sección 3.1.
- jadx — README oficial — https://github.com/skylot/jadx#readme
Consultado el 12 de agosto de 2026. De aquí salen la lista de opciones de la CLI de la
sección 7, la lista de plugins de entrada de la sección 3.4, los subcomandos de
plugins, y la advertencia literal de que «in most cases jadx can't decompile all 100% of the code». - jadx — Troubleshooting Q&A (wiki) — https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A
Consultado el 12 de agosto de 2026. De aquí salen el significado de
--show-bad-codey las recomendaciones de memoria e hilos de la sección 3.5. Los marcadores literales de la sección 3.5 no salen de aquí: están verificados contra el árbol del proyecto y contrastados en la sección 6 de50-procesos/04-diagnostico-de-fallos.md. - A Large-Scale Empirical Study of Android App Decompilation — Noah Mauthe, Ulf Kargén,
Nahid Shahmehri. SANER 2021. https://www.ida.liu.se/~ulfka17/papers/SANER2021.pdf
Consultado el 12 de agosto de 2026; PDF descargado y extraído con
pdftotextpara leer las tablas directamente. De aquí sale toda la sección 6: tamaños de los conjuntos, tablas V, VI y VII, medias de métodos por aplicación, el 96 % de complementariedad, los fallos dedex2jar, y las conclusiones sobre ofuscación. - dex2jar — repositorio oficial — https://github.com/pxb1988/dex2jar Consultado el 12 de agosto de 2026 vía la API de GitHub. De aquí salen la licencia, la versión 2.4, la fecha del último push y los recuentos de la sección 4.2.
- CFR — repositorio oficial — https://github.com/leibnitz27/cfr Consultado el 12 de agosto de 2026 vía la API de GitHub y su lista de tags. De aquí salen la licencia MIT, la release 0.152 y el contraste entre el push de junio de 2026 y la ausencia de releases desde 2021.
- CFR — página oficial del autor — https://www.benf.org/other/cfr/ Consultado el 12 de agosto de 2026. Confirma que la versión anunciada sigue siendo la 0.152 de diciembre de 2021, y de aquí sale su sintaxis de invocación.
- Vineflower — repositorio oficial — https://github.com/Vineflower/vineflower Consultado el 12 de agosto de 2026 vía la API de GitHub. De aquí salen la licencia Apache-2.0, la versión 1.12.0 del 29 de abril de 2026, y su declaración de ser un fork de Fernflower.
- Procyon — repositorio oficial — https://github.com/mstrobel/procyon Consultado el 12 de agosto de 2026 vía la API de GitHub y su lista de releases. De aquí salen la versión 0.6.0 de febrero de 2022 y la fecha del último push, que sostienen la afirmación de que está parado.
- Bytecode Viewer — repositorio oficial — https://github.com/Konloch/bytecode-viewer Consultado el 12 de agosto de 2026 vía la API de GitHub. De aquí salen la licencia GPL-3.0, la versión 2.13.2 y los recuentos de la sección 5.2.
- JEB Decompiler — página oficial de PNF Software — https://www.pnfsoftware.com/jeb/ Consultado el 12 de agosto de 2026. De aquí salen las ediciones, la lista de arquitecturas decompiladas y la versión 5.36 de la sección 5.1.
- Documentos de este corpus — Mapa del ecosistema (§2.3). De aquí sale el precio de la licencia OEM de JEB citado en la sección 5.1, verificado allí contra la página de compra de PNF Software.