skip to content

Why can't a plain Gradle java/kotlin build produce an Android APK, and what exactly does AGP supply that Gradle lacks?

level: seniorimportance: should knowfreq 40%

answer

  1. JVM plugins stop at .class + JAR
  2. Android needs manifest merge, AAPT2/R, DEX/D8, package+sign
  3. DEX runtime ≠ JVM bytecode; desugaring
  4. engine stays domain-agnostic
  5. plugin-extends-engine pattern

basics

~20 s

Plain Gradle plugins only compile JVM bytecode and jar it. Android needs manifest merging, resource/AAPT2 processing, R-class generation, dexing to DEX, and APK/AAB packaging and signing — none of which Gradle's JVM plugins know how to do. AGP adds all of that.

solid answer

~50 s

Gradle's bundled JVM plugins (`java`, `java-library`, the Kotlin JVM plugin) stop at compiling `.class` files and packaging a JAR. An Android artifact is fundamentally different: it requires **manifest merging** across modules/libraries, **resource processing** with AAPT2 and generation of the typed `R` class, **converting JVM bytecode to DEX** via D8 (with desugaring for newer language features on older devices), and **packaging + signing** into an APK or an Android App Bundle (AAB). It also needs Android's source-set conventions (`androidTest`, `res`, manifest) and instrumented-test tasks running on a device/emulator. None of that is JVM-generic, so Gradle can't supply it — it is exactly the Android domain knowledge AGP contributes. Architecturally this is the canonical example of Gradle's plugin model: the engine stays domain-agnostic (task graph, dependency resolution, caching, lazy configuration), and a domain plugin layers the specialized pipeline on top. The same separation lets Spring Boot, KGP, and other ecosystem plugins coexist.

code

kotlin · 9 lines
kotlin
// Plain JVM module: only compiles + jars — no APK is possible
plugins { `java-library` }

// Android module: AGP adds manifest merge, AAPT2/R, D8 dexing, APK/AAB packaging
plugins {
    id("com.android.application")
    id("org.jetbrains.kotlin.android")
}
android { namespace = "com.example.app"; compileSdk = 34 }

go deeper

for a junior

Say plain Gradle only makes a JAR; AGP adds the steps to make an APK (resources, packaging).

for a middle

List the Android-specific steps (manifest merge, AAPT2/R, DEX, package/sign) absent from JVM plugins.

for a senior

Frame it as the plugin-extends-engine architecture: engine stays generic, AGP wraps AAPT2/D8/packaging as tasks with declared I/O.

for a principal

Generalize to ecosystem design: a clean engine/domain boundary lets many large plugins coexist; weigh implications for custom in-house plugins and build platform strategy.

## What vanilla Gradle JVM plugins do The `java`, `java-library`, and Kotlin JVM plugins give you: compile sources to `.class`, run unit tests, and bundle a **JAR**. Their world is JVM bytecode and the classpath. That is the entire scope. ## What an Android artifact additionally requires Producing an installable Android app needs steps with no JVM-generic equivalent: 1. **Manifest merging** — combining the app manifest with manifests from every library dependency, resolving conflicts. 2. **Resource processing (AAPT2)** — compiling `res/` (layouts, drawables, strings) into binary resources and generating the typed **`R`** class so code can reference resources by id. 3. **Bytecode → DEX (D8)** — Android devices run a DEX/ART runtime, not stock JVM bytecode; class files must be converted to `.dex`. **Desugaring** rewrites newer language/library features so they run on older API levels. 4. **Packaging & signing** — assembling DEX + resources + native libs + manifest into an **APK**, or a publish-oriented **AAB**, then signing it. 5. **Android conventions & instrumented tests** — source sets like `src/androidTest`, manifest location, and tasks that run on-device/emulator (`connectedAndroidTest`). ## Why Gradle itself can't do this Gradle is deliberately **domain-agnostic**: its job is the task graph, configuration phase, dependency resolution, incremental builds, and caching. AAPT2, D8, manifest merging, and APK/AAB packaging are Android-specific tools and formats — they belong in a plugin, not the engine. AGP wraps these tools as Gradle tasks with declared inputs/outputs so they slot into Gradle's machinery. ## The architectural pattern ```text Gradle engine (generic) - task graph, lifecycle phases - dependency resolution (configurations) - incremental build + build/configuration cache - Provider/Property lazy configuration ^ | applied as a plugin | AGP (Android domain) - android { } extension - manifest merge / AAPT2 / R / D8 / package+sign tasks - Android source-set + test conventions ``` This is the same plugin-extends-engine pattern used by every ecosystem plugin. The clean boundary is *why* the Gradle ecosystem scales: the engine never needs to know about Android, Spring, or Kotlin/Multiplatform specifics — each is a plugin contributing its own tasks and extension. AGP is simply the largest, most opinionated instance of that pattern.

  • Why must Android bytecode be converted to DEX?
    Android devices run the ART/Dalvik runtime, which executes DEX format, not standard JVM .class bytecode. D8 converts compiled .class files into .dex, and desugaring backports newer language features for older API levels.
  • What is the R class and who generates it?
    R is a generated class exposing integer ids for resources (R.layout.main, R.string.app_name). AGP's resource-processing step (AAPT2) generates it so code can reference compiled resources type-safely.
  • How does this boundary help the broader Gradle ecosystem?
    Because the engine stays domain-agnostic, many ecosystem plugins (AGP, Spring Boot, Kotlin Multiplatform) can each contribute their own tasks/extensions without the engine knowing their domains, enabling them to coexist in one build.

Gradle is a general factory line; turning generic parts into a sealed, signed Android package needs specialized stations (resource press, DEX converter, packager) that AGP bolts onto the line.

saying these in an interview costs you the question

  • Claiming the java/kotlin plugin can output an APK — it stops at a JAR.
  • Saying Android runs standard JVM bytecode at runtime — it runs DEX on ART.
  • Treating manifest merging / AAPT2 / D8 as Gradle features rather than AGP-supplied.

context