nowinandroid builds with Gradle 8.5 and JDK 21
Tags: gradle, java, android
Published: 2024-01-18 02:44:07

Gradle 8.5 fully supports compiling, testing and running on Java 21. Java updates frequently include optimizations that improve the performance of both the JVM and the Java applications running on it. 
At the Devoxx Belgium [presentation](https://www.youtube.com/watch?v=T6X2Yytrzyg) "With Java 21, Your Code Runs Even Faster But How is that Possible?", Per Minborg explains some of the optimizations shipped in Java 21 like [Perform I/O operations in bulk for RandomAccessFile highlighting ](https://bugs.openjdk.org/browse/JDK-8298639
) or [Unroll by hand StringUTF16 and StringLatin1 polynomial hash loops](https://bugs.openjdk.org/browse/JDK-8282664), and what is better, these optimizations have an immediate impact without changing the java application because by direct [usage](https://github.com/search?q=repo%3Agradle%2Fgradle%20import%20java.io.RandomAccessFile&type=code) or by transitive dependency use.

Following the previous [Java 17 article](https://dev.to/cdsap/measuring-jdk-updates-for-local-builds-in-android-projects-3m4g), in this article we share the results of measuring an Android project with Java 21.

## Nowinandroid
The project used is [nowinandroid](https://github.com/android/nowinandroid). The experiment is based on the commit [f5b3ae5](https://github.com/android/nowinandroid/commit/f5b3ae56dcf0022456d061d0c4c121be5a144984) of the main branch(12/22). At this point, the project was already using Gradle 8.5. 
The only change applied was to update the AGP to 8.2.1 because it included the fix for the issue: [JdkImageTransform fails when using JDK 21](https://issuetracker.google.com/issues/317235925)

## Experiment methodology
* Two variants 
 * JDK 17
 * JDK 21
* Scenarios:
 * `assembleDebug`
 * `assembleRelease`
 * `testDemoDebug`
 * `lintDemoRelease`
* Each variant/scenario runs 100 clean builds in GHA runners
* Memory configuration for all builds: `-Xmx6g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:+UseParallelGC -XX:MaxMetaspaceSize=1g`


## Results
First, we explore the overall build time for the different scenarios:

|                   | JDK 17 - Median (secs) | JDK 21 - Median (secs) | Improvement |
|-------------------|------------------------|------------------------|-------------|
| assembleDebug     | 448                    | 433                    | 3.4%        |
| assembleRelease   | 659                    | 591                    | 10.2%       |
| testDemoDebug | 334                    | 320                    | 4.3%        |
| lintDemoRelease   | 306                    | 296                    | 3.3%        |


In four evaluated scenarios, we are observing modest improvements in three of them, while the `assembleRelease` scenario shows a significant reduction of 10% in build time.

Next, we explore where the improvements came from at the task level:

### assembleDebug

| Task                              | Diff Median (seconds) | Improvement |
|-----------------------------------|-----------------------|-------------|
| :app:mergeExtDexDemoDebug         |                   8.5 |        5.4% |
| :app-nia-catalog:mergeExtDexDebug |                   2.8 |        3.4% |
| :app:mergeExtDexProdDebug         |                   2.3 |       10.2% |
| :app:l8DexDesugarLibDemoDebug     |                   2.4 |        8.1% |
| :app:hiltJavaCompileProdDebug     |                   1.7 |        7.3% |

### assembleRelease

| Task                                          | Diff Median (seconds) | Improvement |
|-----------------------------------------------|--------------------|-------------|
| :app:minifyDemoReleaseWithR8                  |                 49 |       18.2% |
| :app-nia-catalog:mergeReleaseGlobalSynthetics |                 35 |       29.6% |
| :app-nia-catalog:mergeExtDexRelease           |                 29 |       19.9% |
| :app-nia-catalog:l8DexDesugarLibRelease       |                  8 |       24.7% |
| :app:minifyProdReleaseWithR8                  |                  7 |        9.9% |


### testDemoDebugUnitTest
| Task                                     | Diff Median (seconds) | Improvement |
|------------------------------------------|--------------------|-------------|
| :feature:foryou:testDemoDebugUnitTest    |                3.4 |        7.6% |
| :app:testDemoDebugUnitTest               |                2.5 |        6.8% |
| :core:designsystem:testDemoDebugUnitTest |                2.4 |        4.9% |
| :core:data:testDemoDebugUnitTest         |                  1 |       10.2% |
| :app:hiltJavaCompileDemoDebug            |                  1 |        9.6% |


### lintDemoRelease


| Task                                        | Diff Median (seconds) | Improvement |
|---------------------------------------------|--------------------|-------------|
| :core:data:lintAnalyzeDemoRelease           |                1.9 |        9.1% |
| :core:designsystem:compileDemoReleaseKotlin |                1.5 |          6% |
| :core:designsystem:lintAnalyzeDemoRelease   |                1.2 |        6.2% |
| :core:analytics:lintAnalyzeDemoRelease      |                1.1 |          8% |
| :core:datastore:lintAnalyzeDemoRelease      |                0.9 |         12% |

Again the release scenario shows a significant improvement in the expensive R8 tasks. 

Finally, we aggregated the absolute diff of the median for the tasks in each scenario providing the "serial" improvement using JDK 21:

|                   | Aggregated diff  (seconds)|
|-------------------|---------------------------------------------------------------|
| assembleDebug     | 37                                                            |
| assembleRelease   | 158                                                           |
| testDebugUnitTest | 32                                                            |
| lintDemoRelease   | 27                                                            |


## Data

| Experiment scenario | Results                                                |
|---------------------|--------------------------------------------------------|
| assembleDebug       | https://github.com/cdsap/Pagan/actions/runs/7509423908 |
| assembleRelease     | https://github.com/cdsap/Pagan/actions/runs/7513465432 |
| testDemoDebug       | https://github.com/cdsap/Pagan/actions/runs/7520610718 |
| lintDemoRelease     | https://github.com/cdsap/Pagan/actions/runs/7516005997 |

Experiments spreadsheet: https://docs.google.com/spreadsheets/d/1wdXYp4ri5XUcBSGNc-ssUpjpcl1e-Pdns4-byNMyvwg/edit?usp=sharing

## Final words
In this article, we explored how using JDK 21 enhances build efficiency in an Android project, particularly noting reduced build times. While some improvements were moderate, the assembleRelease tasks demonstrated notably greater enhancements.
The outcomes may differ based on the specific project, but considering the minimal adjustments needed, it's certainly worthwhile to experiment with this approach if you're utilizing Gradle 8.5.

Happy building!