1. Introduction
WatchService est une partie de Java NIO (New IO) qui permet de suivre en temps réel les modifications du système de fichiers. On peut l’imaginer comme une alarme pour des dossiers : dès que quelqu’un ajoute, supprime ou modifie un fichier, vous recevez immédiatement une notification. Cette fonctionnalité est apparue avec Java 7 en même temps que NIO.2, et auparavant les développeurs devaient soit interroger le dossier manuellement (polling), soit utiliser des bibliothèques tierces.
Les usages pratiques de WatchService sont variés : il aide à traiter automatiquement les nouveaux fichiers, à tenir des journaux et faire des sauvegardes, à synchroniser des dossiers avec un serveur ou le cloud, ainsi qu’à surveiller les modifications dans des fichiers de configuration.
Enregistrement des dossiers à surveiller
Pour commencer à surveiller les modifications, il faut :
- Obtenir une instance de WatchService.
- Enregistrer le dossier souhaité et indiquer les événements qui nous intéressent.
Obtenir WatchService
import java.nio.file.*;
WatchService watchService = FileSystems.getDefault().newWatchService();
Enregistrer un dossier
Pour l’enregistrement, on utilise la méthode register de l’objet Path :
Path dir = Paths.get("data/uploads");
dir.register(
watchService,
StandardWatchEventKinds.ENTRY_CREATE, // création de fichiers/dossiers
StandardWatchEventKinds.ENTRY_DELETE, // suppression de fichiers/dossiers
StandardWatchEventKinds.ENTRY_MODIFY // modification de fichiers/dossiers
);
Explications :
- ENTRY_CREATE — quelque chose a été ajouté.
- ENTRY_DELETE — quelque chose a été supprimé.
- ENTRY_MODIFY — un fichier a été modifié (par exemple, du texte ajouté).
Important ! WatchService ne surveille qu’un seul dossier à la fois (sans sous-dossiers). Si vous souhaitez suivre toute l’arborescence — vous devez enregistrer chaque sous-dossier séparément.
2. Traitement des événements : boucle d’attente
Maintenant que nous avons configuré la surveillance (plutôt comme un « voisin observateur » que dans l’esprit du « Big Brother »), on peut attendre les événements. WatchService met en œuvre le pattern « file d’événements » : dès que quelque chose se produit — l’événement est placé dans la file.
Boucle principale
while (true) {
// En attente d'événements (appel bloquant)
WatchKey key = watchService.take();
for (WatchEvent<?> event : key.pollEvents()) {
// Type d’événement : création, suppression, modification
WatchEvent.Kind<?> kind = event.kind();
// Nom du fichier/dossier (Path, relatif au dossier surveillé)
Path filename = (Path) event.context();
if (kind == StandardWatchEventKinds.ENTRY_CREATE) {
System.out.println("Fichier/dossier créé : " + filename);
} else if (kind == StandardWatchEventKinds.ENTRY_DELETE) {
System.out.println("Fichier/dossier supprimé : " + filename);
} else if (kind == StandardWatchEventKinds.ENTRY_MODIFY) {
System.out.println("Fichier/dossier modifié : " + filename);
}
}
// Toujours réinitialiser la clé, sinon la surveillance s'arrêtera !
boolean valid = key.reset();
if (!valid) {
break; // Dossier indisponible, on sort
}
}
Comment cela fonctionne-t-il ?
- WatchService.take() — bloque le thread jusqu’à l’arrivée d’un événement (on peut utiliser poll() pour un mode non bloquant).
- key.pollEvents() — la liste de tous les événements accumulés.
- event.context() — le nom du fichier ou du dossier modifié (relatif au dossier surveillé).
- Après avoir traité les événements, appelez impérativement key.reset(). Si le dossier a été supprimé ou est devenu indisponible, reset() renvoie false — vous pouvez terminer la boucle.
Exemple complet : nous surveillons le dossier "data/uploads"
Ajoutons à notre application d’apprentissage une simple « alarme » sur le dossier de téléchargements :
import java.nio.file.*;
import java.io.IOException;
public class WatcherDemo {
public static void main(String[] args) throws IOException, InterruptedException {
Path dir = Paths.get("data/uploads");
if (!Files.exists(dir)) {
Files.createDirectories(dir);
}
WatchService watchService = FileSystems.getDefault().newWatchService();
dir.register(
watchService,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_DELETE,
StandardWatchEventKinds.ENTRY_MODIFY
);
System.out.println("Surveillance du dossier " + dir.toAbsolutePath());
while (true) {
WatchKey key = watchService.take(); // on attend les événements
for (WatchEvent<?> event : key.pollEvents()) {
WatchEvent.Kind<?> kind = event.kind();
Path filename = (Path) event.context();
System.out.printf("[%s] %s\n", kind.name(), filename);
}
boolean valid = key.reset();
if (!valid) {
System.out.println("Dossier indisponible, surveillance terminée.");
break;
}
}
}
}
À essayer : Exécutez ce code et essayez de créer, supprimer ou modifier un fichier dans le dossier "data/uploads". Le programme réagira immédiatement !
3. Limitations et particularités WatchService
Un seul dossier, sans sous-dossiers
WatchService ne surveille que le dossier que vous avez enregistré. S’il contient des sous-dossiers, les modifications à l’intérieur de ceux-ci ne seront pas détectées — vous devez enregistrer chaque sous-dossier séparément.
Que faire ?
Si vous souhaitez surveiller toute l’arborescence, il faudra parcourir tous les sous-dossiers et les enregistrer un par un. Par exemple, lors de la création d’un nouveau sous-dossier — l’enregistrer immédiatement.
Particularités selon les OS
Windows : WatchService fonctionne de manière assez stable, mais peut parfois « fusionner » plusieurs événements en un seul (par exemple lors de la copie d’un gros fichier).
Linux/macOS : L’implémentation s’appuie sur des mécanismes système (inotify, kqueue). Parfois, les événements peuvent arriver avec un délai, ou au contraire en trop grand nombre (par exemple, ENTRY_MODIFY à chaque enregistrement).
Uniquement des événements par nom
WatchService ne fournit que le nom de l’élément modifié (relatif au dossier surveillé), mais pas le détail de ce qui a changé à l’intérieur du fichier. Si vous devez savoir ce qui a changé — lisez le fichier manuellement.
Perte d’événements en cas de surcharge
Si trop de modifications surviennent dans le dossier en peu de temps (par exemple, la copie massive de milliers de fichiers), la file d’événements peut déborder et une partie des événements sera perdue. Pour les tâches critiques, utilisez des vérifications supplémentaires.
4. Exemples pratiques
Traitement automatique des nouveaux fichiers
Supposons que vous écrivez un programme qui doit traiter automatiquement les nouvelles images apparaissant dans le dossier "photos/incoming".
Path dir = Paths.get("photos/incoming");
WatchService watchService = FileSystems.getDefault().newWatchService();
dir.register(watchService, StandardWatchEventKinds.ENTRY_CREATE);
while (true) {
WatchKey key = watchService.take();
for (WatchEvent<?> event : key.pollEvents()) {
if (event.kind() == StandardWatchEventKinds.ENTRY_CREATE) {
Path filename = (Path) event.context();
if (filename.toString().endsWith(".jpg")) {
System.out.println("Nouvelle photo : " + filename);
// Ici, vous pouvez ajouter un traitement : copie, compression, analyse, etc.
}
}
}
key.reset();
}
Implémentation d’un logger simple des modifications
On peut enregistrer tous les événements dans un fichier journal séparé :
import java.nio.file.*;
import java.io.*;
import java.time.LocalDateTime;
public class SimpleLogger {
public static void main(String[] args) throws IOException, InterruptedException {
Path dir = Paths.get("logs/monitored");
Files.createDirectories(dir);
Path logFile = Paths.get("logs/changes.log");
try (BufferedWriter writer = Files.newBufferedWriter(logFile, StandardOpenOption.CREATE, StandardOpenOption.APPEND)) {
WatchService watchService = FileSystems.getDefault().newWatchService();
dir.register(watchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE);
System.out.println("Surveillance de " + dir);
while (true) {
WatchKey key = watchService.take();
for (WatchEvent<?> event : key.pollEvents()) {
String log = String.format("%s [%s] %s\n",
LocalDateTime.now(), event.kind().name(), event.context());
writer.write(log);
writer.flush();
System.out.print(log);
}
key.reset();
}
}
}
}
Surveillance de la création de nouveaux sous-dossiers (et leur enregistrement)
Si un nouveau sous-dossier est créé dans le dossier surveillé, on peut l’enregistrer immédiatement pour la surveillance future :
if (event.kind() == StandardWatchEventKinds.ENTRY_CREATE) {
Path createdPath = dir.resolve((Path) event.context());
if (Files.isDirectory(createdPath)) {
createdPath.register(watchService, StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY);
System.out.println("Surveillance démarrée pour le nouveau sous-dossier : " + createdPath);
}
}
5. Points importants et erreurs courantes
Erreur n° 1 : vous avez oublié d’appeler key.reset(). Si vous ne réinitialisez pas la clé après le traitement des événements, la surveillance du dossier s’arrêtera et vous ne recevrez plus aucun événement. C’est le piège classique pour les débutants : tout semble fonctionner, puis — paf ! — le programme se tait.
Erreur n° 2 : ignorer les exceptions. Travailler avec le système de fichiers est toujours sujet aux imprévus : le dossier peut être supprimé, le disque — déconnecté, les droits — modifiés. Si vous n’interceptez pas les exceptions (IOException, ClosedWatchServiceException), le programme peut se terminer brutalement.
Erreur n° 3 : ne surveiller qu’un seul dossier. Beaucoup s’attendent à ce qu’en enregistrant un dossier, tous les sous-dossiers soient également surveillés. Ce n’est pas le cas ! Si vous devez surveiller toute la structure — implémentez un enregistrement récursif.
Erreur n° 4 : blocage du thread principal. WatchService.take() bloque le thread jusqu’à l’arrivée d’un événement. Si le thread principal du programme doit faire autre chose, lancez la surveillance dans un thread séparé.
Erreur n° 5 : perte d’événements sous forte charge. Si trop de modifications surviennent dans le dossier, la file d’événements peut déborder. Pour les applications critiques, mettez en place une vérification périodique de l’état du dossier (par exemple, comparer la liste des fichiers chaque minute).
Erreur n° 6 : mauvaise gestion des chemins relatifs. event.context() renvoie le nom du fichier relatif au dossier surveillé. Si vous avez besoin d’un chemin absolu — utilisez dir.resolve((Path) event.context()).
GO TO FULL VERSION