For the complete documentation index, see llms.txt. This page is also available as Markdown.

j11-to-j21

Overview

This page documents the sequence of changes performed to migrate the project's Android modules from Java 11 to Java 21. Each step covers a specific configuration, dependency, or code change required to bring the build, tests, and runtime into compatibility with JDK 21 and Android Gradle Plugin 8.x.

Step 1 — Upgrade JDK

Install JDK 21 and update environment variables.

Windows — System Environment Variables

JAVA_HOME = C:\Program Files\Java\jdk-21.x.x-hotspot
PATH      = %JAVA_HOME%\bin;%PATH%

# Verify
java -version
# openjdk version "21.x.x"

In Android Studio: File > Settings > Build, Execution, Deployment > Build Tools > Gradle > Gradle JDK → set to JDK 21.


Step 2 — Upgrade Gradle and AGP

android/gradle/wrapper/gradle-wrapper.properties

Before

properties

After

properties

android/build.gradle

Before

After


Step 3 — Update compileSdkVersion and targetSdkVersion

In android/build.gradle (root ext block):

Before

After


Step 4 — Set Java 21 compileOptions in All Modules

Applied to clientmanager, keymanager, packetmanager, transliterationmanager, and the app module.

Before

After


Step 5 — Add Namespace to build.gradle (AGP 8.x Requirement)

ℹ️ Note — AGP 8.x rejects the package= attribute in AndroidManifest.xml. The package identifier must be declared as namespace inside build.gradle.

AndroidManifest.xml — remove the package attribute

Before

After

build.gradle — add the namespace

Applied to all four library modules and the app module.

Auto-fallback for third-party Flutter plugins

Added to the root build.gradle:


Step 6 — Enable BuildConfig Generation (AGP 8.x)

AGP 8.x disables BuildConfig class generation by default. It must be explicitly enabled in any module that references BuildConfig.* constants.

Applied to clientmanager/build.gradle, keymanager/build.gradle, and app/build.gradle:

Without this, any class referencing BuildConfig.BASE_URL, BuildConfig.CLIENT_VERSION, etc. fails to compile.


Step 7 — Enable Core Library Desugaring (app module)

In android/app/build.gradle:

Java 21 lets us use modern APIs like java.time.* and streams, but older Android devices (below API 26) don't have these classes built in. Desugaring backports them into the APK so the app runs everywhere.

It is also required because BouncyCastle 1.78.1 (Step 7) and Jackson's JavaTimeModule (Step 8) internally use these modern APIs. Version 2.0.4 is used because AGP 8.x requires it.


Step 8 — Upgrade BouncyCastle (keymanager)

⚠️ Warning — The bcprov-jdk15on and spongycastle artifacts are not published for JDK 18+.

keymanager/build.gradle

Before

After

Update imports in LocalClientCryptoServiceImpl.java

Before

java

After

java

Replace PEMWriter with JcaPEMWriter in CertificateManagerUtil.java

PEMWriter was removed in bcpkix-jdk18on.

Before

java

After

java


Step 9 — Remove jackson-module-afterburner (keymanager)

jackson-module-afterburner uses bytecode generation that is incompatible with JDK 21. It was removed from dependencies and from JsonUtils.java.

JsonUtils.java

Before

java

After

java


Step 10 — Upgrade Jackson, Mockito, Lombok, Robolectric and Biomterics-utils

Updated in all module build.gradle files (versions centralized in the root ext {} block).

Dependency

Before

After

jackson-core / databind / annotations

2.x

2.16.2

mockito-core

4.x

5.3.1

mockito-android

4.x

5.3.1

mockito-inline

present

removed (built into mockito-core 5.x)

Lombok

1.18.x

1.18.32

Robolectric

4.x

4.13

Jacoco

< 0.8.9

0.8.12

biometrics-util

1.2.0.2

1.3.0

ℹ️ Info — Remove the separate mockito-inline dependency — Mockito 5.x includes inline mocking in mockito-core by default. No code change is required for MockedStatic usage.


Step 11 — Add Jetifier Ignorelist

Jetifier was causing issues processing jackson-core and fastdoubleparser artifacts after the Jackson upgrade.

Added to android/gradle.properties:

properties

This tells the Jetifier tool to skip these artifacts, avoiding the conversion errors.


Step 12 — Add JVM Arguments for Test Modules

Required for Mockito and byte-buddy to access JDK internal APIs under JPMS restrictions.

Added to each module's build.gradle:


Step 13 — Fix Kotlin JVM Target Mismatch for Flutter Plugins

Third-party Flutter plugins declare sourceCompatibility = 1.8, but Kotlin 1.9.x on JDK 21 defaults jvmTarget to 21, causing a build warning or error.

Added to the root build.gradle:

Added to android/gradle.properties:

properties


Step 14 — Disable Unit Tests for Broken Third-Party Flutter Plugins

These plugins use Mockito without the required JVM args, and their source cannot be modified.

Added to the root build.gradle:


Step 15 — Patch flutter_config Manifest (AGP 8.x)

flutter_config 2.0.2 still uses package= in its AndroidManifest.xml, which AGP 8.3+ rejects. Since the source cannot be modified, it is patched at build time.

Added to the root build.gradle:


Step 16 — Validate the Build

Run the following commands to validate the migration.

Test each module individually

powershell

Full project build

powershell

Generate APK

powershell

Clean and refresh Flutter dependencies (from the project root)

powershell

Build a debug APK

powershell

Build a release APK

powershell

The generated APK is written to build/app/outputs/flutter-apk/.


Migration Summary

Area

Change

Toolchain

JDK 11 → JDK 21, Gradle 7.5 → 8.5, AGP 7.x → 8.3.2

Android SDK

compileSdk/targetSdk 32 → 34, buildTools 34.0.0

Manifest

Migrated package attribute to namespace in build.gradle

App module

Enabled core library desugaring with desugar_jdk_libs 2.0.4

Security

BouncyCastle jdk15on / SpongyCastle → bcprov/bcpkix-jdk18on 1.78.1

JSON

Removed jackson-module-afterburner; upgraded Jackson to 2.16.2

Testing

Mockito 5.3.1, Robolectric 4.13, Lombok 1.18.32, Jacoco 0.8.12

JVM args

Added --add-opens and dynamic agent loading for tests

Flutter plugins

Kotlin jvmTarget alignment, manifest patches, broken tests disabled

Last updated

Was this helpful?