DEX (Dalvik Executable) is a bytecode format into which the source code of Android applications in Java and Kotlin is compiled. DEX files are executed by the Dalvik virtual machine (up to Android 4.4) or Android Runtime (ART, starting with Android 5.0). According to Android Open Source Project, 2026, the DEX format provides on average 30% more compact code representation compared to standard JVM Java bytecode.
Key Takeaways
DEX (Dalvik Executable) is a bytecode format designed specifically for Android mobile devices. Unlike standard Java bytecode (.class files), DEX is optimized for limited resources: less memory, smaller size, and faster class loading.
Source code in Java or Kotlin is compiled by javac/kotlinc into standard .class files (Java bytecode). Then the d8 tool (or earlier dx) converts .class into one or more DEX files. This conversion is not a simple repackaging — d8 performs optimizations: merges constant pools, rewrites instructions into register architecture, and removes duplicate data.
DEX uses a register-based architecture (as opposed to stack-based JVM). Each method has a fixed number of registers (up to 65536). DEX instructions are shorter — on average 2 bytes vs 1–4 bytes in JVM. This yields more compact code: a typical application shrinks from 10–15 MB of .class to 4–6 MB of .dex.
A DEX file has a strictly defined binary structure. Each file starts with a header and contains several sections that reference each other through offsets.
| Section | Purpose |
|---|---|
| header | Header: magic, checksum, signature, section sizes and offsets |
| string_ids | String table: class, method, and field names |
| type_ids | Types: references to type string identifiers |
| proto_ids | Method prototypes: return type and parameters |
| field_ids | Class fields: class, type, name |
| method_ids | Methods: class, prototype, name |
| class_defs | Class definitions: flags, superclass, interfaces, data offsets |
| data | Actual data: method code, annotations, debug info |
The magic number of DEX is `dex\n035\0` (version 035). Other versions: 036, 037, 038 (for Android 8.0+). The header is 0x70 bytes in size and contains a SHA-1 checksum and offsets of all sections. Header validation is the first step when the virtual machine loads a DEX.
string_ids, type_ids, proto_ids, field_ids, method_ids — these are indexed tables. Instead of storing full names in method code, a 4-byte index is used. This is a key optimization: if a class is referenced 100 times, its name is stored once in string_ids. dex2oat further optimizes these tables during ART compilation.
The process of converting source code into DEX consists of several stages. The modern toolchain uses the D8 compiler, which replaced DX in 2018 with Android Gradle Plugin 3.2.
javac (for Java) or kotlinc (for Kotlin) compiles source code into .class files. Each class is a separate .class file in Java bytecode. At this stage, type checking, generation of bridge methods, and constant inlining are performed.
D8 takes all .class files and transforms them into DEX bytecode. D8 performs several optimizations: removes unused method arguments, merges constant pools from different .class files into one global DEX pool, and converts JVM stack instructions into Dalvik register instructions.
// Kotlin source code
data class User(
val name: String,
val email: String
)
fun greet(user: User): String {
return "Hello, ${user.name}!"
}
After D8 compilation, this code turns into compact DEX instructions: const-string for loading strings, iget-object for accessing object fields, invoke-virtual for calling StringBuilder.append.
D8 is 2–3 times faster than DX, generates more compact DEX (5–10% smaller), and better optimizes Kotlin-specific constructs (inline functions, lambdas). DX was declared deprecated in 2018 and removed from Android Gradle Plugin 8.0.
The execution of DEX code in Android has gone through two stages: the original Dalvik virtual machine (Android 2.2–4.4) and Android Runtime ART (Android 5.0+). The difference in compilation approach is fundamental.
Dalvik used Just-In-Time (JIT) compilation: DEX bytecode was interpreted, and frequently called methods were compiled into native code on the fly. Pros — fast installation. Cons — slower startup and constant CPU overhead for JIT.
ART (Android Runtime) compiles DEX into native code during application installation via dex2oat. This is an Ahead-Of-Time (AOT) approach: installation takes longer, but startup is faster and power consumption is lower. Since Android 7.0, ART uses a hybrid approach — AOT + JIT + Profile Guided Optimization.
The dex2oat tool runs when an application is installed or updated. It compiles DEX into an ELF file with native code for the device’s architecture. The result — .oat and .art files in the /data/dalvik-cache/ directory. Google continuously improves dex2oat: Android 14 added optimizations for foldable devices.
The limit of 65536 methods per DEX file is a legacy of the Dalvik architecture. The method_ids field in the DEX header takes 4 bytes, giving a maximum of 2^16 = 65536 unique references. Modern applications with Google Play Services, Firebase, and other SDKs easily exceed this limit.
Multidex is a mechanism for splitting code across multiple DEX files. The main classes.dex contains entry points (Application class, main Activity), the rest are classes2.dex, classes3.dex, and so on. At startup, classes from additional DEX files are loaded via DexClassLoader.
// build.gradle.kts — enabling multidex
android {
defaultConfig {
multiDexEnabled = true
}
}
// Application class with multidex support
class MyApp : Application() {
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base)
MultiDex.install(this)
}
}
Loading additional DEX files during application startup can cause ANR (Application Not Responding) on devices with Android versions below 5.0. Recommendation — use multidex only when necessary and minimize dependencies to avoid exceeding the limit.
Optimization of DEX is a standard step in building a release Android application. R8 and ProGuard tools reduce DEX size, obfuscate code, and remove unused classes.
R8 is the successor to ProGuard, built into Android Gradle Plugin since 2019. R8 performs minification, obfuscation, and optimization in a single pass, while ProGuard required two stages: ProGuard → D8. ProGuard is still supported, but Google recommends R8 for new projects.
R8 removes unused classes, methods, and fields, renames them to short names (a, b, c), inlines functions, and eliminates dead code. The result — DEX is reduced by 20–40% without loss of functionality.
R8 configuration is specified in the proguard-rules.pro file. Developers can specify which classes cannot be renamed (for example, for reflection or Gson serialization). Firebase and other SDKs provide their own rules in their dependencies.
DEX can be decompiled back into Java code. This is a key security concern for Android applications: without obfuscation, code is restored to a level close to the original.
JADX is the most popular DEX to Java decompiler. It restores class names, methods, fields, and most of the logic. apktool decompiles DEX into smali code (Dalvik assembler) — a low-level representation close to the original instructions. Bytecode Viewer combines multiple decompilers in one interface.
Obfuscation with R8/ProGuard is the first line of defense: class and method names become unreadable. DexGuard is a commercial tool with additional methods: string encryption, integrity checking, anti-tamper. Control Flow Obfuscation (O-LLVM) changes code structure while preserving functionality, making analysis much more difficult.
Frequently Asked Questions
DEX uses a register-based architecture instead of stack-based JVM, has a more compact format (30% smaller), merges all .class files into one file with a single constant pool, and uses 16-bit indexes instead of 8-bit ones.
Smali is an assembler for DEX bytecode. Each DEX instruction has a text representation in smali format. The baksmali tool converts DEX to smali (disassembly), and smali assembles smali back into DEX.
The Gradle countMethods task or the dex-method-counts plugin show the number of methods in each DEX file. The adb shell command with dumpsys also displays statistics of loaded DEX files for installed applications.
Yes, on devices below Android 8.0, multiple DEX files slow down application startup because each additional file is loaded separately. On ART with Android 8.0+, the difference is minimal thanks to dex2oat compilation into a single .oat file.
Yes, there are projects such as dexplorer and Android-compatible JVM implementations that can execute DEX bytecode outside of Android. However, most DEX files use the Android API, making them unsuitable for running on a standard JVM.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also