Kotlin 2.2 : ce qui change vraiment (et comment migrer sans douleur)

Kotlin 2.2 est une version “charnière” : elle stabilise plusieurs features de langage introduites en preview auparavant, et apporte des changements importants côté JVM/Gradle, ainsi que de nouvelles APIs standard (Base64, HexFormat).

Cet article se concentre sur les nouveautés qui ont un impact concret sur ton code et ta build : langage (stable + preview), compilateur, JVM, Gradle, stdlib, et points de migration.


1) Mettre à jour vers Kotlin 2.2 (Gradle)

1.1 Kotlin Gradle Plugin

// build.gradle.kts
plugins {
    kotlin("jvm") version "2.2.0"
    // ou kotlin("android") version "2.2.0"
}

Ensuite, synchronise ton projet et corrige les warnings/erreurs de compilation.

1.2 Bon réflexe : compilerOptions centralisées

// build.gradle.kts
kotlin {
    compilerOptions {
        // Exemple : future-proofing, options de compilation ici
    }
}

2) Langage : ce qui devient Stable (utilisable “sans drapeau”)

Kotlin 2.2 stabilise notamment :

  • Guard conditions dans les when
  • Non-local break / continue
  • Multi-dollar interpolation (meilleure gestion de $ dans les strings)

2.1 Guard conditions dans when : rendre les branches plus expressives

Avant, on finissait souvent par imbriquer des if dans les branches, ou dupliquer des conditions. Les guard conditions permettent d’exprimer “même type + condition supplémentaire” directement dans le when.


sealed interface Payment
data class Card(val last4: String, val isExpired: Boolean) : Payment
data object Cash : Payment

fun label(p: Payment): String = when (p) {
    is Card if p.isExpired -> "Card (expired)"
    is Card -> "Card (**** ${p.last4})"
    Cash -> "Cash"
}

Avantage : un when plus lisible, plus proche de la logique métier.

2.2 Non-local break/continue : contrôler une boucle depuis un lambda

Certains lambdas inline (ex : forEach) pouvaient rendre les break/continue difficiles. Kotlin 2.2 stabilise de nouveaux comportements permettant des contrôles plus naturels dans certains cas.


fun findFirstEven(numbers: List<Int>): Int? {
    var result: Int? = null
    run loop@{
        numbers.forEach { n ->
            if (n % 2 == 0) {
                result = n
                return@loop // sort du bloc, “comme un break” intentionnel
            }
        }
    }
    return result
}

Idée : clarifier la sortie / continuation d’un flux quand on utilise des fonctions d’ordre supérieur.

2.3 Multi-dollar interpolation : écrire des strings avec $ sans contorsions

Très utile quand tu manipules des templates (SQL, JSON, GraphQL, shell, regex, etc.) où les $ sont fréquents.


val user = "alice"
val query = $$"""
  query User($$id: ID!) {
    user(id: "$user") { id name }
  }
"""

3) Langage : features en preview (à activer)

Kotlin 2.2 introduit en preview :

  • Context parameters (remplace context receivers)
  • Context-sensitive resolution (moins de répétition dans certains contexts)

3.1 Context parameters : injecter un “contexte” sans tout passer en paramètre

But : éviter de propager partout des dépendances “globales” (logger, services, configuration), tout en restant explicite.


// Exemple simplifié

interface UserService {
    fun log(message: String)
    fun findUserById(id: Int): String
}

context(users: UserService)
fun outputMessage(message: String) {
    users.log("Log: $message")
}

context(users: UserService)
val firstUser: String
    get() = users.findUserById(1)

Pour activer la preview :


// build.gradle.kts
kotlin {
    compilerOptions {
        freeCompilerArgs.add("-Xcontext-parameters")
    }
}

Important : c’est une preview, donc à utiliser avec prudence en production (API susceptible d’évoluer).

3.2 Context-sensitive resolution : moins de répétition dans un when

Objectif : quand le type est déjà “évident” via le contexte, éviter de répéter le nom du type.


enum class Problem { CONNECTION, AUTHENTICATION, DATABASE, UNKNOWN }

fun message(problem: Problem): String = when (problem) {
    CONNECTION -> "connection"
    AUTHENTICATION -> "authentication"
    DATABASE -> "database"
    UNKNOWN -> "unknown"
}

C’est une amélioration de lisibilité, surtout sur des sealed / enum très utilisés.


4) Compilateur : gestion unifiée des warnings

Kotlin 2.2 introduit une option pour gérer plus finement les warnings (niveau, exclusions, etc.). Si tu gères une grosse base de code, ça aide à monter progressivement le niveau de qualité sans “casser” la build d’un coup.

Concrètement, l’objectif est de pouvoir dire :

  • tel warning = error
  • tel warning = disabled
  • tel warning = warning

5) Kotlin/JVM : changement important sur les méthodes par défaut d’interface

À partir de Kotlin 2.2.0, les fonctions avec implémentation dans une interface sont compilées en JVM default methods par défaut, sauf configuration contraire. Cela peut impacter la compatibilité binaire et certains cas d’interop Java.

Kotlin fournit une option stable -jvm-default (qui remplace l’ancienne option expérimentale). Modes typiques :

  • enable (par défaut) : compatibilité + bridges si nécessaire
  • no-compatibility : nouveau code, moins de “bridges”
  • disable : comportement proche d’avant (DefaultImpls)

// build.gradle.kts (exemple)
kotlin {
    compilerOptions {
        // Exemple illustratif : choisir un mode
        // jvmDefault = JvmDefaultMode.NO_COMPATIBILITY
    }
}

Si tu as une base mix Kotlin/Java ou des libs publiées, lis bien la doc de compatibilité et teste la compilation côté Java.


6) Gradle : validation de compatibilité binaire intégrée

Kotlin Gradle Plugin intègre des mécanismes de validation de compatibilité binaire. Si tu maintiens une librairie (API publique), c’est un gros plus pour éviter des breaking changes involontaires.

Idée : détecter plus tôt quand une modification casse l’API attendue par des consommateurs.


7) Standard library : Base64 et HexFormat deviennent stables

7.1 Base64 (stable)

Kotlin standardise enfin des APIs Base64 : moins besoin d’ajouter des libs utilitaires juste pour encoder/décoder.


import kotlin.io.encoding.Base64

val bytes = "hello".encodeToByteArray()
val encoded = Base64.encode(bytes)
val decoded = Base64.decode(encoded)

7.2 HexFormat (stable)

Pour parser/formatter de l’hexadécimal proprement.


val hex = 93.toHexString() // "5d" (selon format)

8) Kotlin/Native, Wasm, JS : points à retenir

Kotlin 2.2 continue de renforcer l’écosystème multiplateforme :

  • Native : évolution toolchain (LLVM 19) et outils pour suivre/ajuster la mémoire
  • Wasm : séparation du target Wasm et options de configuration
  • JS : correctifs d’interop / génération

Si tu fais du KMP, lis les notes de version dédiées et vérifie tes plugins (Compose, serialization, etc.).


9) Migration : points d’attention (compatibilité)

Deux catégories de problèmes arrivent souvent lors d’un upgrade :

  • erreurs source : ton code n’est plus accepté (règles plus strictes)
  • compat binaire : ton code compile, mais un consommateur (Java/lib) casse

Exemples de changements signalés dans le guide de compatibilité 2.2.x :

  • des comportements auparavant “tolérés” deviennent warnings puis erreurs,
  • la génération JVM des interfaces peut impacter l’interop Java,
  • certaines syntaxes ou usages sans effet réel sont désormais interdits.

Méthode recommandée :

  1. upgrade Kotlin
  2. corrige les warnings
  3. lance la CI (tests + lint)
  4. si librairie : vérifie compat binaire + consumers

Conclusion

Kotlin 2.2 apporte :

  • des features de langage plus expressives (guard conditions, interpolation améliorée),
  • des previews ambitieuses (context parameters, résolution contextuelle),
  • des changements JVM structurants (-jvm-default),
  • des outils de build/qualité plus solides,
  • des APIs stdlib très pratiques (Base64, HexFormat).

Si tu migres une base existante : traite la migration comme un mini-projet, lis le guide de compatibilité, et teste en particulier l’interop Java et tes bibliothèques publiées.