jadx convierte una app de Android en código Java que se puede leer. Le das el archivo .apk y te devuelve sus clases, su manifiesto con los permisos que pide y todos sus recursos, en una ventana para navegarlos o en una carpeta. Es gratis, de código abierto y funciona en Windows, macOS y Linux. También es la pieza que usan por debajo los servidores MCP con los que un agente de IA analiza apps de Android, como jadx-headless-mcp, que pide REA. Esta guía va de la instalación a tu primer análisis, con cada orden ejecutada de verdad.
- Qué es jadx y para qué sirve
- Instalar Java y jadx
- Dos programas: jadx-gui y jadx
- Tu primera APK desde la terminal
- Lo mismo en jadx-gui
- Entender lo que sale: ofuscación, smali y métodos que fallan
- Búsquedas útiles
- Exportar como proyecto de Gradle
- jadx con un agente de IA
- Qué puedes analizar y qué no
- Errores comunes
- Resumen de órdenes
Qué es jadx y para qué sirve
Una app de Android se distribuye como un archivo .apk, que en realidad es un ZIP. Dentro va el código ya compilado a bytecode de Android (los archivos classes.dex), el manifiesto en un formato binario y los recursos: textos, imágenes y diseños de pantalla. Nada de eso se puede leer tal cual. jadx hace dos trabajos: descompila el bytecode y reconstruye un código Java muy parecido al original, y decodifica el manifiesto y los recursos para que vuelvan a ser XML legible.
Se usa para revisar qué hace una app antes de instalarla, buscar fallos de seguridad, entender cómo habla con su servidor, auditar las bibliotecas que trae, recuperar el código de una app propia cuyo proyecto se perdió y aprender cómo están hechas otras apps. Para el código nativo que algunas apps llevan en archivos .so (C o C++), jadx no sirve: ahí se usa Ghidra. Qué herramienta va con cada lenguaje lo comparamos en qué es descompilar.
Instalar Java y jadx
jadx está escrito en Java y no lo incluye, salvo en una de sus versiones para Windows. Necesitas Java 11 o posterior, de 64 bits. Cualquier versión LTS reciente sirve; nosotros usamos la 21. En Linux:
sudo apt install openjdk-21-jre
En Windows y macOS, el instalador de Eclipse Temurin 21. Para comprobarlo:
java -version
salidaopenjdk version "21.0.13" 2026-10-20 LTS OpenJDK Runtime Environment Temurin-21.0.13+9 (build 21.0.13+9-LTS) OpenJDK 64-Bit Server VM Temurin-21.0.13+9 (build 21.0.13+9-LTS, mixed mode, sharing)
Hay tres formas de instalar jadx:
- El ZIP de GitHub, para cualquier sistema. Se baja
jadx-1.5.6.zipde la página de la versión, se descomprime y los programas quedan en la carpetabin. En Windows hay ademásjadx-gui-1.5.6-with-jre-win.zip, que trae su propio Java y no necesita instalar nada más. - Homebrew en macOS:
brew install jadx. - El gestor de tu distribución en Linux:
sudo pacman -S jadxen Arch, oflatpak install flathub com.github.skylot.jadxen cualquiera con Flatpak. Revisa qué versión te instala: los paquetes de las distribuciones a veces van atrasados.
Con el ZIP, comprueba antes de abrirlo que es el original. La página de la versión publica la huella SHA-256 de cada archivo:
curl -LO https://github.com/skylot/jadx/releases/download/v1.5.6/jadx-1.5.6.zip sha256sum jadx-1.5.6.zip
salida545ea2be9c242511bc145755cf4bda2485ade42966e096f8b4d3da2a230e8974 jadx-1.5.6.zip
Coincide con la publicada. En Windows se calcula con Get-FileHash .\jadx-1.5.6.zip en PowerShell y en macOS con shasum -a 256. Después:
unzip jadx-1.5.6.zip -d jadx cd jadx bin/jadx --version
salida1.5.6
Para no escribir la ruta cada vez, agrega la carpeta bin a tu PATH. En el resto de la guía escribimos solo jadx.
Dos programas: jadx-gui y jadx
El ZIP trae dos programas que hacen lo mismo de distinta manera:
jadx-guiabre una ventana para navegar la app: el árbol de paquetes y clases a la izquierda, el código a la derecha, búsquedas y la opción de saltar de un uso a la definición con un clic. Es lo mejor para explorar y entender. En Windows se abre con doble clic enbin\jadx-gui.bat.jadx, el de terminal, escribe todo en una carpeta. Es lo que se usa en scripts, en un servidor o cuando quieres buscar congrepen todo el código a la vez.
Los dos usan el mismo motor y dan el mismo código. Empezamos por la terminal porque así se ve mejor qué produce jadx.
Tu primera APK desde la terminal
Bajamos Binary Eye de F-Droid. Una APK es un ZIP, y se puede ver qué trae sin jadx:
jar tf BinaryEye.apk | wc -l
salida926
Entre esos 926 archivos están el classes.dex con todo el código, el AndroidManifest.xml en binario y el resources.arsc con la tabla de recursos. Ahora, descompilarla:
jadx -d salida BinaryEye.apk
salida, abreviadaINFO - loading ... INFO - processing ... INFO - progress: … ERROR - finished with errors, count: 6
Tardó 6.5 segundos. El ERROR del final asusta, pero no significa que haya fallado: jadx terminó y avisa que 6 cosas no le salieron bien. Lo vemos más abajo. El resultado son dos carpetas:
contenido de la carpetasalida/ ├── sources/ 2,895 archivos .java └── resources/ 1,096 archivos: AndroidManifest.xml, res/, assets/…
En sources hay una carpeta por paquete: de/markusfisch/android/binaryeye es la app, y el resto son bibliotecas que la app trae dentro (androidx, kotlin, com/google…). En las apps reales, el código propio suele ser una parte pequeña de todo lo descompilado.
Empezar por el manifiesto
resources/AndroidManifest.xml es el resumen de la app y casi siempre el primer archivo que conviene leer. Dice cómo se llama el paquete, su versión, para qué versiones de Android está hecha y qué permisos pide:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="182"
android:versionName="1.75.7"
android:installLocation="auto"
android:compileSdkVersion="37"
android:compileSdkVersionCodename="17"
package="de.markusfisch.android.binaryeye"
platformBuildVersionCode="37"
platformBuildVersionName="17">
<uses-sdk
android:minSdkVersion="21"
android:targetSdkVersion="37"/>
…Los permisos se sacan rápido con grep:
grep -o 'android.permission[A-Z_.]*' salida/resources/AndroidManifest.xml | sort -u
salidaandroid.permission.ACCESS_FINE_LOCATION android.permission.ACCESS_LOCAL_NETWORK android.permission.ACCESS_WIFI_STATE android.permission.BIND_QUICK_SETTINGS_TILE android.permission.BLUETOOTH android.permission.BLUETOOTH_ADMIN android.permission.BLUETOOTH_CONNECT android.permission.CAMERA android.permission.CHANGE_WIFI_STATE android.permission.DUMP android.permission.INTERNET android.permission.VIBRATE android.permission.WRITE_EXTERNAL_STORAGE
Cámara, internet, Bluetooth y wifi tienen sentido en un lector de QR que puede conectarse a una red o mandar lo que lee a otro equipo. Así se hace la primera revisión de cualquier app: comparar lo que pide con lo que dice hacer. El manifiesto también dice qué pantalla se abre al tocar el icono, la que tiene la categoría LAUNCHER:
<activity
android:theme="@style/AppTheme"
android:name="de.markusfisch.android.binaryeye.activity.SplashActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<meta-dataLeer una clase
Es SplashActivity. Cada clase está en un archivo con su nombre, dentro de la carpeta de su paquete, pero si solo te interesa una, jadx puede descompilarla sola, sin procesar el resto:
jadx --single-class de.markusfisch.android.binaryeye.activity.SplashActivity \
--single-class-output SplashActivity.java BinaryEye.apkpackage de.markusfisch.android.binaryeye.activity;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
/* JADX INFO: compiled from: r8-map-id-72af13506ba8dbbc22a778cc11d57d78175fef65d01990c2f7681fa30929cf6e */
/* JADX INFO: loaded from: classes.dex */
public final class SplashActivity extends Activity {
@Override // android.app.Activity
public final void onCreate(Bundle bundle) {
super.onCreate(bundle);
startActivity(new Intent(getApplicationContext(), (Class<?>) CameraActivity.class));
finish();
}
}Se lee como Java normal: al crearse, la pantalla de arranque abre CameraActivity y se cierra. Los comentarios JADX INFO los añade jadx, y el r8-map-id indica que la app pasó por R8, la herramienta de Android que reduce y ofusca el código al compilar. Por eso, como veremos, muchas clases ya no tienen su nombre original. Ojo: la clase se tiene que escribir con su nombre completo y exacto. Con un nombre que no existe, jadx responde ERROR - Incorrect arguments: Input class not found.
Lo mismo en jadx-gui
Para explorar, la ventana es más cómoda:
jadx-gui BinaryEye.apk
A la izquierda aparece el árbol con Source code (los paquetes), Resources (el manifiesto y los recursos), la firma de la APK y un resumen. Tres atajos resuelven casi todo:
- Ctrl+N busca una clase por su nombre.
- Ctrl+Shift+F busca un texto dentro de todo el código.
- Con clic derecho sobre un nombre, Go to declaration salta a su definición, y la flecha de la barra regresa.
La interfaz está en inglés de fábrica, pero trae traducción al español: File → Preferences → Language → Español. Aquí usamos los nombres en inglés porque son los que verás en casi todos los tutoriales.

La búsqueda de texto es la forma más rápida de encontrar dónde se usa algo. Aquí, la dirección web markusfisch.de/encode:

Otras cosas útiles de la ventana: con clic derecho, Find Usage muestra quién usa una clase o un método, y Rename le pone un nombre propio que se guarda en el proyecto de jadx; y File → Export project, con la casilla Export as a Gradle project, hace lo mismo que la opción -e de la terminal.
Entender lo que sale: ofuscación, smali y métodos que fallan
El código que da jadx no es el original, sino una reconstrucción. Casi siempre se lee bien, pero hay dos cosas que no puede recuperar, y conviene reconocerlas.
Nombres ofuscados
Al compilar, R8 cambia los nombres de clases y métodos por letras para que la app pese menos y cueste más leerla. Su trabajo se nota en la carpeta defpackage, donde jadx pone las clases sin paquete: en Binary Eye hay 2,635, con nombres como a.java, a0.java o go.java. Esta es go:
public abstract class go extends hi0 {
public static List u0(Object... objArr) {
if (objArr.length <= 0) {
return c50.X;
}
List listAsList = Arrays.asList(objArr);
listAsList.getClass();
return listAsList;
}No hay forma de recuperar el nombre original, pero la opción --deobf pone a cada uno un nombre único y más largo, más fácil de buscar sin que salgan coincidencias falsas, y mueve defpackage a una carpeta p000:
jadx --deobf -d salida-deobf BinaryEye.apk
la misma línea de EncodeActivity, con --deobfList listM1836u0 = AbstractC0263go.m1836u0("binaryeye://encode", "http://markusfisch.de/encode", "https://markusfisch.de/encode");go.u0 pasó a llamarse AbstractC0263go.m1836u0, y el comentario renamed from: go recuerda el nombre anterior. A partir de ahí, el trabajo es ir poniendo nombres propios a medida que entiendes qué hace cada cosa, con Rename en la ventana.
Métodos que no se reconstruyen
A veces jadx no logra convertir un método en Java correcto, por ejemplo con bucles muy enredados por la optimización. En vez de inventar, lo marca:
@Override // androidx.recyclerview.widget.LinearLayoutManager, defpackage.z31
/*
Code decompiled incorrectly, please refer to instructions dump.
To view partially-correct add '--show-bad-code' argument
*/
public final android.view.View S(android.view.View r23, int r24, defpackage.f41 r25, defpackage.j41 r26) {
/*
Method dump skipped, instruction units count: 309
To view this dump add '--comments-level debug' option
*/
throw new UnsupportedOperationException("Method not decompiled: androidx.recyclerview.widget.GridLayoutManager.S(android.view.View, int, f41, j41):android.view.View");
}En Binary Eye pasó en 21 de los 2,895 archivos, y ninguno era del paquete de la app: 17 estaban en defpackage y el resto en bibliotecas de Google. Hay tres formas de ver qué hace un método así:
- La pestaña Smali de jadx-gui muestra las instrucciones exactas de la máquina virtual de Android, sin interpretar. Es menos cómodo que Java, pero nunca se equivoca.
--show-bad-codeescribe el Java que jadx consiguió armar aunque sea inconsistente. Sirve para darse una idea, no para fiarse.-m fallbackda una versión intermedia: Java instrucción por instrucción, sin reconstruir bucles ni condiciones.

Así se ve SplashActivity con -m fallback, comparada con el Java de antes:
package de.markusfisch.android.binaryeye.activity;
/* JADX INFO: compiled from: r8-map-id-72af13506ba8dbbc22a778cc11d57d78175fef65d01990c2f7681fa30929cf6e */
/* JADX INFO: loaded from: classes.dex */
public final class SplashActivity extends android.app.Activity {
public SplashActivity() {
r0 = this;
r0.<init>()
return
}
@Override // android.app.Activity
public final void onCreate(android.os.Bundle r3) {
r2 = this;
super.onCreate(r3)
android.content.Intent r3 = new android.content.Intent
android.content.Context r0 = r2.getApplicationContext()
java.lang.Class<de.markusfisch.android.binaryeye.activity.CameraActivity> r1 = de.markusfisch.android.binaryeye.activity.CameraActivity.class
r3.<init>(r0, r1)
r2.startActivity(r3)
r2.finish()
return
}
}Búsquedas útiles
Como sources y resources son carpetas de texto, todas las herramientas de siempre funcionan. Algunas búsquedas que sirven en casi cualquier app:
Direcciones web a las que la app podría conectarse:
grep -rhoE 'https?://[A-Za-z0-9./?=_%:-]+' salida/sources/de/markusfisch | sort | uniq -c | sort -rn
salida 2 https://www.google.com/search?q=
1 https://markusfisch.de/encode
1 http://www.w3.org/2000/svg
1 http://markusfisch.de/encodeY dónde aparece cada una:
grep -rn 'https://' salida/sources/de/markusfisch
salidasalida/sources/de/markusfisch/android/binaryeye/activity/EncodeActivity.java:264: List listU0 = go.u0("binaryeye://encode", "http://markusfisch.de/encode", "https://markusfisch.de/encode");
salida/sources/de/markusfisch/android/binaryeye/app/BinaryEyeApp.java:71: editorEdit3.putString("default_search_url", "https://www.google.com/search?q=");
salida/sources/de/markusfisch/android/binaryeye/app/BinaryEyeApp.java:72: editorEdit3.putString("open_with_url", "https://www.google.com/search?q=");La búsqueda de Google es la que usa la app por defecto para buscar el texto de un código escaneado, y markusfisch.de/encode es el enlace para generar códigos. Ninguna sorpresa: es lo que esperas de un lector de QR.
Textos de la interfaz, en res/values/strings.xml y sus traducciones (values-es para español), que ayudan a ubicar una pantalla:
grep -n 'name="scan_code"' salida/resources/res/values-es/strings.xml
salida347: <string name="scan_code">Escanear código</string>
Llaves y contraseñas escritas en el código, uno de los errores de seguridad más comunes en apps:
grep -rniE '"(api[_-]?key|secret|token|password)"' salida/sources/de grep -niE 'api_key|secret|token' salida/resources/res/values/strings.xml
En Binary Eye las dos búsquedas no devuelven nada, como debe ser. Si en una app tuya aparece algo, cualquiera que tenga la APK puede verlo igual que tú.
Si quieres procesar el código con un programa, --output-format json da cada clase con sus métodos, líneas y la dirección de cada instrucción:
{
"package": "de.markusfisch.android.binaryeye.activity",
"type": "class",
"extends": "Activity",
"dex": "classes.dex",
"fields": [],
"methods": [
{
"signature": "onCreate(Landroid/os/Bundle;)V",
"return-type": "void",
"arguments": [
"Bundle"
],
"lines": [
{
"code": "",
"source-line": 1
},
{
"code": "super.onCreate(bundle);",
"offset": "0x229e34",
"source-line": 15
},
{
…Exportar como proyecto de Gradle
Con -e, jadx escribe la salida como un proyecto de Android Studio:
jadx -e -d proyecto BinaryEye.apk
contenido de la carpetaproyecto/
├── build.gradle
├── settings.gradle
└── app/
├── build.gradle
└── src/main/ (AndroidManifest.xml, java/, kotlin/, res/, assets/…)plugins {
id 'com.android.application'
}
android {
namespace "de.markusfisch.android.binaryeye"
compileSdkVersion 37
defaultConfig {
applicationId 'de.markusfisch.android.binaryeye'
minSdkVersion 21
//noinspection ExpiringTargetSdkVersion,ExpiredTargetSdkVersion
targetSdkVersion 37
versionCode 182
versionName "1.75.7"
vectorDrawables.useSupportLibrary = true
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
…
}jadx recupera el identificador, las versiones y el SDK del manifiesto. Abrir la carpeta en Android Studio es cómodo para navegar el código con todas sus herramientas. Compilarlo de nuevo casi nunca sale a la primera: los métodos que jadx no reconstruyó, las clases ofuscadas y las bibliotecas que van incluidas como código dan errores que hay que corregir a mano. Para recuperar una app propia cuyo código se perdió es un buen punto de partida, no una solución completa.
jadx con un agente de IA
Un agente de IA como Claude Code, Codex o Cursor puede usar jadx a través de un servidor MCP, el estándar con el que los agentes llaman herramientas externas (lo explicamos en qué es MCP). Así, en vez de copiar y pegar código, le pides «revisa qué datos manda esta app a internet» y el agente busca, abre las clases y te responde citando el código. Hay tres opciones, y cada una sirve para algo distinto.
- jadx-headless-mcp es un servidor que lleva el motor de jadx dentro y no necesita ninguna ventana. Es un solo archivo,
jadx-headless-mcp-0.8.0-all.jaren su última versión, y necesita Java 17 o posterior. Es lo más sencillo para un agente que trabaja en la terminal o en un servidor. Qué archivo bajar y cómo registrarlo en Claude Code está en su página. - jadx-ai-mcp es un plugin para jadx-gui más un servidor aparte, en Python 3.10 o posterior. El plugin abre un puerto local (el 8650) mientras jadx-gui está abierta y el servidor lo conecta con el agente. Su ventaja es que el agente ve lo que tú tienes seleccionado y puede dejar comentarios dentro de jadx-gui: sirve para trabajar a la par con él. Su versión actual es la 6.5.0, del 19 de octubre. El plugin se instala con la propia jadx:
jadx plugins --install-jar jadx-ai-mcp-6.5.0.jar jadx plugins --list
salidaInstalled plugins: 1 - jadx-ai-mcp - JADX-AI-MCP Plugin: Integrates MCP Server support for JADX
- REA no es un servidor de jadx, sino una herramienta de ingeniería inversa para agentes que junta varios motores y guarda evidencias de cada análisis. Para las apps de Android usa jadx-headless-mcp, pero exactamente la 0.7.1: la fija en su código con su suma SHA-256, y así sigue en REA 6.9.0, publicada el 12 de noviembre de 2026. Por eso tanta gente busca justo el archivo
jadx-headless-mcp-0.7.1-all.jar. REA no lo descarga: se le da la ruta con la variableREA_JADX_MCP_JAR, y pide un JDK completo, no solo el JRE. Los pasos están en nuestra guía de REA.
En resumen: para que un agente analice una APK por su cuenta, jadx-headless-mcp; para trabajar en jadx-gui con el agente al lado, jadx-ai-mcp; y si ya usas REA para otros análisis, la 0.7.1 que pide.
Qué puedes analizar y qué no
jadx es legal y se usa todos los días en seguridad y en desarrollo. Lo que puede no serlo es lo que hagas con el código de otros. Algunas reglas prácticas:
- Tus apps, sin problema. Revisar lo que realmente queda dentro de tu APK (llaves, registros, código de prueba) es una buena práctica antes de publicar.
- Apps de código abierto, como Binary Eye, según su licencia.
- Apps de terceros: analizarlas para entender cómo funcionan, comprobar su seguridad o hacer un programa compatible suele estar permitido en muchos países, pero copiar su código o publicarlo no. Muchas condiciones de uso prohíben la ingeniería inversa, como las de Microsoft desde este año.
- Si encuentras una falla de seguridad, avisa en privado al desarrollador antes de contarla en público.
Qué dice la ley en México y en otros países lo explicamos en qué es la ingeniería inversa. No es asesoría legal: si vas a analizar algo ajeno con fines comerciales, consulta a un abogado.
Errores comunes
- «UnsupportedClassVersionError … class file version 55.0»: tu Java es demasiado viejo. Es lo que sale con Java 8:
salida con Java 8Error: A JNI error has occurred, please check your installation and try again Exception in thread "main" java.lang.UnsupportedClassVersionError: jadx/cli/JadxCLI has been compiled by a more recent version of the Java Runtime (class file version 55.0), this version of the Java Runtime only recognizes class file versions up to 52.0 at java.lang.ClassLoader.defineClass1(Native Method) at …
- Instala Java 11 o posterior. Si tienes varios, la variable
JAVA_HOMEdecide cuál usa jadx. - «finished with errors, count: N»: no es un fallo. jadx terminó y avisa cuántas cosas no pudo reconstruir; el resto del código está completo. Eso sí, el programa termina con un código distinto de 0, así que en un script no lo tomes como error fatal.
- «Input class not found» con
--single-class: el nombre debe ser completo, con su paquete, y exacto. Búscalo antes en el manifiesto o con Ctrl+N. - Se queda sin memoria con apps grandes: jadx usa por defecto hasta el 70 % de la memoria del equipo. Para ponerle otro límite,
JAVA_OPTS="-Xmx4g" jadx …(oJADX_OPTS; para la ventana,JADX_GUI_OPTS). Si solo te interesa el código,-rse salta los recursos. - Le diste un archivo
.aab,.xapko.apkm: jadx 1.5.6 los abre directamente, igual que.dex,.jary.aar. - Mucho código «raro» o métodos marcados: la app puede estar protegida con un empaquetador que cifra el código y lo descifra al ejecutarse. jadx solo ve el envoltorio; eso ya es trabajo de análisis dinámico, fuera de esta guía.
Resumen de órdenes
- ver la versión:
jadx --version - descompilar una APK:
jadx -d salida app.apk - abrir la ventana:
jadx-gui app.apk - una sola clase:
jadx --single-class paquete.Clase --single-class-output Clase.java app.apk - nombres únicos si está ofuscada:
jadx --deobf -d salida app.apk - ver el código que falló:
jadx --show-bad-code -d salida app.apk - solo el código, sin recursos:
jadx -r -d salida app.apk - proyecto de Gradle:
jadx -e -d proyecto app.apk - salida en JSON:
jadx --output-format json -d salida app.apk - más memoria:
JAVA_OPTS="-Xmx4g" jadx -d salida app.apk - instalar un plugin:
jadx plugins --install-jar plugin.jar - buscar direcciones:
grep -rhoE 'https?://[A-Za-z0-9./?=_%:-]+' salida/sources | sort -u
Las opciones completas salen con jadx --help. Para el código nativo de una app, sigue con la guía de Ghidra; para dárselo a un agente de IA, jadx-headless-mcp y REA.
Las pruebas se ejecutaron el 12 de noviembre de 2026 con jadx 1.5.6 y OpenJDK 21.0.13 (Eclipse Temurin) en contenedores desechables de Linux sin red, con Binary Eye 1.75.7 de F-Droid (SHA-256 6dd37373…cd3f); las salidas y las capturas son de esa ejecución. El error con Java 8 se obtuvo con OpenJDK 1.8.0_512. Requisitos e instalación según el README de jadx 1.5.6; versiones de jadx, jadx-headless-mcp y jadx-ai-mcp según sus páginas de GitHub, y la versión que pide REA según su código en la 6.9.0. El servidor en Python de jadx-ai-mcp no se ejecutó: su funcionamiento y sus puertos son los de su documentación.
Fuente: skylot/jadx en GitHub
