Riadh Mnasri
← Retour au blog
3 min de lecture

Pourquoi Kotlin reste mon langage JVM de prédilection

Kortex, kotlin-counterparty-risk, kotlin-chess-tournament : trois projets sans rapport apparent, unis par le même choix de langage. Ce n'est pas de la fidélité aveugle à un outil, mais le résultat de quelques caractéristiques précises qui comptent particulièrement dans les domaines où je travaille.

La null-safety comme garde-fou, pas comme contrainte

En finance de marché, une valeur nulle qui traverse un calcul sans être gérée explicitement peut coûter cher : un NullPointerException en pleine nuit de calcul batch sur l'ensemble d'un portefeuille n'est pas une abstraction. Le système de types de Kotlin, avec sa distinction stricte entre types nullables et non-nullables, déplace cette classe entière de bugs du runtime vers la compilation. Ce n'est pas un confort de développeur, c'est une réduction mesurable du risque opérationnel.

// collateral.amount ne compile pas : collateral est nullable (Collateral?),
// et .amount n'est accessible directement que sur un type non-nullable.

fun calculateExposure(collateral: Collateral?): BigDecimal {
    return collateral?.amount ?: BigDecimal.ZERO
}

Ce deuxième appel n'est pas juste plus sûr : il rend visible, dans la signature même de la fonction, qu'un collatéral absent est un cas prévu et géré, pas un oubli.

Les data classes et la modélisation du domaine

Modéliser un domaine métier riche (les règles d'un tournoi d'échecs, les invariants d'un calcul de risque) demande des types qui portent du sens, pas juste des structures de données. Les data classes de Kotlin, avec l'égalité structurelle et la copie immuable (copy()) intégrées, rendent naturel ce que d'autres langages demandent d'implémenter à la main. Sur kotlin-chess-tournament, chaque état de tournoi est un objet immuable : impossible de le modifier accidentellement depuis un autre point du code, ce qui élimine une catégorie entière de bugs de synchronisation d'état.

data class TournamentState(
    val round: Int,
    val standings: List<PlayerScore>,
)

val nextRound = currentState.copy(round = currentState.round + 1)
// currentState reste inchangé : copy() produit un nouvel objet,
// currentState et nextRound ne partagent aucune référence mutable.

Sans cette garantie, il faudrait écrire à la main l'égalité, le hashcode et la logique de copie immuable, avec le risque d'oublier un champ à chaque évolution de la classe.

AspectJavaKotlin
Valeurs nullesOptional opt-in, NPE toujours possibleNullable/non-nullable dans le système de types
Classes de donnéesBoilerplate manuel ou Lombokdata class : equals/hashCode/copy() générés
Cas finis (sum types)switch sur enum, pas exhaustif par défautsealed class + when exhaustif vérifié à la compilation
Interop avec l'existant JavaDirecte, sans wrapper ni FFI

L'interopérabilité Java, un pont plutôt qu'un mur

Travaillant en environnement bancaire, où l'écosystème Java reste dominant, la capacité de Kotlin à s'interfacer sans friction avec du code Java existant n'est pas un détail technique : c'est ce qui rend le langage adoptable progressivement, module par module, plutôt que par une réécriture risquée. Cette compatibilité change littéralement la décision d'introduire Kotlin sur une mission : le coût d'entrée est proche de zéro.

Ce que je ne prétends pas

Kotlin n'est pas magique et ne remplace pas la rigueur d'architecture : sur counterparty-risk-lab, j'applique la même discipline hexagonale en TypeScript. Le choix du langage ne dispense jamais de la discipline de conception. Mais à discipline égale, Kotlin élimine par construction des catégories de bugs que d'autres langages laissent à la vigilance du développeur, et c'est cette élimination structurelle, plus que la syntaxe elle-même, qui explique pourquoi j'y reviens systématiquement pour mes projets JVM.