Qu'est-ce qu'une IOException en Java et comment la gérer ?
java.io.IOException est l'exception de référence pour signaler qu'une opération d'entrée/sortie a échoué en Java. Lecture d'un fichier absent, écriture sur un disque plein, socket fermée par le serveur : tous ces cas remontent une IOException ou l'une de ses sous-classes.
Pourquoi IOException est une exception vérifiée
Elle hérite directement de Exception (pas de RuntimeException), ce qui en fait une checked exception : le compilateur oblige à la capturer ou à la déclarer avec throws.
// Ne compile pas : IOException non gérée
public void read(String path) {
Files.readAllLines(Paths.get(path)); // ❌
}
// Version correcte : déclarer
public void read(String path) throws IOException {
Files.readAllLines(Paths.get(path));
}
Cette contrainte force le développeur à réfléchir explicitement aux cas d'échec, qui sont la règle (pas l'exception) en I/O.
Hiérarchie : les sous-classes utiles
FileNotFoundException— le fichier n'existe pas.EOFException— fin de fichier atteinte prématurément.InterruptedIOException— opération interrompue parThread.interrupt().MalformedURLException— URL syntaxiquement invalide.SocketException,SocketTimeoutException— erreurs réseau.CharacterCodingException— problème d'encodage.
Attraper la sous-classe la plus spécifique permet de répondre différemment selon le cas.
Causes fréquentes
- Fichier inexistant ou chemin incorrect (attention aux séparateurs
/vs\, aux chemins relatifs). - Permissions insuffisantes — lecture d'un fichier système, écriture dans un dossier protégé.
- Flux (stream) déjà fermé — utilisation d'un
InputStreamaprèsclose(). - Disque plein — écriture impossible, exception au
flush(). - Réseau coupé — câble débranché, timeout, serveur down.
- Encodage — lecture d'un fichier ISO-8859-1 avec un décodeur UTF-8 strict.
Le pattern moderne : try-with-resources
Depuis Java 7, le try-with-resources ferme automatiquement les ressources I/O, même en cas d'exception :
import java.io.*;
import java.nio.file.*;
public void copier(Path source, Path cible) throws IOException {
try (InputStream in = Files.newInputStream(source);
OutputStream out = Files.newOutputStream(cible)) {
in.transferTo(out);
}
// in et out sont fermés automatiquement, même si une IOException est lancée
}
Cette syntaxe remplace le vieux finally { if (in != null) in.close(); } fragile et verbeux.
Capturer et réagir proprement
try {
String contenu = Files.readString(Paths.get("config.json"));
traiter(contenu);
} catch (NoSuchFileException e) {
logger.warn("Fichier de config manquant, utilisation des valeurs par défaut");
utiliserValeursParDefaut();
} catch (AccessDeniedException e) {
logger.error("Permission refusée pour {}", e.getFile());
throw new ConfigException("Configuration illisible", e);
} catch (IOException e) {
// Filet de sécurité pour les autres erreurs I/O
logger.error("Erreur I/O inattendue", e);
throw new ConfigException("Impossible de charger la configuration", e);
}
Points clés :
- Attrapez la sous-classe la plus spécifique en premier.
- N'ignorez jamais une exception avec un
catchvide — vous perdez toute information de débogage. - Encapsulez dans une exception métier si votre couche supérieure n'a rien à faire d'une
IOException.
Erreurs courantes à éviter
Attraper trop large
// ❌ Avale même les bugs de logique
try {
...
} catch (Exception e) {
e.printStackTrace();
}
Réencapsuler sans la cause
// ❌ La cause réelle est perdue
catch (IOException e) {
throw new RuntimeException("Oups"); // sans 'e' en second argument
}
// ✅ La cause est préservée
catch (IOException e) {
throw new RuntimeException("Lecture impossible", e);
}
Ignorer silencieusement
// ❌ Le problème se reproduira demain sans trace
try { ... } catch (IOException ignored) { }
Quand une IOException n'en est pas une
Certaines API modernes comme java.nio.file.Files lèvent des sous-classes plus informatives :
NoSuchFileExceptionau lieu deFileNotFoundExceptionAccessDeniedExceptionFileAlreadyExistsExceptionDirectoryNotEmptyException
Elles héritent toutes de IOException, donc un catch (IOException e) les intercepte toutes. Cibler la sous-classe précise rend vos messages d'erreur plus actionnables.
Règle de survie : fermez toujours vos ressources avec try-with-resources, loguez la cause complète, et capturez la sous-classe la plus spécifique quand vous comptez y réagir.