1. Introdução
WatchService é parte do Java NIO (New I/O) que permite acompanhar alterações no sistema de arquivos em tempo real. Você pode imaginá-lo como um sistema de alarme para pastas: se alguém adicionar, excluir ou modificar um arquivo — você recebe a notificação imediatamente. Esse recurso apareceu no Java 7 junto com o NIO.2; antes disso, os desenvolvedores precisavam fazer polling manual da pasta (polling) ou usar bibliotecas de terceiros.
As aplicações práticas do WatchService são variadas: ele ajuda a processar automaticamente novos arquivos, manter logs e fazer backups, sincronizar pastas com um servidor ou nuvem, além de acompanhar alterações em arquivos de configuração.
Registro de diretórios para monitoramento
Para começar a monitorar alterações, é preciso:
- Obter uma instância de WatchService.
- Registrar a pasta desejada e indicar quais eventos nos interessam.
Obtendo o WatchService
import java.nio.file.*;
WatchService watchService = FileSystems.getDefault().newWatchService();
Registrando a pasta
Para registrar, usamos o método register do objeto Path:
Path dir = Paths.get("data/uploads");
dir.register(
watchService,
StandardWatchEventKinds.ENTRY_CREATE, // criação de arquivos/pastas
StandardWatchEventKinds.ENTRY_DELETE, // exclusão de arquivos/pastas
StandardWatchEventKinds.ENTRY_MODIFY // modificação de arquivos/pastas
);
Explicação:
- ENTRY_CREATE — alguém adicionou algo.
- ENTRY_DELETE — alguém excluiu algo.
- ENTRY_MODIFY — alguém modificou um arquivo (por exemplo, adicionou texto).
Importante! WatchService monitora apenas uma pasta por vez (sem subpastas). Se quiser acompanhar toda a hierarquia — é preciso registrar cada subpasta separadamente.
2. Processamento de eventos: loop de espera
Agora que configuramos o monitoramento (mais como um “vizinho observador”, e não no espírito do “Grande Irmão”), podemos aguardar eventos. O WatchService implementa o padrão de “fila de eventos”: assim que algo acontece — o evento é colocado na fila.
Loop principal
while (true) {
// Aguardamos o surgimento de eventos (chamada bloqueante)
WatchKey key = watchService.take();
for (WatchEvent<?> event : key.pollEvents()) {
// Tipo de evento: criação, exclusão, modificação
WatchEvent.Kind<?> kind = event.kind();
// Nome do arquivo/pasta (Path relativo à pasta monitorada)
Path filename = (Path) event.context();
if (kind == StandardWatchEventKinds.ENTRY_CREATE) {
System.out.println("Arquivo/pasta criado(a): " + filename);
} else if (kind == StandardWatchEventKinds.ENTRY_DELETE) {
System.out.println("Arquivo/pasta excluído(a): " + filename);
} else if (kind == StandardWatchEventKinds.ENTRY_MODIFY) {
System.out.println("Arquivo/pasta modificado(a): " + filename);
}
}
// É obrigatório resetar a chave; caso contrário, o monitoramento será interrompido!
boolean valid = key.reset();
if (!valid) {
break; // Pasta indisponível; saindo
}
}
Como isso funciona?
- WatchService.take() — bloqueia a thread até que surja um evento (você pode usar poll() para modo não bloqueante).
- key.pollEvents() — lista de todos os eventos acumulados.
- event.context() — nome do arquivo ou pasta alterado (relativo ao diretório monitorado).
- Após processar os eventos, chame obrigatoriamente key.reset(). Se a pasta foi excluída ou ficou indisponível, reset() retorna false — você pode encerrar o loop.
Exemplo completo: monitorando a pasta "data/uploads"
Vamos adicionar ao nosso aplicativo de estudo um simples “alarme” para a pasta de uploads:
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("Monitorando a pasta " + dir.toAbsolutePath());
while (true) {
WatchKey key = watchService.take(); // aguardando eventos
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("Pasta indisponível; monitoramento encerrado.");
break;
}
}
}
}
Experimente: Execute esse código e tente criar, excluir ou modificar um arquivo na pasta "data/uploads". O programa reagirá na hora!
3. Limitações e particularidades do WatchService
Apenas uma pasta, sem subpastas
WatchService monitora apenas a pasta que você registrou. Se ela tiver subpastas, as alterações dentro delas não serão percebidas — é preciso registrar cada subpasta separadamente.
O que fazer?
Se você deseja monitorar toda a hierarquia, será necessário percorrer todas as subpastas e registrá-las uma a uma. Por exemplo, ao criar uma nova subpasta — registre-a imediatamente.
Particularidades em diferentes sistemas operacionais
Windows: WatchService funciona de forma bastante estável, mas às vezes pode “agrupar” vários eventos em um só (por exemplo, ao copiar um arquivo grande).
Linux/macOS: A implementação é baseada em mecanismos do sistema (inotify, kqueue). Às vezes os eventos podem chegar com atraso ou, ao contrário, em excesso (por exemplo, ENTRY_MODIFY a cada salvamento).
Apenas eventos com o nome do alvo
WatchService informa apenas o nome do objeto alterado (relativo à pasta monitorada), mas não fornece informações detalhadas sobre o que mudou dentro do arquivo. Se for necessário saber o que exatamente mudou — leia o arquivo manualmente.
Perda de eventos sob alta carga
Se na pasta ocorrerem muitas alterações em um curto período (por exemplo, cópia em massa de milhares de arquivos), a fila de eventos pode encher e parte dos eventos será perdida. Para tarefas críticas, convém implementar verificações adicionais.
4. Exemplos práticos
Processamento automático de novos arquivos
Suponha que você esteja desenvolvendo um programa que deve processar automaticamente novas imagens que aparecem na pasta "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("Nova foto: " + filename);
// Aqui você pode adicionar processamento: cópia, compactação, análise etc.
}
}
}
key.reset();
}
Implementação de um logger simples de alterações
Você pode salvar todos os eventos em um arquivo de log separado:
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("Monitorando " + 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();
}
}
}
}
Acompanhando a criação de novas subpastas (e seu registro)
Se uma nova subpasta for criada dentro da pasta monitorada, você pode registrá-la imediatamente para monitoramento contínuo:
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("Monitoramento iniciado para a nova subpasta: " + createdPath);
}
}
5. Pontos importantes e erros comuns
Erro nº 1: esqueceu de chamar key.reset(). Se você não resetar a chave após processar os eventos, o monitoramento da pasta será interrompido e nenhum outro evento será recebido. Esse é o clássico “tropeço” para iniciantes: parece que tudo está funcionando e, de repente — pronto! — o programa fica silencioso.
Erro nº 2: ignorar exceções. Trabalhar com o sistema de arquivos sempre pode trazer surpresas: a pasta pode ser excluída, o disco — desconectado, as permissões — alteradas. Se você não tratar exceções (IOException, ClosedWatchServiceException), o programa pode encerrar com falha.
Erro nº 3: monitorar apenas uma pasta. Muitos esperam que, ao registrar uma pasta, todas as subpastas também serão monitoradas. Não é assim! Se for necessário acompanhar toda a estrutura — implemente o registro recursivo.
Erro nº 4: bloquear a thread principal. WatchService.take() bloqueia a thread até aparecer um evento. Se a thread principal do programa precisar fazer algo mais, execute o monitoramento em uma thread separada.
Erro nº 5: perda de eventos sob alta carga. Se ocorrerem muitas mudanças na pasta, a fila de eventos pode lotar. Para aplicativos críticos, é recomendável implementar uma verificação periódica do estado da pasta (por exemplo, comparar a lista de arquivos uma vez por minuto).
Erro nº 6: tratamento incorreto de caminhos relativos. event.context() retorna o nome do arquivo relativo à pasta monitorada. Se você precisar do caminho absoluto — use dir.resolve((Path) event.context()).
GO TO FULL VERSION