Android jumps on Java release train
12/22/2020
Android jumps on Java release train

​​For many years, Android was stuck with Java 8. Finally, we got a big update. The gap between Java 8 and Java 9 in terms of build compatibility has been overcome and more modern Java versions (up to Java 11) are officially supported on Android. On top of that, Android Gradle Plugin 7.0.0 now requires JDK 11 for running Gradle builds.

In this post, I’ll describe the technical background of this change and how it might affect your project, even if it’s written exclusively in Kotlin.

What is the release train and why have Java 9 and 10 been skipped?

Historically, a new major Java version has been released “every once in a while”. This has led to an irregular release schedule and the language not evolving rapidly.

Beginning with Java 9, it was decided that a new major Java version would be released every 6 months and LTS (long-term support) releases would arrive every 3 years.

​​Java version ​Release date ​Selected language features
​Java 6​December 2006​No language changes
​Java 7​July 2011 ​Project Coin: Diamond operator, Strings in switch, etc.
​Java 8 LTSMarch 2014 ​Lambdas
Type Annotations
Default methods in interfaces
​Java 9September 2017 Private methods in interfaces
​Java 10March 2018 Local-Variable Type Inference
​Java 11 LTSSeptember 2018 Local-Variable Syntax for Lambda Parameters
​Java 12March 2019No stable language features
​Java 13September 2019No stable language features
​Java 14March 2020 Switch Expressions
​Java 15September 2020 Text Blocks
​Java 16March 2021 Pattern Matching for instanceof
Records
​Java 17 LTSSeptember 2021Nothing announced yet

Standard releases have quite a short support period and receive just 2 minor updates exactly 1 and 4 months after their initial release. The LTS releases are guaranteed to be supported till another LTS version is released, in 3 years timeframe (for details about Java release trains, I recommend reading Stephen Colebourne's posts).

Many projects have decided to follow the LTS releases only and now it seems that Google has the same plans for Android. Even though Java 15 is the latest released version, it is a non-LTS version, so Android maintains the latest LTS release, Java 11, as the required minimum.

What complicates the update from Java 8 to Java 9 and onwards?

Java 9 was the first version released in the new era and brought a lot of new features the community desired. The most significant of these is probably the new modular system known by the codename “Project Jigsaw”. It has a concept of dependencies that can define a public API and can keep the implementation private at the same time.

This feature is first and foremost meant to be used by libraries. As the JDK is a library itself, it has also been modularized. The main advantage is that it is possible to create a smaller runtime with only a subset of necessary modules.

During this journey, some Java APIs have been made private and others were moved to different packages. This causes trouble for some well-known annotation processors like Dagger. The generated code is usually annotated with @Generated annotation, which has been moved to a different package in JDK 9. In the case of an Android project written in Kotlin (which has to use kapt to enable Dagger), the build fails on JDK 9 or newer due to a missing @Generated annotation class. Dagger itself has a check for the target Java level and uses @Generated annotation from the correct package. However, there was a bug in kapt - it didn’t report configured target Java level to Java compiler, failing the build and leaving poor developers scratching their heads for hours.

The restructuralization of the JDK was actually wider than just moving Java classes around. The Java compiler needed to be changed as well in order to understand the module system and know how to handle its classpaths appropriately.

As a result, the -bootclasspath compiler option (that was used to include the android.jar to the build) was removed, effectively making all Android classes unavailable to the build. Projects that are written 100% in Kotlin are not affected by this until Android view binding (or similar feature) is enabled. View binding build step generates Java classes that need to be compiled by javac. As the generated classes have dependencies on android.jar classes, the compilation fails when the project is configured to target Java 9 or newer. This limitation has been known and tracked for quite a long time and now it has finally been resolved as part of AGP 7.0.0.

Other tools that also needed an update were D8 and R8, as they work directly with new Java versions of class files.

What to do to upgrade to Android Gradle Plugin 7.0?

When a project with AGP 7.0 build is executed on JDK 8, the build will fail immediately with the following error:

    An exception occurred applying plugin request [id: 'com.android.application']
      Failed to apply plugin 'com.android.internal.application'.
        Android Gradle plugin requires Java 11 to run. You are currently using Java 1.8.
          You can try some of the following options:
            - changing the IDE settings.
            - changing the JAVA_HOME environment variable.
            - changing `org.gradle.java.home` in `gradle.properties`.

The only requirement is to use at least JDK 11 for Gradle when building the project. This can be set through JAVA_HOME environmental variable, org.gradle.java.home Gradle property or in Project Structure dialog in Android Studio:

Project Structure dialog in Android Studio

Can we use new Java language features?

Since Java 9, new language features are usually implemented in the Java compiler without any impact on bytecode or JVM. These can be easily handled by Android’s D8 tool and can be used in Android projects without a problem.

Example:

    public void sayIt() {
        var message = "I am a Java 10 inferred type running on Android";
        System.out.println(message);
    }

You just need to tell Gradle that the project is targeting new Java versions. This can be configured in build.gradle.kts:

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

When compileOptions are not defined, the defaults come into play. Up until AGP 4.1, the default Java compatibility level was set to a very ancient Java 6. Since AGP 4.2, it has been bumped to (only slightly less ancient) Java 8.

Can we use new Java APIs?

Regrettably, Java library APIs are a completely different thing.

Java 8 APIs are available starting with Android 8 (API level 26). Some Java 9 APIs (like List.of()) are available starting with Android 11 (API level 30). These APIs might also be available on older Android versions through Java APIs desugaring.

Hopefully, every future Android version will adopt more of these new Java APIs and make them available for use on older Android versions via desugaring.

Can we use the latest version - Java 15?

We can use JDK 15 for running Gradle builds as it supports the latest Java version since Gradle 6.7.

Unfortunately, we cannot use Java 15 language features in our code. In fact, we cannot use Java 14, 13 and 12 language features either, as the highest supported sourceCompatibility level is still Java 11. However, the limitation of R8 not being able to parse the latest Java version class files was resolved at the beginning of December 2020, so we can hope for Java 15 support arriving soon.

How does this affect Kotlin projects?

Not much. Kotlin compiler and toolchain are not affected by JDK used for Gradle build nor the Java compatibility level set for a project.

However, when you use JDK 11 for build and Java 11 as a source compatibility level for Java compiler, it is reasonable to use the same level as Kotlin target for JVM. This allows Kotlin to generate code optimized for newer Java language versions:

    kotlinOptions {
        jvmTarget = JavaVersion.VERSION_11.toString()
    }

When kotlinOptions are not defined, the default jvmTarget is again set to a very ancient Java 6. Please define your kotlinOptions!

The bottom line

Better late than never, Java 11 has just arrived in the Android world. It won’t much change your day to day work of writing Java code. It may not change your work of writing Kotlin code at all. Nevertheless, it is a big thing for the ecosystem which promises easier upgrades in the future, that will in turn allow for the depreciation of lots of outdated stuff. Both, the Android team at Google and the Kotlin team at JetBrains may finally drop support for Java 6 and Java 8 and focus on more contemporary language versions. That is something we would all profit from.


Pavel Švéda

Twitter: @xsveda​


Tags

#android; #java; #kotlin; #gradle

Author

Pavel Švéda

Versions

Android Gradle Plugin 7.0.0-alpha01
Android Studio 2020.3.1 Canary 2
Java 15.0.1
Kotlin 1.4.21