1. Couches de versions dans un projet Kotlin
Quand vous entendez « mettons Kotlin a jour en 2.x », on a tres envie d’imaginer un seul bouton “Kotlin version” que l’on tourne — et tout devient moderne et joli. En pratique, Kotlin vit dans un projet a plusieurs couches a la fois : le compilateur, le plugin Gradle, la bibliotheque standard, les dependances et l’IDE. Et n’importe quelle couche peut casser ; parfois, elle casse meme tres poliment : elle se met juste a se plaindre via des avertissements que vous ignorerez, et dans six mois ils deviendront des erreurs.
Pour ne pas se meler, etablissons une « carte du terrain » claire : quelle version vit ou, et de quoi elle est responsable. Le plus utile est d’apprendre a separer : la version du langage (quelle syntaxe et quelles regles sont disponibles), la version du compilateur (comment c’est analyse et en quoi c’est transforme), et la version des outils de build (comment le projet est compile dans CI et sur votre machine).
Tableau : quelles versions existent dans un projet Kotlin
| Couche | Ce que c’est en langage humain | Ou c’est generalement configure | Symptomes typiques de problemes |
|---|---|---|---|
| Version de Kotlin Gradle Plugin (KGP) | Le plugin qui ajoute des taches de compilation Kotlin dans Gradle | build.gradle.kts (plugins { kotlin("jvm") version "…" }) | Le build ne demarre pas, des taches Gradle echouent, erreurs de configuration etranges |
| Version du compilateur Kotlin | Ce qui compile reellement le code (y compris le frontend K2 dans Kotlin 2.x) | Arrive avec KGP | Du code qui “compilait hier” ne compile plus aujourd’hui, la diagnostique change |
| Language/API version | Les “regles du langage” et le “niveau d’API” avec lequel vous etes compatible | compilerOptions { languageVersion… apiVersion… } | Interdictions/autorisations inattendues de fonctionnalites ; attentes divergentes entre modules |
| Bibliotheque standard (stdlib) | Fonctions/types de base de Kotlin | Generalement tiree automatiquement par KGP, mais peut etre figee via une dependance | Conflits de versions, NoSuchMethodError au demarrage (rare, mais ca arrive) |
| Plugins et generation de code (KSP, etc.) | Outils qui participent a la compilation | Plugins Gradle + dependances | La generation ne marche plus, “version incompatible”, echec a l’etape de build |
| IDE + plugin Kotlin dans l’IDE | Ce qui surligne le code et lance le build depuis un bouton | IntelliJ IDEA | « Rouge dans l’IDE, mais Gradle build » ou l’inverse |
Ce tableau est comme une trousse de secours : il peut paraitre ennuyeux, mais au moment de la douleur il devient soudain votre prefere.
2. K2, languageVersion et compatibilite des mises a jour
Qu’est-ce que K2, et pourquoi le code devient “rouge” apres une mise a jour
Kotlin 2.x, ce n’est pas seulement de nouvelles possibilites du langage, mais aussi un gros changement interne : un nouveau frontend du compilateur est apparu (on l’appelle souvent K2). Pour simplifier a l’extreme, le frontend est la partie du compilateur qui lit votre code, en comprend le sens, construit une representation interne et decide : « ca, c’est autorise, et ca, non ». Quand le frontend change, les diagnostics changent aussi, ainsi que l’interpretation de certains cas “aux angles” — parfois votre code n’est pas devenu pire, c’est juste que le compilateur est devenu plus strict et plus previsible.
Une idee importante pour les debutants (et pour les veterans fatigues aussi) : si, apres une mise a jour de Kotlin, vous voyez apparaitre de nouvelles erreurs, ce n’est pas forcement qu’“ils ont tout casse”. Parfois, c’est juste que le compilateur ne tolere plus ce qu’il avalait auparavant en silence.
Exemple : open val et initialisation dans init
Voici un exemple parlant issu d’une migration vers Kotlin 2.0 : auparavant, certaines variantes d’initialisation differee “passaient”, maintenant — non. En Kotlin 2.0, les proprietes open avec backing field doivent etre initialisees immediatement, sinon le compilateur se plaint.
open class Base {
open val a: Int
open var b: Int
init {
this.a = 1 // En Kotlin 2.0 : erreur, open val doit avoir un initializer
this.b = 1 // Et ca a toujours ete une erreur pour open var
}
}
class Derived : Base() {
override val a: Int = 2
override var b: Int = 2
}
Pourquoi est-ce important dans une lecon sur les « versions » ? Parce que c’est un cas classique : vous mettez Kotlin a jour — et soudain, du code qu’on n’a pas touche depuis longtemps “casse”. En realite, c’est juste l’interpretation d’une regle qui a change, et le projet exige maintenant des garanties plus explicites.
Comment definir les regles du langage via languageVersion
Quand un projet devient plus qu’un seul dossier “avec des exercices”, vous commencez a vivre dans une realite ou une partie du code est ecrite “comme on a l’habitude”, une partie — “comme c’est a la mode”, et une partie — “comme l’IDE l’a suggere a 2 heures du matin”. Et a ce moment-la, il est crucial de savoir piloter les regles de compilation : parfois vous voulez mettre a jour le plugin Gradle et les outils, mais conserver temporairement les anciennes regles du langage pour que la migration se fasse plus en douceur. Ou a l’inverse : activer des regles plus recentes la ou tout est deja pret.
Idee cle : la version du plugin Kotlin et la version des regles du langage ne sont pas la meme chose. Oui, le plus souvent elles vont de pair, mais il est parfois utile de “figer” temporairement languageVersion.
Exemple : activer temporairement les regles 1.9
Dans le guide officiel de migration, il y a une recette explicite : pour utiliser le compilateur precedent en Kotlin 2.0.0+, vous pouvez definir languageVersion a 1.9 ou passer -language-version 1.9.
Le but de cette etape n’est pas de “rester en 1.9 pour toujours”. Le but est de decouper le probleme en morceaux : d’abord mettre a jour les outils (Gradle, plugins), puis activer les nouvelles regles du langage et traiter les erreurs.
Ci-dessous un exemple de ce que cela peut donner dans build.gradle.kts (simplifie : l’idee est plus importante que l’emplacement exact dans le fichier) :
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
tasks.withType<KotlinCompile>().configureEach {
compilerOptions {
languageVersion.set(KotlinVersion.KOTLIN_1_9)
apiVersion.set(KotlinVersion.KOTLIN_1_9)
}
}
Un tel interrupteur, c’est comme un mode “roue de secours temporaire”. Ce n’est pas tres rapide, mais ca permet d’arriver au garage au lieu de reparer au bord de l’autoroute.
Pourquoi Kotlin 2.x entraine souvent un « train » de dependances
Il y a deux types de mises a jour. Le premier type — “j’ai change un chiffre a un endroit et tout s’est compile”. Le deuxieme type — “j’ai change un chiffre, et j’ai soudain appris de nouveaux mots : ABI, frontend, binary compatibility, plugin API”. Kotlin 2.x appartient plus souvent au deuxieme type, surtout si le projet utilise KSP, kotlinx.serialization ou d’autres outils lies a la compilation.
L’habitude la plus importante a developper : si une dependance participe a la compilation (et pas seulement au runtime), elle est plus sensible a la version de Kotlin. Cela concerne la generation de code, les plugins de compilateur, et meme la facon dont l’IDE analyse le projet.
Schema des dependances de versions
flowchart TD
A["Version de Kotlin Gradle Plugin (KGP)"] --> B["Version du compilateur Kotlin (K2/K1)"]
A --> C["Taches Gradle : compileKotlin, test, etc."]
B --> D["Regles du langage (languageVersion/apiVersion)"]
B --> E["Outils de compilation : KSP/plugins"]
E --> F["Fichiers .kt generes"]
F --> C
L’idee est simple : si vous avez mis Kotlin a jour, mais oublie que vous avez KSP qui genere du code, alors vous avez mis a jour le “moteur”, mais garde l’ancien “carburateur”. La machine (le build) peut commencer a tousser.
Principe pratique de compatibilite
Si on le formule de la maniere la plus humaine possible, la regle est la suivante : mettez a jour de facon coherente tout ce qui est lie a la compilation. Si vous mettez Kotlin a jour, regardez :
- le plugin Kotlin pour Gradle (c’est lui qui apporte le compilateur) ;
- KSP (s’il y en a) ;
- les plugins de serialisation et autres outils compile-time (s’il y en a) ;
- et seulement ensuite — les bibliotheques ordinaires.
Ici, l’important n’est pas de “connaitre toutes les versions par coeur”, mais d’avoir une discipline : vous mettez a jour une couche — vous verifiez le build, vous figez le resultat, puis vous avancez.
3. Discipline des mises a jour et avertissements du compilateur
Scenario de mise a jour pas a pas sans « tout brule »
A un moment, vous commencerez a mettre Kotlin a jour non pas parce que “vous voulez du neuf”, mais parce que le projet a besoin d’un patch de securite, ou qu’une bibliotheque ne supporte plus une ancienne version, ou qu’un collegue a envoye une PR avec les mots “chez moi ca marche” (ce qui est un antique sortilege du chaos). Et ici, ne gagnent pas ceux qui changent les chiffres le plus vite, mais ceux qui rendent les mises a jour previsibles.
La suite ne sera pas “le process ideal en 40 pages”, mais quelque chose de realiste pour un petit projet educatif ou un pet-project. Et ce qui est agreable, c’est que ca se met a l’echelle : les memes idees fonctionnent aussi dans de grandes equipes.
D’abord, vous creez une branche separee. C’est banal, mais c’est le moment ou vous vous achetez le droit a l’erreur. Ensuite, vous mettez a jour le Kotlin Gradle plugin (en general, c’est une ligne de version dans plugins { … }). Apres cela, vous lancez immediatement le build via Gradle (pas uniquement via l’IDE), parce que l’IDE vit parfois “dans son propre monde”, alors que Gradle est le monde ou vit CI.
Si le build echoue, vous ne vous mettez pas a tout reparer au hasard. Vous regardez : est-ce une erreur de configuration Gradle (le script de build ne s’est pas compile), une erreur de compilation Kotlin (le code ne compile pas), ou un echec de tests (le code compile, mais le comportement a change). Ce sont trois types de problemes differents, et il est utile de les separer au moins dans sa tete.
Ensuite, vous n’activez pas les nouvelles regles du langage tout de suite : vous le faites consciemment. Si, en passant a Kotlin 2.x, le projet en mode K2 commence a se plaindre a beaucoup d’endroits, vous pouvez temporairement fixer languageVersion a 1.9, compiler le projet, puis revenir a 2.x et corriger deja des problemes de langage “purs”, plutot que langage + build en meme temps. Le fait meme de pouvoir revenir en arriere est decrit dans le guide de migration.
Enfin, vous figez les changements en petits commits. Ce n’est pas de la bureaucratie, c’est une aide pour votre futur vous : “mise a jour de Kotlin”, “corrige X”, “corrige Y”. Ainsi, le rollback et l’analyse deviennent simples.
Pourquoi, en Kotlin 2.x, il vaut mieux ne pas « etouffer » les avertissements
Le compilateur a deux modes de communication avec vous : “tout va bien” et “je t’avais prevenu”. Les avertissements, surtout ceux de deprecation, sont souvent percus comme du bruit : ils genent, soulignent le code, gachent l’humeur. Mais en realite, c’est l’un des moyens les moins chers de connaitre a l’avance des problemes futurs.
Kotlin et son ecosysteme transforment regulierement des avertissements en erreurs au fil de l’evolution du langage. Ainsi, l’habitude “oh, c’est pas grave, plus tard” se transforme parfois en “pourquoi le CI est rouge alors qu’on n’a rien change ?”. La reponse est souvent : vous n’avez pas change le code, mais le monde autour (les versions) a change les regles.
Une strategie pratique ici sonne ennuyeuse, mais fonctionne : si un avertissement peut etre corrige en 5–10 minutes sans perte de sens — corrigez-le. Si ce n’est pas possible, prenez au moins une decision consciente : laissez un commentaire, creez une tache, ou appliquez une suppression (@Suppress(...)) de facon la plus locale possible. Sinon, dans six mois, vous regarderez les suppressions comme un archeologue : “qui a fait ca, et pourquoi y a-t-il un mammouth enterre ici ?”.
4. Migration de sa propre API via @Deprecated
Quand vous mettez Kotlin a jour, une deuxieme couche de travail remonte presque toujours : votre propre code evolue aussi. Vous renommez des fonctions, changez des contrats, enlevez des “rustines”, ameliorez le design de l’API. Et ici, il est facile de faire mal aux utilisateurs de votre code (ou a vous-meme dans un mois) : vous avez renomme une methode — et toute l’application s’ecroule.
Kotlin offre un mecanisme tres pratique de migration douce : @Deprecated. C’est litteralement un panneau “travaux, passez par la nouvelle rue”. Et le plus agreable : l’IDE sait suggerer automatiquement un remplacement via ReplaceWith.
Exemple : migration d’une fonction via ReplaceWith
Imaginons que, dans notre application console d’apprentissage (appelons-la conditionnellement StudyTracker), il y avait une fonction formatMoneyOld(...), et que nous voulons la remplacer par formatMoney(...), parce que l’ancien nom sonne comme un aveu de peches.
@Deprecated(
message = "Utilisez formatMoney(amountCents)",
replaceWith = ReplaceWith("formatMoney(amountCents)")
)
fun formatMoneyOld(amountCents: Int): String = formatMoney(amountCents)
fun formatMoney(amountCents: Int): String {
val dollars = amountCents / 100
val cents = amountCents % 100
return "$dollars.${cents.toString().padStart(2, '0')}"
}
Ici, l’important est que nous n’avons pas supprime l’ancienne fonction instantanement : nous en avons fait un fin wrapper, pour que le projet continue a compiler, et pour que l’IDE aide a reecrire les appels.
Exemple : migration d’une propriete via delegation
Parfois, vous ne renommez pas une fonction, mais une propriete. Kotlin permet de deleguer une propriete a une autre, et c’est meme recommande comme scenario de renommage compatible : on introduit une nouvelle propriete, on marque l’ancienne avec @Deprecated et on la delegue vers la nouvelle.
class Settings {
var currencyCode: String = "USD"
@Deprecated("Use 'currencyCode' instead", ReplaceWith("currencyCode"))
var currency: String by this::currencyCode
}
fun main() {
val s = Settings()
s.currency = "EUR"
println(s.currencyCode) // EUR
}
Cela ressemble presque a de la magie, mais le sens est tres simple : l’ancien nom continue de vivre comme un “alias”, tandis que les donnees sont en realite stockees au nouvel endroit.
5. Erreurs typiques lors de la mise a jour vers Kotlin 2.x
Erreur n°1 : mettre Kotlin a jour et encore vingt dependances “au passage”, puis chercher la source de la casse.
On a tres envie de le faire, parce que “tant qu’a souffrir — autant y aller a fond”. Mais cela rend le diagnostic presque impossible : vous ne savez pas si ca a casse a cause de Kotlin, de Gradle, d’un plugin ou d’une bibliotheque. Il est bien plus stable de mettre a jour par couches et de figer de petits pas.
Erreur n°2 : confondre “la version de Kotlin” et “la version de languageVersion”.
La version du Kotlin Gradle plugin apporte le compilateur, mais les regles du langage peuvent etre figees temporairement. Le guide de migration mentionne explicitement l’option de retour en languageVersion 1.9 ou -language-version 1.9 pour utiliser le compilateur precedent. C’est utile comme mesure temporaire, mais il est important de se souvenir que c’est un pont provisoire, pas une “nouvelle norme”.
Erreur n°3 : ignorer les changements de semantique et de regles que le compilateur verifie plus strictement.
En Kotlin 2.x, vous pouvez obtenir soudainement une erreur la ou, auparavant, le code etait “gris”. L’exemple avec open val, qui pouvait auparavant etre initialise dans init, mais exige maintenant un initializer, illustre bien la nature de ces changements. Ici, il est inutile de se facher contre le compilateur — mieux vaut accepter que le projet a besoin de garanties plus explicites.
Erreur n°4 : casser son API “brutalement”, sans migration douce.
Supprimer une ancienne fonction, renommer une propriete et esperer “je vais tout trouver avec la recherche” — ca marche jusqu’au premier endroit manque. Kotlin offre un excellent mecanisme de migration via @Deprecated et meme via la delegation de l’ancienne propriete vers la nouvelle. Si vous ne l’utilisez pas, vous renoncez volontairement a une aide gratuite de l’IDE et du compilateur.
Erreur n°5 : considerer les avertissements comme des dechets et les etouffer globalement.
Les avertissements sont souvent une “version precoce d’une erreur venue du futur”. Si on les supprime sans reflechir, le projet devient fragile : il compile “maintenant”, mais n’explique pas ce qu’il faut corriger avant la prochaine mise a jour. Il vaut mieux soit corriger, soit documenter pourquoi la suppression est justifiee, et la garder locale.
GO TO FULL VERSION