provectus/kafka-ui · error · ValidationException

File already exists and is not writable

Error message

File already exists and is not writable

What it means

After ensuring the target path is a file and creating parent directories, writeYamlToFile() checks that an existing config file is writable via Files.isWritable(). If the file exists but the process has no write permission on it, persisting the dynamic config throws this ValidationException.

Solutions

  1. chown/chmod the config file so the kafka-ui process user can write it (e.g. chmod u+w, chown to the container UID)
  2. Move dynamic.config.path to a writable volume instead of a read-only mount
  3. Ensure the container's securityContext allows writing to that path

Example fix

// before
-r--r--r-- application-config.yml
// after (shell)
chmod u+w application-config.yml && chown kafka-ui:kafka-ui application-config.yml
Defensive patterns

Strategy: validation

Validate before calling

Path p = Path.of(configPath);
if (Files.exists(p) && !Files.isWritable(p)) {
  throw new IllegalStateException("Config file exists but is not writable by this process");
}

Try / catch

try {
  dynamicConfigOperations.persist(props);
} catch (ValidationException e) {
  if (e.getMessage().contains("not writable")) {
    // chmod/chown or relocate to a writable volume
  }
}

Prevention

When it happens

Trigger: dynamic.config.path points to an already-created file owned by another user or opened read-only (e.g. a read-only mounted secret/ConfigMap file) while kafka-ui tries to save new properties.

Common situations: Container runs as non-root but the config file was created by root; the file lives on a read-only volume mount; file permissions like 0444 set by a provisioning script.

Understand the failure class

Background: "Permission denied" / "Failed to write" file errors: why a library can't write its files to disk (EACCES, EPERM, ENOSPC) and how to fix them — this error's family across 43 libraries.

Related errors


AI-assisted analysis of provectus/kafka-ui@83b5a60cc0 (2026-09-08). Data as JSON: /api/errors/105d4ffb3d89f8cf. Report an issue: GitHub.

Appendix: source

Thrown at kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/DynamicConfigOperations.java:180

  private void checkIfDynamicConfigEnabled() {
    if (!dynamicConfigEnabled()) {
      throw new ValidationException(
          "Dynamic config change is not allowed. "
              + "Set dynamic.config.enabled property to 'true' to enabled it.");
    }
  }

  @SneakyThrows
  private void writeYamlToFile(String yaml, Path path) {
    if (Files.isDirectory(path)) {
      throw new ValidationException("Dynamic file path is a directory, but should be a file path");
    }
    if (!Files.exists(path.getParent())) {
      Files.createDirectories(path.getParent());
    }
    if (Files.exists(path) && !Files.isWritable(path)) {
      throw new ValidationException("File already exists and is not writable");
    }
    try {
      Files.writeString(
          path,
          yaml,
          StandardOpenOption.CREATE,
          StandardOpenOption.WRITE,
          StandardOpenOption.TRUNCATE_EXISTING // to override existing file
      );
    } catch (IOException e) {
      throw new ValidationException("Error writing to " + path, e);
    }
  }

  private String serializeToYaml(PropertiesStructure props) {
    //representer, that skips fields with null values
    Representer representer = new Representer(new DumperOptions()) {
      @Override

View on GitHub (pinned to 83b5a60cc0)