Une méthode qui peut ne rien trouver ne devrait pas laisser un null se promener discrètement dans toute l’application. En Java, la classe Optional rend cette absence explicite et aide à construire des traitements plus sûrs, notamment dans les services web, les accès aux bases de données et les API. Je vous montre comment la créer, la lire, la transformer et surtout quand éviter de l’utiliser.
L’essentiel pour utiliser Optional sans alourdir votre code
-
Optional
représente une valeur présente ou absente. -
ofNullable() convient lorsqu’une source peut déjà renvoyer
null. - orElseGet() évite de calculer inutilement une valeur par défaut.
- map() transforme une valeur, tandis que flatMap() évite les Optional imbriqués.
- get() est rarement un bon choix, car il peut lever une exception.

Comprendre ce que représente Optional en Java
Optional est un conteneur qui peut contenir une valeur non nulle ou être vide. Il ne remplace pas tous les usages de null, mais il permet à une méthode d’indiquer clairement qu’un résultat peut être absent. Pour moi, c’est surtout un contrat lisible entre une méthode et son appelant.
Une méthode comme findById() ne promet pas de trouver un utilisateur. Avec Optional, cette possibilité devient visible dans sa signature, ce qui pousse le développeur à traiter les deux cas au lieu de supposer que la valeur existe toujours.
public Optional trouverParId(long id) {
// résultat présent ou Optional.empty()
}
La classe est apparue avec Java 8 et se trouve dans le package java.util. La documentation Java la destine principalement aux valeurs de retour lorsqu’un résultat peut manquer, et non au stockage systématique dans les champs ou aux paramètres de méthode.
Créer un Optional correctement
Quand la valeur est obligatoirement présente
Optional.of() convient lorsque vous êtes certain que la valeur n’est pas nulle. Si cette hypothèse est fausse, Java lève immédiatement une NullPointerException. Ce comportement est utile pour détecter une incohérence tôt, mais il ne faut pas l’utiliser avec une donnée provenant d’une base ou d’une requête utilisateur sans vérification.
Optional nom = Optional.of("Camille");
Quand la valeur peut être nulle
Optional.ofNullable() convertit une valeur existante en conteneur rempli, ou crée un conteneur vide si la valeur vaut null. C’est généralement le bon choix pour adapter une ancienne API, un résultat JDBC ou une propriété dont la présence n’est pas garantie.
String valeur = lireDepuisUneSource();
Optional resultat = Optional.ofNullable(valeur);
Représenter explicitement l’absence
Pour renvoyer une absence, utilisez Optional.empty(). Je préfère cette forme à un null retourné par une méthode, car l’appelant sait immédiatement quelle stratégie appliquer et le risque d’un oubli diminue fortement.
return Optional.empty();
Une règle simple évite beaucoup de problèmes. Ne retournez jamais null à la place d’un Optional, et n’écrivez pas Optional.of(null). Si la donnée peut manquer, utilisez ofNullable() ou empty().
Lire la valeur sans recréer les pièges de null
La méthode isPresent() indique si une valeur existe, tandis que isEmpty(), disponible depuis Java 11, teste l’absence. Ces méthodes sont utiles dans certains cas, mais un enchaînement de conditions qui ressemble à l’ancien code avec null perd une partie de l’intérêt d’Optional.
if (utilisateur.isPresent()) {
envoyerEmail(utilisateur.get());
}
Dans ce cas, ifPresent() est souvent plus expressif. L’action ne s’exécute que si la valeur est là, sans appel direct à get().
utilisateur.ifPresent(this::envoyerEmail);
Lorsque les deux branches doivent être traitées, ifPresentOrElse() permet de conserver une logique claire. Cette méthode est particulièrement pratique pour journaliser l’absence d’une ressource ou renvoyer une réponse adaptée dans un contrôleur.
utilisateur.ifPresentOrElse(
this::afficherProfil,
() -> afficherMessage("Utilisateur introuvable")
);
Lire aussi : À quoi sert Java ? Usages réels et pertinence en 2026
Choisir une valeur de remplacement
orElse() renvoie la valeur contenue ou une valeur de secours. Attention, son argument est évalué immédiatement, même lorsque l’Optional contient déjà une valeur. Si le remplacement déclenche un appel réseau, une requête SQL ou un calcul coûteux, cette différence peut devenir mesurable.
String nom = utilisateur
.map(Utilisateur::getNom)
.orElse("Visiteur");
orElseGet() reçoit un fournisseur exécuté uniquement si l’Optional est vide. Dans mon code, c’est celui que je choisis dès que la valeur par défaut n’est pas purement littérale ou qu’elle demande un traitement.
Utilisateur resultat = utilisateur.orElseGet(this::creerUtilisateurInvite);
Enfin, orElseThrow() ou sa version avec fournisseur d’exception convient lorsque l’absence représente une erreur métier. Cette approche transforme une absence silencieuse en décision explicite, avec une exception adaptée au contexte.
Utilisateur resultat = utilisateur.orElseThrow(
() -> new UtilisateurIntrouvableException(id)
);
Transformer et enchaîner les traitements
L’un des vrais avantages de cette API apparaît lorsque plusieurs étapes peuvent échouer. map() applique une fonction uniquement si la valeur existe et encapsule le résultat dans un nouvel Optional. Si la fonction renvoie null, le résultat devient vide au lieu de propager un nouveau null.
Optional email = utilisateur
.map(Utilisateur::getProfil)
.map(Profil::getEmail);
Quand la fonction appelée renvoie déjà un Optional, utilisez flatMap(). Avec map(), vous obtiendriez un type imbriqué comme Optional. flatMap() aplati cette structure et garde une chaîne de traitement lisible.
Optional adresse = utilisateur.flatMap(Utilisateur::getAdresse);
filter() permet de conserver la valeur uniquement si elle respecte une condition. C’est pratique pour exprimer une règle courte, par exemple vérifier qu’un compte est actif avant de poursuivre.
Optional actif = utilisateur
.filter(Utilisateur::estActif);
Avec les streams, Optional.stream() permet aussi de transformer un résultat facultatif en flux de zéro ou une valeur. Je l’utilise surtout lorsqu’une liste de résultats optionnels doit être aplatie avant un traitement collectif.
List utilisateurs = resultats.stream()
.flatMap(Optional::stream)
.toList();
Comparer Optional aux autres façons de gérer l’absence
Optional n’est pas automatiquement meilleur que toutes les alternatives. Le bon choix dépend de la nature de l’absence, de la fréquence du traitement et de la frontière entre votre code et une bibliothèque externe.
| Approche | Point fort | Limite principale |
|---|---|---|
null |
Simple et sans objet supplémentaire | Le contrat est implicite et les erreurs arrivent souvent plus tard |
Optional |
L’absence est visible dans le type de retour | Inadapté comme champ permanent ou paramètre omniprésent |
| Exception | Appropriée lorsqu’il s’agit d’une erreur exceptionnelle | Trop lourde pour une absence normale et prévisible |
| Valeur par défaut | Le code appelant reste très simple | Peut masquer une information importante ou produire une valeur trompeuse |
Pour une recherche qui peut légitimement ne rien retourner, je recommande Optional. Pour une violation de règle, une exception métier est plus honnête. Une liste vide reste souvent préférable à Optional, car elle exprime directement qu’il n’y a aucun élément à parcourir.>
Pour les types primitifs, Java propose OptionalInt, OptionalLong et OptionalDouble. Ils évitent l’emballage systématique d’un int, d’un long ou d’un double, mais leur API est un peu différente. Je les réserve aux cas où la performance ou la nature primitive de la donnée compte réellement.
Éviter les usages qui rendent le code plus compliqué
Le premier piège est de transformer chaque variable en Optional. Un champ Optional peut compliquer la sérialisation, les frameworks et les tests sans apporter de bénéfice clair. Dans la majorité des modèles de données, je préfère un champ classique et je réserve Optional aux méthodes qui annoncent un résultat incertain.
Le deuxième piège consiste à appeler get() juste après isPresent() partout. Ce code n’est pas toujours faux, mais il recrée une logique impérative plus difficile à composer. Utilisez plutôt map(), filter(), orElseGet() ou orElseThrow() selon l’intention.
// Fragile lorsque le contrat évolue
String nom = utilisateur.get().getNom();
// Plus explicite
String nom = utilisateur
.map(Utilisateur::getNom)
.orElse("Visiteur");
Évitez également les Optional imbriqués et les chaînes interminables de quinze appels. Une chaîne courte rend le parcours des données agréable, tandis qu’un bloc excessivement fonctionnel devient parfois moins lisible qu’une condition classique. La lisibilité reste le critère décisif, surtout dans un service maintenu par plusieurs équipes.
Enfin, ne retournez pas un Optional depuis une méthode qui peut elle-même renvoyer null. Les fonctions passées à flatMap() doivent retourner un Optional non nul, sinon l’erreur réapparaît sous une autre forme. La sécurité vient du contrat appliqué avec rigueur, pas du nom de la classe.
Adopter une règle simple dans vos projets Java
Je conseille de commencer par les méthodes de recherche, comme findById(), findByEmail() ou une résolution de configuration facultative. Faites apparaître l’absence dans le type de retour, puis choisissez à l’appel le comportement adapté, qu’il s’agisse d’une valeur de secours, d’une réponse HTTP 404 ou d’une exception métier.
Gardez en tête cette ligne directrice. Optional décrit une absence attendue, tandis qu’une exception signale une situation anormale. Utilisé à cette frontière, il rend les API plus honnêtes, limite les erreurs liées à null et laisse le code Java suffisamment direct pour rester agréable à maintenir.