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).
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
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?