Introduction
Il est 17h50, un vendredi. Le déploiement de la semaine vient de partir en prod. Et là, une NullPointerException remonte dans les logs, quelque part au milieu d'une chaîne d'appels qu'on avait pourtant relue trois fois. On finit par trouver : un Optional mal géré, un try/catch qui avalait l'exception d'origine sans dire ce qui s'était vraiment passé, et un enchaînement de conditions imbriquées devenu illisible à force d'ajustements successifs.
Rien de dramatique en soi. C'est même un classique. Mais ce genre de situation revient avec une régularité troublante dans les projets Java, et elle a souvent la même origine : on écrit du code impératif, avec des effets de bord et des cas d'erreur traités au coup par coup, sans langage commun pour les exprimer clairement.
C'est précisément le terrain sur lequel Vavr vient jouer.
Pourquoi Vavr
Java a fait beaucoup de chemin depuis la version 8. Les Streams ont changé la manière d'écrire les traitements sur des collections, et Optional a introduit l'idée qu'une valeur absente devait être représentée explicitement plutôt que laissée à la merci d'un null. Ce sont de vraies avancées.
Mais ces outils restent des briques isolées, posées sur un langage qui reste fondamentalement impératif et mutable. Optional ne se compose pas toujours élégamment avec la gestion d'erreurs. Les collections standard restent mutables par défaut. Et en dehors des exceptions, qui cassent le flux du programme et coûtent cher à manipuler proprement, il n'existe pas de représentation native pour dire « soit un résultat, soit une erreur ».
Vavr comble cet espace : une bibliothèque qui apporte à Java les briques fonctionnelles qui lui manquent, avec des types immuables, composables, et une façon d'exprimer les cas d'erreur comme des valeurs plutôt que comme des ruptures de flux.
Les briques centrales
Option : dire clairement qu'une valeur peut manquer
Option de Vavr joue un rôle proche d'Optional, mais avec une API pensée pour la composition et l'immuabilité de bout en bout :
Option<String> nom = Option.of(getNomUtilisateur());
String affichage = nom
.map(String::toUpperCase)
.getOrElse("UTILISATEUR INCONNU");Rien de spectaculaire à première vue. Mais la différence se voit à l'usage : Option s'intègre nativement avec les autres types Vavr, permettant d'enchaîner des transformations sans jamais revenir à des vérifications de nullité manuelles.
Try : encapsuler l'échec sans casser le flux
Try capture le résultat d'une opération qui peut échouer, sans recourir à un try/catch qui interrompt la lecture du code :
Try<Integer> resultat = Try.of(() -> Integer.parseInt(entreeUtilisateur))
.map(valeur -> valeur * 2);
resultat
.onSuccess(v -> System.out.println("Résultat : " + v))
.onFailure(e -> System.out.println("Erreur : " + e.getMessage()));L'échec devient une valeur comme une autre, que l'on peut transformer, transmettre ou traiter plus loin dans le flux, sans perdre l'information d'origine en chemin, contrairement à un catch trop généreux qui l'aurait avalée.
Either : représenter un résultat OU une erreur, explicitement
Là où Try capture une exception, Either va plus loin en modélisant explicitement deux issues possibles, sans dépendre d'une exception levée quelque part :
Either<String, Integer> validation(int age) {
return age >= 18
? Either.right(age)
: Either.left("Âge insuffisant");
}
validation(16)
.map(age -> "Accès autorisé pour " + age)
.getOrElseGet(erreur -> "Refusé : " + erreur);La signature de la méthode dit, à elle seule, ce qu'elle peut renvoyer. Pas besoin de lire la Javadoc ou le corps de la méthode pour savoir qu'un cas d'échec existe : il est là, dans le type de retour.
Collections persistantes : l'immuabilité par défaut
Vavr propose aussi ses propres implémentations de List, Map ou Set, immuables par construction. Toute opération de modification retourne une nouvelle collection plutôt que de modifier l'existante :
List<Integer> nombres = List.of(1, 2, 3);
List<Integer> doubles = nombres.map(n -> n * 2);
// nombres reste inchangée : List(1, 2, 3)Ce détail, presque anodin dans un exemple isolé, change beaucoup de choses à l'échelle d'un projet : plus besoin de se demander si une collection passée en paramètre a été modifiée ailleurs. Elle ne peut tout simplement pas l'être.
Adoption progressive dans un projet existant
Introduire Vavr ne suppose pas de réécrire l'application. Une démarche progressive suffit à en tirer les bénéfices sans prendre de risque :
- Ajouter la dépendance au module concerné, sans l'imposer à l'ensemble du projet dès le départ.
- Choisir une couche isolée pour commencer, typiquement la validation ou le traitement métier, où la gestion d'erreurs explicite apporte le plus de valeur.
- Remplacer les
try/catchles plus problématiques parTry, là où l'information d'erreur se perdait ou où le code devenait difficile à suivre. - Introduire
Eithersur les méthodes dont le contrat métier prévoit un cas d'échec identifié : validation, appel externe, règle de gestion. - Étendre progressivement aux collections immuables, une fois l'équipe à l'aise avec les types fonctionnels de base.
Pas besoin d'attendre une refonte pour commencer : c'est justement en s'insérant dans l'existant, couche par couche, que Vavr démontre le mieux sa valeur.
Un choix qui dépasse la syntaxe
Adopter Vavr revient à choisir un code plus prévisible, où les cas d'erreur sont visibles dans les signatures plutôt que cachés dans des exceptions qui remontent sans prévenir, et où l'immuabilité réduit une classe entière de bugs liés aux effets de bord.
Cette rigueur a un coût d'apprentissage au départ, le temps que l'équipe se familiarise avec ces nouveaux types. Mais elle rejoint une préoccupation plus large : celle d'un code que l'on peut lire, comprendre et faire évoluer en confiance, à plusieurs, sur la durée. C'est précisément ce qui distingue un code qui fonctionne d'un code sur lequel on peut réellement s'appuyer.
Le vendredi soir, revisité
Le bug du vendredi soir, avec son Optional mal géré et son exception avalée en silence, n'avait rien d'une fatalité. Il était le symptôme d'un langage qui, sans bibliothèque comme Vavr, laisse trop de place à l'implicite dans la gestion des cas d'échec.
Vavr n'en fait pas un langage fonctionnel pur pour autant, mais il donne accès aux outils qui, bien utilisés, rendent le code plus explicite, plus composable et plus robuste face à ce genre de vendredi soir.
Continuer la lecture