Java : exceptions vérifiées vs non vérifiées
Différence entre les exceptions vérifiées et non vérifiées en Java et quand utiliser chacune.
Java divise ses exceptions en deux groupes, et le compilateur les traite différemment. Les exceptions vérifiées doivent être capturées ou déclarées ; le compilateur refuse de compiler du code qui les ignore. Les exceptions non vérifiées n'ont pas cette contrainte — le compilateur les laisse se propager silencieusement et seul le runtime les intercepte. Cette séparation est l'une des décisions les plus distinctives et les plus débattues de Java, et la comprendre façonne la manière d'écrire du code robuste.
Laquelle est laquelle
Tout ce qui est Throwable tombe dans l'un des trois cas suivants :
Error— catastrophe au niveau de la JVM. Non vérifiée. Ne pas capturer.RuntimeException(une sous-classe deException) et tous ses sous-types. Non vérifiée.- Les autres sous-classes de
Exception. Vérifiées.
La règle est : « RuntimeException, Error et leurs sous-types sont non vérifiés ; tout le reste sous Throwable est vérifié. » C'est une distinction dans l'arbre de classes, pas un indicateur de configuration — il n'existe aucun moyen de rendre une RuntimeException vérifiée ni une IOException non vérifiée.
Ce que « vérifié » signifie dans le code
Si le corps d'une méthode lève une exception vérifiée, la méthode doit faire l'une des deux choses suivantes, sinon le programme ne compilera pas :
// Option 1: catch it
public void load() {
try {
String text = Files.readString(path); // throws IOException
} catch (IOException e) {
// ...
}
}
// Option 2: declare it
public void load() throws IOException {
String text = Files.readString(path);
}Le compilateur parcourt chaque méthode et vérifie : pour chaque exception vérifiée susceptible de s'en échapper, y a-t-il un catch englobant ou une clause throws ? Sinon, erreur.
Les exceptions non vérifiées n'ont pas cette exigence. Vous pouvez lever une IllegalArgumentException depuis n'importe quelle méthode sans toucher à sa signature, et tout appelant est libre de la capturer ou non.
L'idée de conception
La motivation derrière les exceptions vérifiées était la suivante : certaines défaillances sont des résultats attendus de l'opération — un fichier peut ne pas exister, un réseau peut être indisponible — et le langage devait s'assurer que les appelants y réfléchissent. Les exceptions non vérifiées concernent les erreurs de programmation — déréférences null, index hors limites, arguments illégaux — où la bonne correction consiste à réparer le code, pas à ajouter un try/catch.
Cette distinction paraît claire. En pratique, elle se déforme :
- Le code en couches multiplie les déclarations. Une
IOExceptionde bas niveau provenant d'un chargeur de configuration ne reste pas là — chaque méthode qui l'appelle doit la gérer ou la déclarer. Au moment où l'exception atteint le contrôleur, la signature liste six exceptions vérifiées sans rapport les unes avec les autres. - Les lambdas ne les gèrent pas bien.
Stream.map(this::parseLine)ne compilera pas siparseLinelève une exception vérifiée, car l'applydeFunctionn'en déclare aucune. - L'encapsulation est omniprésente. Une solution courante consiste à capturer immédiatement une exception vérifiée et à la relancer en tant qu'exception runtime, ce qui annule l'objectif initial.
La plupart du code Java moderne utilise moins d'exceptions vérifiées que ne le fait la bibliothèque standard — parfois aucune. Les nouveaux frameworks comme Spring s'appuient presque entièrement sur les exceptions runtime pour cette raison.
Quand utiliser l'une ou l'autre
Une règle empirique défendable :
- Utilisez les vérifiées quand l'appelant dispose d'une stratégie de récupération réaliste spécifique à la défaillance — par exemple, réessayer en cas d'erreur réseau, recourir à un autre mécanisme en cas d'échec d'analyse. Forcez-le à y réfléchir.
- Utilisez les non vérifiées quand la défaillance est un bug (
IllegalArgumentException,NullPointerException,IllegalStateException) ou quand aucun appelant ne pourrait raisonnablement se remettre de l'erreur.
Dans le doute, préférez les non vérifiées. Le coût d'une erreur est moindre, et vous pouvez toujours durcir les contraintes plus tard.
L'expérience côté capture
Qu'une exception soit vérifiée ou non change l'apparence du catch :
// checked: caller must catch or declare
try {
parser.parse(path);
} catch (IOException e) {
// handle
}
// unchecked: caller may catch but doesn't have to
try {
return numbers.get(idx);
} catch (IndexOutOfBoundsException e) {
return -1;
}Du point de vue du catch, il n'y a pas de différence comportementale — les deux blocs capturent ce qu'ils déclarent. La différence n'importe qu'au niveau du site d'appel qui n'a pas capturé : une exception vérifiée forcerait une déclaration throws ; une non vérifiée se propage silencieusement.
Une erreur courante : capturer Exception
Comme Exception est le parent des types vérifiés et non vérifiés (sauf Error), catch (Exception e) correspond à presque tout. C'est souvent une mauvaise pratique :
try { complexOperation(); }
catch (Exception e) { log("failed"); } // hides bugs and real failures alikeLe problème n'est pas qu'il capture — c'est qu'il capture trop. Une NullPointerException ici indique un bug ; une IOException indique un vrai échec récupérable ; une RuntimeException d'une bibliothèque que vous ne contrôlez pas peut être n'importe quoi. Les traiter de manière identique signifie généralement n'en gérer aucune correctement. Préférez capturer les types spécifiques pour lesquels vous avez un plan.
Un exemple concret
Un petit programme avec deux méthodes : l'une déclare une IOException vérifiée et l'autre lève une IllegalArgumentException non vérifiée. Le compilateur les traite différemment — seule la vérifiée force la gestion au site d'appel.
Notez trois choses. Supprimez le throws java.io.IOException de maybeReadFile et le fichier ne compilera pas. Supprimez le try/catch englobant du premier appel et le fichier ne compilera pas non plus. Mais il n'y a pas de try/catch autour du dernier requirePositive(-7) — et cela compile bien, même si l'appel plantera le programme. C'est le traitement asymétrique en un seul écran.
Et ensuite
Nous avons parlé de types comme IOException, RuntimeException, Error — mais comment sont-ils réellement liés ? L'arbre de classes est petit et vaut la peine d'être mémorisé. Continuez vers la hiérarchie des exceptions Java.