A DEX-to-JVM bytecode converter built to run the output, not just read it.
dex2jvm converts the Dalvik bytecode inside an Android app (classes*.dex) into
standard Java class files that load and run on a stock HotSpot JVM with
bytecode verification on. It was built for Mac.apk,
which runs Android apps on macOS, and is released here as a standalone tool.
Android apps contain DEX bytecode, designed for Android's own runtime (ART, formerly Dalvik). If you want to run Android app code inside a regular JVM, converting DEX into something the JVM will actually verify and execute is much harder than producing a jar that a decompiler can open.
classes.dex --> dex2jvm --> JVM class files --> HotSpot (verification on)
Tools like dex2jar and enjarify are mostly used for the decompiler case. dex2jvm is built for the execution case: it targets the strict class-file rules (version 52, full StackMapTable frames) and preserves the metadata real apps depend on at run time.
- Passes HotSpot's strict split verifier. Output is class-file version 52, verified with stack-map frames. Frames are computed from real least-upper-bounds over the app's own classes plus a library classpath.
- Methods over the JVM's 64 KB limit. Dalvik has no method size limit; the JVM caps a method at 65,535 bytes. dex2jvm hoists straight-line regions into helper methods, or splits a method with complex control flow into a chain of methods. A method it cannot rescue becomes a stub that throws, and the rest of the class is kept.
- R8-minified input. Shipping apps are built by R8, which emits shapes a JVM
rejects: constructor calls re-pointed at a superclass,
invoke-superon an indirect interface, private access between classes whose nest metadata was stripped. Each is rewritten into its JVM-legal equivalent. - Modern bytecode:
invoke-custom,invoke-polymorphic,const-method-handle,const-method-type. - Metadata: annotations, generic signatures, InnerClasses / EnclosingMethod, Exceptions, MethodParameters, AnnotationDefault, LineNumberTable and SourceFile. Reflection-driven libraries (Retrofit, Gson, Moshi, Kotlin reflection) work, and stack traces have file and line numbers.
- JIT-friendly synchronized code. Exception tables are shaped so HotSpot can
prove monitors balanced and JIT-compile
synchronizedmethods. - Split APKs:
.apk,.apkm,.xapk,.apks(bundletool), plus bare.dex. - Fast, parallel and deterministic: up to 8 threads, with byte-identical output at any thread count. Pure Java, no dependencies.
Requires JDK 21 or newer.
./build.sh
java -jar build/dex2jvm.jar app.apk app.jardex2jvm needs the Android API's class hierarchy to compute precise stack-map frames for framework types (without it, AnkiDroid goes from 0 rejected classes to 169). It picks one automatically:
- An installed Android SDK. If
ANDROID_HOME,ANDROID_SDK_ROOTor the Android Studio default location has a platform installed, dex2jvm reads the app's manifest and uses theandroid.jarof the API level it was compiled against (or the nearest installed one). - Otherwise, a bundled index. dex2jvm ships a 42 KB index of the Android API 36 class hierarchy, generated from AOSP's public API files, so it works with no SDK at all.
The first line of output says which one it used. --classpath <android.jar>
overrides the choice.
| option | |
|---|---|
--classpath <path> |
jars/dirs the class-hierarchy oracle reads library types from (header only, nothing is loaded); overrides the automatic choice above |
--no-library |
use no library hierarchy at all (less precise; for comparison) |
--threads <n> |
worker threads (default min(cores, 8)) |
--synthetic-prefix <s> |
prefix for synthetic members (default dex2jvm) |
--redirect-unsafe <cls> |
route sun.misc.Unsafe calls to static methods on <cls> (for hosts whose Unsafe semantics differ from ART's) |
--redirect-exit <cls> |
route System.exit / Runtime.exit / Runtime.halt to <cls> |
Exit codes: 0 jar written, 1 bad input, 2 usage, 3 the input has no DEX
(a valid hasCode="false" app), 4 the input parsed but conversion failed (for
example out of memory: try a larger -Xmx).
A class that fails to translate is reported on stderr and left out, and the
rest of the app still converts. Large apps may need more heap
(java -Xmx4g -jar ...).
From Java, DexConverter.open(dexBytes...) returns a session that converts
everything or one class at a time (classBytes(name)), so it can also back a
class loader that converts on demand.
Measured 2026-10-02 on an Apple Silicon Mac, JDK 21, against
enjarify at its latest commit (f2db056,
CPython 3) and dex2jar v2.4. Each tool
converted the same APK; every output jar was then judged by the same gates
(bench/compare.sh reproduces it). "Rejected" is the number of classes
HotSpot's verifier refuses to load (G8, with android.jar as the library
classpath; lower is better). Times are wall clock, including JVM or Python
startup.
| app | classes | dex2jvm rejected | enjarify rejected | dex2jar rejected | dex2jvm s | enjarify s | dex2jar s |
|---|---|---|---|---|---|---|---|
| Flappy Bird | 1,136 | 0 | 0 | 0 | 0.6 | 5.3 | 1.3 |
| F-Droid | 19,701 | 0 | 1 | 1,750 | 2.4 | 39.9 | 9.6 |
| Simon Tatham's Puzzles | 3,978 | 0 | 1,235 | 1,101 | 1.1 | 12.9 | 3.9 |
| Mindustry | 6,018 | 0 | 0 | 0 | 1.4 | 20.6 | 4.9 |
| Geometry Dash Lite | 22,982 | 0 | 3 (-9 classes) | 3 | 3.0 | 47.6 | 11.0 |
| Hill Climb Racing 1.43 | 17,058 | 0 | 0 | 0 | 2.1 | 34.6 | 7.9 |
| Wikipedia | 12,788 | 0 | 4,206 | 3,655 | 2.5 | 38.8 | 13.1 |
| Google Calculator | 3,620 | 0 | 692 | 378 | 1.0 | 10.3 | 3.2 |
| AnkiDroid | 9,549 | 0 | 3,657 | 3,394 | 2.1 | 28.5 | 8.0 |
| Telegram | 41,073 | 1 | 167 (-1 classes) | 787 | 5.7 | 101.0 | 45.3 |
| total | 1 | 9,961 | 11,068 | 22 | 340 | 108 |
All three tools pass G6 and G7 on every app. Overall: dex2jvm had 1 class rejected where enjarify had 9,961 and dex2jar 11,068, and it was 15x faster than enjarify and 5x faster than dex2jar. enjarify silently dropped classes on two apps (Geometry Dash Lite, Telegram).
A note on fairness: the tools do not face the same verifier. enjarify emits class-file version 49, which HotSpot checks with the old, more lenient type-inferencing verifier (no stack maps needed). dex2jar emits version 50, which may fall back to that verifier on failure. dex2jvm emits version 52, which always gets the strict stack-map verifier with no fallback, so its numbers are measured against the hardest check of the three. The verifier is not the whole story either: a class can verify and still be translated wrongly, which is what the semantic suite below is for.
tools/verifyholds the structural gates: G6 BranchTargetCheck (branch and handler targets are instruction starts), G7 StackDepthCheck (stack depth agrees with HotSpot's GenerateOopMap), G8 VerifyCheck (HotSpot's real verifier, class by class) and PrivAudit (no latent cross-class private access).tests/semantic/run.shis the semantic suite. It compiles each case with javac, runs it as the reference, then builds it into DEX three ways (d8, d8 without desugaring, R8), converts it back with dex2jvm, runs it with verification on, and diffs the output. Needs the Android SDK command-line tools (ANDROID_HOME).
ANDROID_HOME=~/Library/Android/sdk tests/semantic/run.sh- Correctness on arbitrary apps cannot be fully proven. The structural gates show the output is well-formed, and the semantic suite shows the converted code computes the same result as javac's on its cases. For an arbitrary APK there is no source to compare against, so a translation that verifies but computes a wrong value would not be caught by any gate here. The method splitter in particular relies on a liveness analysis that no gate can check end to end. Bug reports with a reproducing APK are very welcome.
- A small number of classes in real apps still fail the strict verifier. Some of those are invalid code in the apps themselves.
- CompactDex (
.cdex) and.aabbundles are not supported (convert an.aabwithbundletool build-apksfirst). - Resources are not converted. The output jar holds class files only.
See docs/ARCHITECTURE.md. Each class also has a header comment explaining what it does and why, with JVMS and AOSP citations.
Apache License 2.0. See LICENSE and NOTICE.
dex2jvm's design owes a lot to enjarify (Google, Apache 2.0), in particular its register type-inference model. dex2jvm is an independent Java implementation.
Developed with heavy use of AI coding assistants (Claude). Every change is held to the verification gates and semantic tests in this repo, which is how the output is checked rather than trusted.