W3docs

Hiérarchie des exceptions Java

Comment Throwable, Error, Exception et RuntimeException sont liés dans la hiérarchie des classes d'exceptions Java.

Chaque exception en Java fait partie d'un petit arbre de classes dont la racine est java.lang.Throwable. Connaître la forme de cet arbre est constamment utile : cela explique pourquoi catch (Exception e) n'attrape pas OutOfMemoryError, pourquoi RuntimeException est spécial, et pourquoi certaines exceptions vous obligent à les gérer alors que d'autres non. Tout l'ensemble tient en un seul schéma.

Cette page cartographie cet arbre : la racine Throwable, les branches Error et Exception, où se situe la frontière checked/unchecked, et comment tout cela détermine ce que vos blocs catch attrapent réellement.

L'arbre complet

Throwable
├── Error                       (unchecked — JVM-level)
│   ├── OutOfMemoryError
│   ├── StackOverflowError
│   ├── VirtualMachineError
│   └── ...
└── Exception                   (checked by default)
    ├── IOException             (checked)
    ├── SQLException            (checked)
    ├── ClassNotFoundException  (checked)
    ├── ...
    └── RuntimeException        (unchecked)
        ├── NullPointerException
        ├── IllegalArgumentException
        ├── IndexOutOfBoundsException
        ├── ArithmeticException
        ├── ClassCastException
        ├── IllegalStateException
        └── ...

L'ensemble de l'arbre est une seule hiérarchie de classes. C'est pourquoi un catch pour un supertype attrape ses sous-types, pourquoi les variables d'exception se comportent comme des références ordinaires, et pourquoi vous pouvez stocker une IOException dans un champ de type Exception.

Throwable — la racine

Throwable est ce que throw accepte et ce que catch déclare. Tout ce que vous souhaitez lever ou gérer est une sous-classe de Throwable. La classe elle-même fournit :

  • Un message (getMessage())
  • Une trace de pile capturée à la construction (getStackTrace(), printStackTrace())
  • Une cause optionnelle — un autre Throwable qui a déclenché celui-ci (getCause())
  • Des exceptions supprimées — échecs secondaires attachés par try-with-resources (getSuppressed())

Vous n'étendez presque jamais Throwable directement. La conception intéressante se trouve un niveau en dessous.

Error — ne pas attraper

Error et ses sous-classes représentent des défaillances que la JVM signale : mémoire insuffisante, débordement de pile, un fichier de classe qui ne peut pas être lié. Par convention, vous ne les attrapez pas dans le code applicatif, car :

  1. Elles signifient généralement que la JVM n'est plus fiable. Continuer après un OutOfMemoryError fonctionne rarement longtemps.
  2. Il n'existe presque jamais d'action de récupération sensée que votre code peut entreprendre.
  3. La JVM elle-même s'en occupe peut-être déjà ; les intercepter perturbe ce mécanisme.

Error est techniquement attrapable — Java ne vous en empêche pas. Mais la convention est si forte que « attraper Error » est considéré comme un signal d'alarme lors d'une revue de code. Le seul cas d'utilisation légitime est un superviseur de premier niveau (un gestionnaire de requêtes, un lanceur de tâches) qui journalise et se termine proprement.

Exception — les échecs applicatifs

Tout ce qui se trouve sous Throwable autre que Error est Exception ou l'un de ses sous-types. La frontière entre checked et unchecked passe à l'intérieur de cette branche, pas au-dessus :

  • Les sous-classes directes de Exception qui ne sont pas RuntimeException sont checked.
  • RuntimeException et tous ses sous-types sont unchecked.

C'est pourquoi catch (Exception e) correspond à la fois à IOException (checked) et à NullPointerException (unchecked) — ce sont des frères sous la même racine. C'est aussi pourquoi attraper Exception est si grossier : vous avez regroupé les deux branches ensemble.

La distinction checked/unchecked a de vraies conséquences sur vos signatures de méthodes — les exceptions checked doivent être déclarées avec throws ou gérées, les unchecked non. Ce compromis fait l'objet d'un traitement dédié dans checked vs. unchecked exceptions.

RuntimeException — la branche des bugs

RuntimeException et ses sous-types sont réservés par convention aux erreurs de programmation qui ne devraient pas se produire dans un code correct :

  • NullPointerException — déréférencement de null
  • IllegalArgumentException — argument invalide
  • IllegalStateException — état incorrect pour l'opération
  • IndexOutOfBoundsException — indice de liste/tableau dépassant la limite
  • ArithmeticException — division par zéro
  • ClassCastException — cast invalide
  • UnsupportedOperationException — opération non supportée (par exemple, mutation d'une liste non modifiable)

Vous pouvez les lever de n'importe où sans modifier votre signature de méthode. Les appelants peuvent les attraper, mais le langage ne les y oblige pas. Ce sont les bons outils quand l'échec signifie « c'est un bug » plutôt que « cela arrive parfois ».

Relations de types dans catch

Un catch (T e) correspond à toute valeur levée qui est une instance de T ou d'un sous-type de T. La hiérarchie dicte donc directement ce que vos blocs catch voient :

try { ... }
catch (IOException e)        { ... }   // catches FileNotFoundException too
catch (Exception e)          { ... }   // catches almost everything below Throwable
catch (Throwable t)          { ... }   // catches everything, including Error — don't

L'ordre est important ici : comme chaque catch correspond aux sous-types, vous devez lister les types les plus spécifiques en premier. Si catch (Exception e) venait avant catch (IOException e), le bloc IOException serait inaccessible et le compilateur le rejetterait. Consultez multiple catch blocks pour les règles complètes.

C'est aussi pourquoi une forme attrape-tout est dangereuse. catch (Exception) attrape NullPointerException (un bug) et IOException (un échec récupérable) et IllegalStateException (probablement un bug) — le tout dans un seul bloc, sans possibilité de les gérer différemment. La hiérarchie vous invite à être plus précis.

Rechercher des types

Quand vous rencontrez une nouvelle exception dans une trace de pile et voulez savoir où elle se situe :

  • Elle est dans java.lang s'il s'agit d'une erreur fondamentale (NullPointerException, ArithmeticException).
  • Elle est dans java.io, java.sql, java.net si elle concerne le domaine de ce package.
  • Une classe se terminant par Error se trouve presque certainement sous Error.
  • Une classe se terminant par Exception se trouve presque certainement sous Exception — mais vérifiez si elle étend RuntimeException pour savoir si elle est checked.

La Javadoc affiche la chaîne d'héritage en haut de chaque page. En cas de doute, consultez-la.

Un exemple concret

Un petit programme qui parcourt la hiérarchie avec des vérifications instanceof. Il attrape une séquence de levées comme Throwable, puis indique où chacune se situe dans l'arbre.

java— editable, runs on the server

L'auxiliaire isChecked encode la règle en une seule ligne : le sous-ensemble checked correspond à Exception moins RuntimeException. Exécutez le programme et vous verrez exactement où se situent chacun des cinq : IOException est checked, les deux RuntimeException ne le sont pas, OutOfMemoryError est une Error (donc ni une Exception ni checked), et la Exception simple est checked.

La suite

L'arbre intégré couvre la plupart des cas. Quand votre domaine a ses propres échecs — « facture introuvable », « configuration obsolète » — vous écrivez vos propres classes en étendant le bon nœud de cet arbre. Continuez vers Java custom exceptions.

Lectures connexes :

Pratique

Pratique
Un programme applique `catch (Exception e)` autour d'un bloc. Lesquels de ces éléments seront attrapés ?
Un programme applique `catch (Exception e)` autour d'un bloc. Lesquels de ces éléments seront attrapés ?
Was this page helpful?