Skip to main content

ART and DEX — from source code to execution

The complete idea in one sentence​

Kotlin and Java are compiled into JVM class files; Android build tools transform those class files into DEX bytecode; Android Runtime (ART) loads and executes that DEX using a changing combination of interpretation, just-in-time compilation, ahead-of-time compilation, and profiles.

Kotlin source (.kt) ──► Kotlin compiler ──┐
├─► JVM bytecode (.class)
Java source (.java) ──► Java compiler ────┘
│
desugaring + D8/R8
│
▼
DEX bytecode
│
packaged in APK(s)
│
▼
ART
interpreter / JIT / profile-guided AOT

This corrects two common oversimplifications:

  • “Kotlin runs directly on Android” — the Android toolchain transforms compiler output into DEX.
  • “ART compiles the entire app during installation” — modern ART uses hybrid execution and profile-guided optimization depending on device/API/install state.

What DEX is designed for​

DEX means Dalvik Executable format. ART uses it even though the old Dalvik runtime was replaced.

Compared with separate JVM class files, a DEX file organizes bytecode and shared metadata in an Android-oriented format. An APK can contain classes.dex, classes2.dex, and more.

D8, R8, and desugaring​

Tool/processPractical responsibility
Kotlin/Java compilerProduces .class bytecode and metadata
DesugaringRewrites supported newer language/library constructs for configured Android compatibility
D8Converts class bytecode into DEX
R8Shrinks, optimizes, and obfuscates; also produces DEX in release pipelines
Resource shrinkerRemoves eligible unused resources after code shrinking analysis

The exact pipeline is controlled by Android Gradle Plugin versions and configuration. Learn the responsibilities rather than memorizing one internal task name forever.

Practical example: a lambda on an older device​

val labels = orders.map { order -> order.displayName }

The Kotlin compiler creates JVM-compatible output. Android desugaring/D8 then ensures supported constructs can run for the application’s configured API range. This is why using a newer language syntax is not identical to calling an Android framework API introduced on a newer API level.

Language compatibility: can tooling rewrite/compile this construct?
Platform API compatibility: does this device actually contain this Android API?

Calling a new platform method still requires an API check or compatibility abstraction when minSdk is older.

Core-library desugaring​

Some newer Java library APIs can be made available on older Android releases through core-library desugaring when configured and supported. This is a tool-provided compatibility implementation, not proof that every modern JDK API exists on every Android version.

❌ Avoid assumption-driven compatibility​

// “It compiles, therefore it works on minSdk.”
val result = SomeNewAndroidApi.doWork()

✅ Prefer​

Check official API availability, lint output, desugaring support, and tests on the minimum/representative API levels.

What ART does​

ART is the managed runtime that:

  • loads DEX classes;
  • verifies bytecode and runtime constraints;
  • manages the heap and garbage collection;
  • executes interpreted or compiled code;
  • collects runtime profiles;
  • supports debugging, stack traces, JNI interaction, and runtime services.

The AOSP ART configuration documentation explains that modern ART combines AOT, JIT, interpretation, and profile-guided compilation.

Method execution can change over time​

First uses: interpreted DEX may run
Repeated hot use: JIT may compile the method
Profile saved: important paths recorded
Idle/install optimization: profile-guided AOT may compile paths
Next runs: compiled code can execute directly

This is a mental model, not a fixed schedule promised to every app. Device state, runtime version, compilation filters, install source, and profiles affect behaviour.

Release build versus debug build​

Debug builds can disable/minimize optimizations, add debugging checks, skip shrinking, and use different compilation/runtime conditions. A performance conclusion from debug mode is weak evidence for production.

✅ Rule​

Use debuggable builds for debugging correctness. Use release-like, non-debuggable, profile/benchmark-compatible builds for performance measurement.

Multidex​

DEX has reference-index constraints. Large apps may produce multiple DEX files.

Modern Android/plugin support makes multidex routine, but it does not make dependency growth free:

  • larger downloads and installs;
  • more classes to verify/load;
  • longer builds and shrinking;
  • startup placement concerns on older configurations;
  • more attack surface and update cost.

❌ Avoid “multidex solved app size”​

Adding another DEX file solves a packaging constraint, not architectural dependency bloat.

✅ Prefer dependency governance​

Measure size contribution, remove redundant SDKs, use focused artifacts, review transitive dependencies, and keep startup-critical code paths lean.

Class initialization can hide startup work​

object CurrencyFormatter {
private val rules = loadRulesFromDisk() // Runs when object initializes.
}

The first reference can trigger disk I/O and class/object initialization on whichever thread touches it. “Lazy” only moves cost; it does not guarantee a safe thread or good user moment.

✅ Prefer explicit suspend initialization or precomputed lightweight configuration​

Make expensive work visible in the API and assign lifecycle/thread ownership.

Interview-ready answer

Kotlin and Java compile to class bytecode, then Android tooling desugars and converts it into DEX using D8 or the R8 release pipeline. ART loads DEX and uses a hybrid of interpretation, JIT, AOT, and profile-guided compilation. Desugaring can provide selected language/library compatibility, but it does not make newer Android framework APIs exist on older devices. Multidex handles packaging limits but does not remove size, startup, or dependency costs.

Continue​

  1. ART execution, JIT, AOT, and profiles
  2. Heap, garbage collection, and memory leaks
  3. Baseline and Startup Profiles
  4. Runtime interview scenarios