Identifying the "4 JDKs" in Android Studio: From Launcher JVM to Toolchain

Have you ever encountered a situation where your build passes in the terminal but fails in the IDE? Or perhaps you updated Java on your system, but Android Studio seems to ignore it ?

The root cause often lies in the fact that Android Studio manages four different JDKs depending on the specific task. Confusing these can lead to "mysterious" environment issues. In this guide, we will identify each one and show you how to pinpoint them in your own environment.

 

🤔 Why You Need to Know the "4 JDKs"

Android Studio separates the processes that run the IDE, manage the build, and compile your source code. If these refer to different JDKs, you may run into compatibility glitches.

Let’s identify the "true identity" of each.

 

🤔 1. Launcher JVM (The Terminal/Entry Point)

Role: This is the JVM that initially kicks off when you run the ./gradlew script. It acts as the "starter" to wake up the heavy-lifting Gradle Daemon.

How to Identify: Run the following command in your terminal:


❯ ./gradlew -v            

------------------------------------------------------------
Gradle 9.3.1
------------------------------------------------------------

Build time:    2026-01-29 14:15:01 UTC
Revision:      44f4e8d3122ee6e7cbf5a248d7e20b4ca666bda3

Kotlin:        2.2.21
Groovy:        4.0.29
Ant:           Apache Ant(TM) version 1.10.15 compiled on August 25 2024
Launcher JVM:  21.0.10 (Eclipse Adoptium 21.0.10+7-LTS)
Daemon JVM:    Compatible with Java 21, JetBrains, nativeImageCapable=false (from gradle/gradle-daemon-jvm.properties)
OS:            Mac OS X 26.3 aarch64

Look for the line labeled Launcher JVM.

Dependent on: Your OS environment variables, specifically JAVA_HOME or your PATH setting.

 

🤔 2. Daemon JVM (The Build Engine)

Role: This is the "workhorse" JVM that actually performs the heavy lifting—compiling code and packaging your APK/AAB. It stays resident in memory to speed up subsequent builds.

How to Identify: Check the project-level configuration file:
gradle/gradle-daemon-jvm.properties


toolchain.vendor=JetBrains
toolchain.version=21

You can also see this in the ./gradlew -v output under Daemon JVM.

Dependent on: Gradle 8.8+ project settings.

 

🤔 3. IDE JDK (The Android Studio Runtime)

Role: This JVM runs the Android Studio application itself. It handles the editor UI, code completion (indexing), and IDE plugins.

How to Identify:
Mac: Android Studio > About Android Studio

Dependent on: The jbr folder bundled within your Android Studio installation directory.

 

🤔 4. Java Toolchain (The Compilation Target)

Role: This setting strictly defines which Java version should be used to compile your source code. It ensures that everyone on a team uses the exact same Java version for the final binary, regardless of their local IDE settings.

How to Identify: Check your app/build.gradle.kts (or build.gradle):


// build.gradle.kts

java {
  toolchain {
    languageVersion = JavaLanguageVersion.of(21)
  }
}


// build.gradle.kts

with(javaToolchains.compilerFor(java.toolchain).get().metadata) {
  logger.lifecycle("Compiler JVM: $vendor $languageVersion ($jvmVersion)")
  logger.lifecycle("$installationPath")
}

// Output:
// Compiler JVM: JetBrains 21 (21.0.10+7-b1163.108)
// /Users/jake/.gradle/jdks/jetbrains_s_r_o_-21-aarch64-os_x.2/jbrsdk_jcef-21.0.10-osx-aarch64-b1163.108/Contents/Home

Note: If this is not explicitly set, the Daemon JVM usually handles the compilation tasks by default.

 

🤔 Summary: Identification Checklist

Use this table as a quick reference to audit your development environment:

Final Thought
Even if your Launcher JVM is "Eclipse Adoptium" while your Daemon JVM is "JetBrains Runtime," your project will generally work fine as long as the major versions (e.g., Java 21) match.


Modernizing Android Build Scripts: Moving from "android { ... }" to "configure { ... }"

In the world of Android development, Kotlin DSL has become the standard for writing build scripts.

While the familiar android { ... } block works perfectly for simple projects, as your project grows and you start sharing build logic across multiple modules (e.g., using Convention Plugins), you might find it a bit limiting.

Today, we’ll look at why and how to switch to the more explicit and scalable configure<ApplicationExtension> syntax.

 

🧑🏻‍💻 1. Why Make the Switch?

The standard android { ... } block in build.gradle.kts is actually a "shorthand" provided by the Android Gradle Plugin (AGP). While convenient, using configure<T> offers several advantages:

  • Better Type Safety: By explicitly telling Gradle that "this block is an ApplicationExtension," the IDE (Android Studio) can provide more accurate code completion and error highlighting.
  • Scalable Build Logic: If you are moving common logic into buildSrc or external plugins to keep your Gradle files DRY (Don't Repeat Yourself), using the explicit extension type becomes essential for writing clean, reusable functions.

 

🧑🏻‍💻 2. The Transformation: Before vs. After

Let’s compare the standard approach with the explicit configuration style for an App module.

Before: The Standard android Block


// app/build.gradle.kts
android {
    compileSdk = 35
    defaultConfig {
        applicationId = "com.example.myapp"
        minSdk = 26
        targetSdk = 35
    }
}

After: Using configure<ApplicationExtension>
Note that you will need to import the ApplicationExtension class explicitly.


// app/build.gradle.kts
import com.android.build.api.dsl.ApplicationExtension

configure<ApplicationExtension> {
    compileSdk = 35
    defaultConfig {
        applicationId = "com.example.myapp"
        minSdk = 26
        targetSdk = 35
        // ...
    }
}

 

🧑🏻‍💻 3. Choosing the Right Extension Type

Not every module is an "Application."

You should choose the extension type that matches your module's purpose:

[!TIP]
Use CommonExtension when writing shared logic that applies to both your App and Library modules (like Java versioning or Compose options).

 

🧑🏻‍💻 4. Practical Implementation: Reusable Build Logic

The true power of this syntax shines when you extract common configurations into a function, such as in buildSrc.


// Example of a shared configuration function in buildSrc
import com.android.build.api.dsl.ApplicationExtension
import org.gradle.api.Project

fun Project.configureAndroidApplication() {
    extensions.configure<ApplicationExtension> {
        compileSdk = 35
        defaultConfig {
            minSdk = 26
            // ...other shared settings
        }
    }
}

By defining your build logic this way, your module-level Gradle files stay thin and highly maintainable.

 

🧑🏻‍💻 Conclusion

The traditional android { ... } block is great for its brevity. However, once your project reaches a certain scale and you start treating your build configuration as "real code," switching to configure is the way to go.

It brings better IDE support, type safety, and makes your build logic much easier to share across modules.


The New Standard in Android Studio Panda: Automating JDK Management with Foojay Resolver

As an Android developer, are you still wasting time managing JDK versions?

"I cloned a new project and the build failed,"
"Updating JDK settings in CI is a pain,"
"Different team members are using different JDK vendors..."

These headaches are now a thing of the past thanks to the combination of Android Studio Panda (2025.3.1), AGP 9.1, and the Foojay Resolver plugin.

 

🤔 1. What is org.gradle.toolchains.foojay-resolver-convention?

In short, it is a plugin that allows Gradle to automatically find, download, and configure the required JDK from the internet.

Normally, even if you define a Java Toolchain in your build.gradle, the build will fail if that specific JDK isn't already installed on your local machine.

By adding this plugin, Gradle communicates with the Foojay (Friends Of OpenJDK) database (via the Disco API) to automatically fetch and set up the correct JDK for you.

 

🤔 2. What Changed in Android Studio Panda?

With the release of Android Studio Panda, JDK management has shifted from "IDE-driven" to "Project-driven (Gradle-driven)."

  • Gradle Daemon JVM Criteria: Instead of manually selecting a JDK in the IDE settings, Android Studio now reads the toolchain configuration directly from your project files. It automatically switches the JVM used to run Gradle itself (the Daemon) to match your project.
  • Synchronized Environment: This eliminates the common "it works in the terminal but fails in the IDE" issue. The JDK used by ./gradlew and the "Run" button in Android Studio will now always be 100% identical.

 

🤔 3. Critical Notes for AGP 9.1

If you are using AGP 9.1 or higher, keep these points in mind:

  • Java 21 Requirement: AGP 9.x series strictly requires JDK 21.
  • Consistency is Key: Since AGP 9.1 strongly encourages the Gradle Daemon and the compilation JVM to be the same, the benefits of automatic resolution via foojay-resolver are more significant than ever. It ensures your entire pipeline stays on JDK 21 without manual intervention.

 

🤔 4. Implementation Guide (Quick Steps)

Step 1: Update settings.gradle.kts

Add the plugin to the very top of your root settings.gradle.kts file. This enables the automatic download capability.


plugins {
    // The magic line for automatic JDK downloads
    id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
}

Step 2: Configure build.gradle.kts

Define the Java version in your app module’s build.gradle.kts (or within a convention plugin).


android {
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_21
        targetCompatibility = JavaVersion.VERSION_21
    }
    
    kotlinOptions {
        jvmTarget = "21"
    }

    // Java Toolchain configuration
    java {
        toolchain {
            languageVersion.set(JavaLanguageVersion.of(21))
            // Optional: Specify a vendor if needed
            // vendor.set(JvmVendorSpec.ADOPTIUM)
        }
    }
}

 

🤔 5. Key Benefits at a Glance

 

🤔 Conclusion

In the era of Android Studio Panda and AGP 9.1, foojay-resolver-convention is no longer just a "nice-to-have" option—it is core infrastructure for modern Android development.

When upgrading your legacy projects, make this plugin your first priority. Stop fighting with environment variables and start focusing on what matters most: writing great code.

[!TIP] To verify that your JDKs are being recognized correctly, run ./gradlew -q javaToolchains in your terminal.