1Registrar el directorio a vigilar

WatchService se suscribe a eventos del sistema de ficheros: en vez de comprobar el directorio en bucle, el SO avisa cuando cambia algo.

java
Path directorio = Path.of("buzon-entrada");
WatchService watcher = FileSystems.getDefault().newWatchService();
directorio.register(watcher, StandardWatchEventKinds.ENTRY_CREATE);

2Atender el evento ENTRY_CREATE

Solo interesa la creación de ficheros nuevos, no su modificación ni su borrado.

EventoCuándo se dispara
ENTRY_CREATESe crea un fichero o subdirectorio — el que usa esta actividad
ENTRY_MODIFYSe modifica el contenido de una entrada
ENTRY_DELETESe elimina una entrada

3Bucle de procesamiento y reset()

Cada WatchKey debe reiniciarse tras procesar sus eventos. Olvidarlo hace que «el vigilante deje de funcionar en silencio»: sin excepción visible, simplemente deja de recibir eventos.

java
while (activo) {
    WatchKey clave = watcher.take(); // bloquea hasta que hay eventos
    for (WatchEvent<?> evento : clave.pollEvents()) {
        if (evento.kind() == StandardWatchEventKinds.ENTRY_CREATE) {
            Path rutaCompleta = directorio.resolve((Path) evento.context());
            log.info("Fichero detectado: {}", rutaCompleta);
            procesar(rutaCompleta);
            ultimoDetectado = rutaCompleta;
        }
    }
    boolean sigueValida = clave.reset(); // obligatorio para seguir recibiendo eventos
    if (!sigueValida) {
        break;
    }
}

4take() vs. poll(timeout)

Decide y documenta cuál usar: no es una elección neutra.

EstrategiaComportamientoCuándo conviene
watcher.take()Bloquea indefinidamente hasta que hay eventosHilo dedicado en exclusiva a vigilar
watcher.poll(timeout, unidad)Devuelve enseguida o espera como máximo el timeoutEl hilo necesita atender otras tareas periódicas además de vigilar

5Ciclo de vida: hilo propio, arranque y parada

El vigilante vive en su propio hilo, arrancado al iniciar la aplicación y detenido de forma ordenada al pararla — no debe quedar un hilo colgado.

java
@Component
public class VigilanteBuzonEntrada {

    private Thread hilo;
    private volatile boolean activo;

    @PostConstruct
    void iniciar() {
        activo = true;
        hilo = new Thread(this::vigilar, "vigilante-buzon");
        hilo.start();
    }

    @PreDestroy
    void detener() throws InterruptedException {
        activo = false;
        hilo.interrupt();
        hilo.join();
    }
}

6Endpoint de estado

java
@GetMapping("/buzon-entrada/estado")
public EstadoBuzon estado() {
    return new EstadoBuzon(vigilante.isActivo(), vigilante.getUltimoDetectado());
}

!Problemas frecuentes

Olvidar reset()Sin clave.reset(), el vigilante deja de recibir eventos sin lanzar ningún error: parece que funciona pero está muerto.
Cerrar el WatchServiceCierra el WatchService (o úsalo con try-with-resources si el ciclo de vida lo permite) al detener la aplicación para no dejar recursos del SO abiertos.

✓Criterios de aceptación

  • El registro se hace sobre el directorio correcto al arrancar
  • La detección ocurre en un tiempo razonable
  • Se llama a reset() de la WatchKey tras procesar los eventos
  • La detención es ordenada, sin hilos colgados
  • La decisión entre take()/poll() está justificada en el diario técnico
  • El endpoint refleja correctamente el estado activo y el último fichero detectado

Adaptado de apartado d — WatchService y apartado h — Actividades.