alibaba/spring-ai-alibaba · warning

启动 WatchService 失败,将不进行热更新

Error message

启动 WatchService 失败,将不进行热更新: {}

What it means

FileModelConfigRepository watches the model-config file directory with a JDK WatchService to hot-reload config changes. If the WatchService cannot be started (directory doesn't exist, no filesystem watch support, insufficient permissions), this warning is logged and hot reloading is disabled; the repo falls back to the snapshot loaded at startup.

Solutions

  1. Verify the model-config directory exists and is readable/writable by the process user; create it if missing
  2. Check filesystem watch support and raise inotify limits (e.g. fs.inotify.max_user_watches) or move config to a local disk
  3. Check ulimit -n / fd limits if 'too many open files' appears in the exception message
  4. Restart the application to reload config manually if hot update is not critical

Example fix

// before
log.warn("启动 WatchService 失败,将不进行热更新: {}", e.getMessage());
// after
log.warn("启动 WatchService 失败,将不进行热更新: {}", e.getMessage(), e); // log full stack to diagnose dir/permission issue
Defensive patterns

Strategy: fallback

Validate before calling

java.nio.file.Path dir = Paths.get(configDir);
if (!java.nio.file.Files.isDirectory(dir) || !java.nio.file.Files.isReadable(dir)) {
    throw new IllegalStateException("config dir missing/unreadable: " + dir);
}

Try / catch

try { repo.afterPropertiesSet(); } catch (Exception e) { log.warn("hot reload disabled: {}", e.getMessage(), e); /* schedule periodic full reload instead */ }

Prevention

When it happens

Trigger: afterPropertiesSet -> startWatchService throws when calling FileSystems.getDefault().newWatchService(), path.register(...), or dir.exists()/mkdirs fails: config directory missing or unreadable, filesystem without inotify support (some network mounts, containers with limits), or too many open files / watch limits exceeded.

Common situations: Running the admin server in a container with a low inotify watch limit; config dir on NFS/CIFS mounts; read-only filesystem; running as a user without permission to the model-config directory.

Related errors


AI-assisted analysis of alibaba/spring-ai-alibaba@f82da0b50f (2026-09-09). Data as JSON: /api/errors/bb69eb8edd4274f6. Report an issue: GitHub.

Appendix: source

Thrown at spring-ai-alibaba-admin/spring-ai-alibaba-admin-server-start/src/main/java/com/alibaba/cloud/ai/studio/admin/repository/impl/FileModelConfigRepository.java:197

                        for (WatchEvent<?> event : key.pollEvents()) {
                            Path changed = (Path) event.context();
                            if (changed != null && configPath.getFileName().equals(changed.getFileName())) {
                                try {
                                    Map<Long, ModelConfigDO> data = loadFromFile(configPath);
                                    snapshot.set(Collections.unmodifiableMap(data));
                                    log.info("模型配置热更新成功,数量: {}", data.size());
                                } catch (Exception e) {
                                    log.error("模型配置热更新失败,沿用旧配置: {}", e.getMessage(), e);
                                }
                            }
                        }
                    } finally {
                        key.reset();
                    }
                }
            });
        } catch (Exception e) {
            log.warn("启动 WatchService 失败,将不进行热更新: {}", e.getMessage());
        }
    }
    
    @Override
    public ModelConfigDO findById(Long id) {
        return snapshot.get().get(id);
    }
    
    @Override
    public boolean existsById(Long id) {
        return snapshot.get().containsKey(id);
    }
    
    @Override
    public List<ModelConfigDO> list(String name, String provider, Integer status, int offset, int limit) {
        List<ModelConfigDO> all = new ArrayList<>(snapshot.get().values());
        return all.stream().filter(m -> name == null || m.getName().contains(name))
                .filter(m -> provider == null || provider.isBlank() || provider.equalsIgnoreCase(m.getProvider()))

View on GitHub (pinned to f82da0b50f)