Pourquoi attraper des exceptions en Java, quand vous pouvez attraper Throwables?

Nous avons récemment rencontré un problème avec une application serveur Java où l’application émettait des erreurs qui n’étaient pas interceptées car Error est une sous-classe distincte de Throwable et que nous ne capturions que des exceptions.

Nous avons résolu le problème immédiat en interceptant Throwables plutôt que des exceptions, mais cela m’a fait réfléchir à la raison pour laquelle vous voudriez jamais intercepter des exceptions, plutôt que des Throwables, car les erreurs vous manqueraient.

Alors, pourquoi voudriez-vous attraper des exceptions, quand vous pouvez attraper des Throwables ?

Tout dépend un peu de ce que vous allez faire avec une erreur une fois que vous l’avez détecté. En général, les erreurs de capture ne devraient probablement pas être considérées comme faisant partie de votre stream d’exceptions “normales”. Si vous en attrapez un, vous ne devriez pas penser à “continuer comme si rien ne s’était passé”, car la JVM (et diverses bibliothèques) utiliserait des erreurs pour signaler que “quelque chose de vraiment grave est arrivé et que nous devons fermer le plus vite possible “. En général, il est préférable de les écouter quand ils vous disent que la fin est proche.

Un autre problème est que la récupérabilité d’une erreur peut dépendre de la machine virtuelle particulière, ce que vous pouvez ou non contrôler.

Cela dit, il y a quelques cas isolés où il est sécuritaire et / ou souhaitable de détecter des erreurs, ou au moins certaines sous-classes:

  • Il y a des cas où vous voulez vraiment arrêter le déroulement normal du stream: par exemple, si vous êtes dans un servlet, vous ne voudrez peut-être pas que le gestionnaire d’exceptions par défaut du coureur Servlet annonce au monde que vous avez une erreur OutOfMemoryEr vous ne pouvez pas vous en remettre.
  • À l’occasion, une erreur est générée dans les cas où la machine virtuelle Java peut récupérer correctement la cause de l’erreur. Par exemple, si une erreur OutOfMemoryError se produit lors de la tentative d’allocation d’un tableau, au moins dans Hotspot, il semble que vous puissiez vous en sortir en toute sécurité. (Il y a bien sûr d’autres cas où une erreur OutOfMemoryError peut être lancée là où il n’est pas sûr d’essayer et de travailler.)

Donc, le résultat est le suivant: si vous attrapez Throwable / Error plutôt que Exception, cela devrait être un cas bien défini où vous savez que vous “faites quelque chose de spécial” .

Edit: C’est peut-être évident, mais j’ai oublié de dire qu’en pratique, la JVM pourrait ne pas invoquer votre clause catch sur une erreur. J’ai certainement vu Hotspot briller sur les tentatives d’attraper certains OutOfMemoryErrors et NoClassDefFoundError.

A partir de la documentation de l’API Java:

La classe Exception et ses sous-classes sont une forme de Throwable qui indique les conditions qu’une application raisonnable peut vouloir intercepter.

Une Error est une sous-classe de Throwable qui indique des problèmes sérieux qu’une application raisonnable ne doit pas tenter d’attraper.

Les erreurs sont généralement de bas niveau (par exemple, déclenchées par la machine virtuelle) et ne doivent pas être interceptées par l’application car une suite raisonnable pourrait ne pas être possible.

Habituellement, les erreurs sont des problèmes dont vous ne pouvez pas récupérer, comme OutOfMemoryError . Il n’y a rien à faire en les attrapant, vous devriez donc les laisser s’échapper et faire tomber la machine virtuelle.

Un grand nombre des autres réponses examinent les choses de manière trop étroite.

Comme ils disent, si vous écrivez le code d’application, vous ne devriez pas attraper Throwable. Vous ne pouvez rien y faire, alors mieux vaut laisser le système environnant (JVM ou framework) gérer ces problèmes.

Toutefois, si vous écrivez “code système”, comme un framework ou un autre code de bas niveau, vous pouvez très bien vouloir attraper Throwable. La raison est d’ essayer de signaler l’exception, peut-être dans un fichier journal. Dans certains cas, votre journalisation échouera, mais dans la plupart des cas, cela réussira et vous aurez les informations nécessaires pour résoudre le problème. Une fois que vous avez effectué votre tentative de journalisation, vous devez alors renvoyer, tuer le thread en cours ou quitter la JVM entière.

Je vais suivre un itinéraire légèrement différent des autres.

Il y a beaucoup de cas où vous voudriez attraper Throwable (principalement pour enregistrer / signaler que quelque chose de mal est arrivé).

Cependant , vous devez faire attention et relancer tout ce que vous ne pouvez pas traiter.

Cela est particulièrement vrai pour ThreadDeath.

Si vous attrapez jamais Throwable, assurez-vous de faire ce qui suit:

 try { ... } catch (SomeExceptionYouCanDoSomethingWith e) { // handle it } catch (ThreadDeath t) { throw t; } catch (Throwable t) { // log & rethrow } 

Ne jamais attraper Throwable ou Error et vous ne devriez pas non plus attraper une Exception générique. Error sont généralement des choses dont la plupart des programmes raisonnables ne peuvent pas récupérer. Si vous savez ce qui se passe, vous pourriez être en mesure de récupérer d’une erreur spécifique, mais dans ce cas, vous ne devriez intercepter que cette erreur particulière et pas toutes les erreurs en général.

Une bonne raison pour ne pas attraper Error est due à ThreadDeath . ThreadDeath est une occurrence assez normale qui peut théoriquement être lancée de n’importe où (d’autres processus, comme la JVM elle-même, peuvent la générer), et le but est de détruire votre thread. ThreadDeath est explicitement une Error plutôt qu’une Exception car trop de personnes interceptent toutes les Exception . Si vous ThreadDeath attraper ThreadDeath , vous devez le relancer afin que votre thread meurt réellement.

Si vous contrôlez la source, celle-ci devrait probablement être restructurée pour générer une Exception plutôt qu’une Error . Si vous ne le faites pas, vous devriez probablement appeler le fournisseur et vous plaindre. Error s doivent être réservées uniquement aux éléments terminaux sans moyen de les récupérer.

Il y a au moins un cas où je pense que vous devrez peut-être intercepter une exception jetable ou une exception générique – si vous exécutez un thread séparé pour effectuer une tâche, vous voudrez peut-être savoir si la méthode “run” du thread en a capturé exception ou non. Dans ce cas, vous ferez probablement quelque chose comme ceci:

 public void run() { try { ... } catch(Throwable t) { threadCompletionError = t; } } 

Je ne suis vraiment pas sûr que ce soit la meilleure approche, mais ça marche. Et j’avais une erreur “ClassNotFound” soulevée par la JVM, et c’est une erreur et non une exception. Si je laisse tomber l’exception, je ne sais pas comment l’attraper dans le thread appelant (il y a probablement une méthode mais je ne le sais pas encore).

Comme pour la méthode ThreadDeath, n’appelez pas la méthode “Thread.stop ()”. Appelez Thread.interrupt et demandez à votre thread de vérifier s’il a été interrompu par quelqu’un.

Normalement, lors de la programmation, vous ne devez capturer qu’une exception spécifique (telle que IOException ). Dans beaucoup de programmes, vous pouvez voir un très haut niveau

 try { ... } catch(Exception e) { ... } 

Cela intercepte toutes les erreurs qui pourraient être récupérées et toutes celles qui indiquent un bogue dans votre code, par exemple InvalidArgumentException , NullPointerException . Vous pouvez ensuite envoyer automatiquement un courrier électronique, afficher une boîte de message ou tout ce que vous voulez, car le JavaVM lui-même fonctionne toujours correctement.

Tout ce qui provient de Error est quelque chose de très mauvais, on ne peut rien faire contre. La question est, s’il est logique d’attraper une OutOfMemoryError ou une VirtualMachineError . (C’est une erreur dans le JavaVM lui-même, vous ne pouvez probablement même pas afficher un message ou envoyer un e-mail)

Vous ne devriez probablement pas une classe dérivée d’ Error , vous devriez dériver d’ Exception ou RuntimeException .

Je sais que cela peut être contre-intuitif, mais ce n’est pas parce que vous pouvez attraper toutes sortes d’exceptions et Throwables et Erreurs que vous devriez le faire.

Une capture trop agressive de java.lang.Exception peut conduire à des bogues graves dans les applications – car les exceptions inattendues ne font jamais surface, ne sont jamais détectées lors du développement / des tests, etc.

Meilleure pratique: attraper seulement

  1. Des exceptions que vous pouvez gérer
  2. Exceptions nécessaires pour attraper

En général, il serait raisonnable d’essayer d’attraper les erreurs, ne serait-ce que pour que cela puisse être correctement signalé.

Cependant, je pense qu’il y a des cas où il serait approprié d’attraper une erreur et de ne pas la signaler. Je fais référence à UnsatisfiedLinkError. Dans JAI, la bibliothèque utilise des bibliothèques natives pour implémenter la plupart des opérateurs pour des raisons de performances. Cependant, si la bibliothèque ne parvient pas à se charger (n’existe pas, format incorrect, plateforme non prise en charge), la bibliothèque fonctionnera .

Cet article ne rendra pas heureux les “exceptions vérifiées”. Cependant, je me base sur la manière dont les exceptions Java sont conçues pour être utilisées par les personnes qui ont créé le langage.

Tableau de référence rapide:

  • Jetable – ne jamais attraper ça
  • Erreur – indique une erreur de machine virtuelle – n’attrape jamais cela
  • RuntimeException – indique une erreur du programmeur – n’attrape jamais cela
  • Exception – ne jamais attraper ça

La raison pour laquelle vous ne devez pas intercepter Exception est qu’il intercepte toutes les sous-classes, y compris RuntimeException.

La raison pour laquelle vous ne devez pas intercepter Throwable est qu’il intercepte toutes les sous-classes, y compris Error et Exception.

Il y a des exceptions (sans jeu de mots) aux “règles” ci-dessus:

  • Code avec lequel vous travaillez (d’un tiers) lance Throwable ou Exception
  • Vous exécutez un code non fiable qui pourrait provoquer le blocage de votre programme s’il présentait une exception.

Pour le second, il suffit généralement de placer le code principal, le code de gestion des événements et les threads avec le composant catch dans Throwable, puis de vérifier le type réel de l’exception et de le traiter comme il convient.

Un peu hors sujet, mais vous voudrez peut-être aussi regarder ce très bon article sur les exceptions.

Pourquoi ne pas les attraper tous? Ensuite, connectez-les, au moins vous savez que vous avez une erreur. Donc mieux vaut attraper Throwable / s que Exception / s seulement.

Il n’y a pas de raison d’attraper une erreur .

Les erreurs sont utilisées pour indiquer que quelque chose a vraiment mal tourné dans votre application et qu’il doit être redémarré.

Par exemple, une erreur commune est

 java.lang.OutOfMemoryError 

Il n’y a rien que vous puissiez faire quand cela se produit. Est déjà trop tard, la JVM a épuisé toutes ses options pour obtenir plus de mémoire mais c’est impossible.

Voir cette autre réponse pour mieux comprendre les trois types d’exceptions .