Question

Fix Java and Kotlin JVM Target Mismatch in Android Gradle Builds

kotlinandroidandroid-studiogradleandroid-gradle-plugin

Question

An Android Studio build fails during kaptGenerateStubsDebugKotlin with the following error:

'compileDebugJavaWithJavac' task (current target is 1.8) and
'kaptGenerateStubsDebugKotlin' task (current target is 17)
jvm target compatibility should be set to the same Java version.

How can the Java and Kotlin JVM target compatibility settings be configured so that Java compilation, Kotlin compilation, and KAPT use the same version?

Short Answer

You will learn what JVM target compatibility means in an Android Gradle build, why Java and Kotlin targets must match, and how to configure both consistently with compileOptions and a Kotlin JVM toolchain. You will also learn how KAPT relates to Kotlin compilation and how to diagnose conflicting Gradle settings.

Concept

Java and Kotlin source files are compiled into JVM bytecode. A JVM target specifies the bytecode version produced by a compiler.

In the error:

  • Java compilation (compileDebugJavaWithJavac) produces Java 8-compatible bytecode because its target is 1.8.
  • Kotlin compilation, including the KAPT stub-generation task (kaptGenerateStubsDebugKotlin), is configured to target Java 17 bytecode.

These tasks contribute classes to the same Android app variant, debug. Gradle rejects the build because mixing target bytecode versions can cause inconsistent compilation and tooling behavior.

The important rule is:

Configure Java and Kotlin to target the same JVM version for every Android module that contains both languages.

KAPT is Kotlin's annotation-processing tool. Before Java annotation processors such as Room, Dagger, or Hilt can run, KAPT generates Java-like stubs from Kotlin code. Therefore, a KAPT error often reveals a Kotlin compiler configuration issue rather than a problem in an annotation processor.

Mental Model

Think of Java and Kotlin compilation as two machines making parts for the same product.

  • The Java machine is set to the Java 8 specification.
  • The Kotlin machine is set to the Java 17 specification.

Although both machines produce bytecode, their parts follow different versions of the specification. Gradle stops the assembly line rather than producing an app with mismatched parts.

KAPT is an extra station attached to the Kotlin machine. If Kotlin is configured for Java 17, KAPT stubs are also associated with that Kotlin target. Fix the main Kotlin and Java settings, and the KAPT task will use the consistent configuration.

Take Quiz

Syntax and Examples

For a modern Android project using Kotlin DSL (build.gradle.kts), choose one JVM version and configure both Java and Kotlin to use it.

Target Java 17

android {
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_17
        targetCompatibility = JavaVersion.VERSION_17
    }
}

kotlin {
    jvmToolchain(17)
}

This is a common configuration for modern Android projects whose Android Gradle Plugin and Gradle versions support JDK 17.

  • sourceCompatibility controls Java language features accepted by the Java compiler.
  • targetCompatibility controls the Java bytecode target.
  • jvmToolchain(17) selects a JDK/toolchain for Kotlin compilation and aligns Kotlin's target with Java 17.

Target Java 8 instead

If the project must remain on Java 8 bytecode, use the same version everywhere:

android {
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
}

kotlin {
    jvmToolchain(8)
}

Do not configure Java for 8 while configuring Kotlin for 17, or the reverse.

Groovy DSL equivalent

For build.gradle rather than build.gradle.kts:

android {
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_17
        targetCompatibility JavaVersion.VERSION_17
    }
}

kotlin {
    jvmToolchain(17)
}

Use only the syntax matching the Gradle file in your project.

Step by Step Execution

Consider this mismatched configuration:

android {
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
}

kotlin {
    jvmToolchain(17)
}

During a debug build, Gradle performs these steps:

  1. The Android Gradle Plugin configures compileDebugJavaWithJavac from compileOptions.
  2. That Java task receives target compatibility 1.8.
  3. The Kotlin plugin configures Kotlin compile-related tasks, including kaptGenerateStubsDebugKotlin, from the Kotlin toolchain.
  4. Those Kotlin tasks receive a target of 17.
  5. Kotlin Gradle plugin validation compares the Java target and Kotlin target for the same variant.
  6. It finds 1.8 versus 17 and fails before annotation processing proceeds.

Fix it by making the settings agree:

android {
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_17
        targetCompatibility = JavaVersion.VERSION_17
    }
}

kotlin {
    jvmToolchain(17)
}

Now both sides target 17, so KAPT can generate stubs and the build can continue.

Real World Use Cases

Matching JVM targets matters whenever an Android module contains Java and Kotlin, including:

  • Android applications: Kotlin Activities and Java utility classes are compiled into the same app variant.
  • Room databases: KAPT or KSP generates database code from Kotlin entities and DAOs.
  • Dagger or Hilt: Annotation processors generate dependency-injection code that must compile with the rest of the module.
  • Multi-module apps: Each Android library module needs internally consistent Java and Kotlin settings.
  • Shared build conventions: A convention plugin can apply one standard JVM target to all modules, preventing configuration drift.

The target version is separate from Android's minSdk. Android API compatibility is controlled by SDK settings and, when required, desugaring. JVM target compatibility controls the compiler bytecode level used by Java and Kotlin tasks.

Take Quiz

Real Codebase Usage

In production projects, developers usually centralize this configuration rather than setting unrelated values in each task.

Preferred pattern: one Java target and one Kotlin toolchain

android {
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_17
        targetCompatibility = JavaVersion.VERSION_17
    }
}

kotlin {
    jvmToolchain(17)
}

A JVM toolchain is preferable to relying only on whichever JDK happens to run Gradle. It makes builds more reproducible across developer machines and CI.

Check all Android modules

A multi-module project may have app, feature-*, and core-* modules. Configure each module that applies Android and Kotlin plugins, or place the shared rules in a convention plugin.

Use KAPT only when needed

KAPT does not need a special target setting of its own in the normal case. It follows Kotlin compilation configuration. Configure the Java and Kotlin compilers correctly rather than attempting to fix only kaptGenerateStubsDebugKotlin.

Legacy Kotlin configuration

Older projects may explicitly configure kotlinOptions.jvmTarget. If it exists, it must agree with Java:

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

android {
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_17
        targetCompatibility = JavaVersion.VERSION_17
    }
}

tasks.withType<KotlinCompile>().configureEach {
    kotlinOptions.jvmTarget = "17"
}

For newer Kotlin Gradle plugin setups, prefer kotlin { jvmToolchain(17) } instead of adding task-by-task configuration.

Common Mistakes

Changing only sourceCompatibility

This does not fully describe the bytecode target. Set both Java properties:

// Incomplete configuration
compileOptions {
    sourceCompatibility = JavaVersion.VERSION_17
}

Use:

compileOptions {
    sourceCompatibility = JavaVersion.VERSION_17
    targetCompatibility = JavaVersion.VERSION_17
}

Configuring Java but not Kotlin

android {
    compileOptions {
        targetCompatibility = JavaVersion.VERSION_17
    }
}

// Kotlin may still have an explicit jvmTarget of "1.8" elsewhere.

Always inspect Kotlin configuration, including jvmToolchain(...) and any older kotlinOptions.jvmTarget setting.

Keeping contradictory old and new settings

A project may contain a legacy kotlinOptions.jvmTarget = "1.8" plus a new toolchain of 17. Remove or update stale settings so one intended version is clear.

Editing the wrong Gradle file

The failing task begins with :app:, so start with the app module's Gradle file, usually app/build.gradle.kts or app/build.gradle. Also check applied convention plugins or root scripts that can override module settings.

Assuming the installed JDK alone fixes targets

Running Gradle with JDK 17 does not automatically mean Java source is compiled with target 17. compileOptions can still explicitly target Java 8. The JDK used to run Gradle and the target bytecode version are related but distinct settings.

Using Java 17 language APIs without Android compatibility planning

A Java 17 target does not automatically make every Java 17 standard-library API available on every Android device. When using newer Java APIs on older Android versions, verify Android support and configure core library desugaring when appropriate.

Comparisons

Setting or termWhat it controlsExample
sourceCompatibilityJava language level accepted by javacWhether Java 17 syntax can be used
targetCompatibilityBytecode target produced by Java compilationJavaVersion.VERSION_17
Kotlin jvmToolchain(17)JDK/toolchain Kotlin uses and its JVM target configurationKotlin compilation aligned to 17
kotlinOptions.jvmTargetLegacy task-level Kotlin bytecode target setting"17"
Gradle runtime JDKJDK used to execute Gradle itselfConfigured in Android Studio or org.gradle.java.home
minSdkLowest Android API level supported by the appminSdk = 24

Java 8 versus Java 17 target

ChoiceWhen it is usefulImportant consideration
Java 8Legacy projects or builds constrained by older toolingConfigure Java and Kotlin consistently to 8
Java 17Modern Android projects using compatible Gradle and Android Gradle Plugin versionsEnsure all modules and plugins support the selected toolchain

The correct choice is not always the highest available version. It is the version that your project tooling supports and that is configured consistently across the build.

Take Quiz

Cheat Sheet

  • A Java/Kotlin Android module needs matching JVM targets.
  • The error 1.8 versus 17 means Java and Kotlin compilation settings disagree.
  • compileDebugJavaWithJavac gets its target from Android compileOptions.
  • kaptGenerateStubsDebugKotlin follows Kotlin compilation settings.
  • Modern Kotlin DSL configuration for Java 17:
android {
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_17
        targetCompatibility = JavaVersion.VERSION_17
    }
}

kotlin {
    jvmToolchain(17)
}
  • For Java 8, use VERSION_1_8 and jvmToolchain(8).
  • Check for stale kotlinOptions.jvmTarget settings that override or contradict the intended target.
  • Apply consistent settings to every module that compiles Java and Kotlin.
  • The JDK that runs Gradle is not necessarily the Java bytecode target.

FAQ

Why does the error mention KAPT rather than my Kotlin file?

KAPT generates Java stubs from Kotlin source before annotation processors run. It is a Kotlin compile-related task, so it detects the Kotlin-versus-Java target mismatch.

Do I need to configure KAPT separately?

Usually no. KAPT uses the Kotlin compiler configuration. Align android.compileOptions with the Kotlin toolchain or Kotlin JVM target.

Should I use Java 8 or Java 17 for an Android project?

Use a version supported by your Android Gradle Plugin, Gradle version, dependencies, and team environment. Either can work when Java and Kotlin are configured to the same target.

Is jvmToolchain(17) the same as setting Android minSdk to 17?

No. jvmToolchain(17) refers to Java version 17. minSdk refers to Android API level 17. They are different version systems.

Why does Android Studio use JDK 17 but Java compilation still targets 1.8?

The JDK running Android Studio or Gradle is separate from targetCompatibility. Your Gradle configuration can explicitly compile Java bytecode for 1.8 even when Gradle runs on JDK 17.

Where should I look for a conflicting JVM target?

Check the failing module first, such as app/build.gradle.kts. Then search the project for jvmTarget, jvmToolchain, sourceCompatibility, and targetCompatibility, including convention plugins.

Does changing the JVM target require a clean build?

It is often helpful to run a clean build after changing compiler configuration, but it does not replace correcting the conflicting settings.

Take Quiz

Mini Project

Description

Configure a small Android module that contains Kotlin and Java code, plus KAPT. The project demonstrates how one shared JVM target prevents compiler and annotation-processing tasks from disagreeing.

Goal

Make the debug build use Java 17 consistently for Java compilation, Kotlin compilation, and KAPT stub generation.

Requirements

Configure Java source and target compatibility as Java 17. Configure Kotlin to use a Java 17 JVM toolchain. Enable KAPT in the module. Include one Kotlin class and one Java class in the same module. Verify that no older Kotlin JVM target setting conflicts with Java 17.

Take Quiz

Keep learning

Related questions

View more